forge-openclaw-plugin 0.2.3 → 0.2.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.
Files changed (117) hide show
  1. package/README.md +114 -6
  2. package/dist/assets/board-CzgvdLO8.js +6 -0
  3. package/dist/assets/board-CzgvdLO8.js.map +1 -0
  4. package/dist/assets/favicon-BCHm9dUV.ico +0 -0
  5. package/dist/assets/index-8d_oM8fL.js +27 -0
  6. package/dist/assets/index-8d_oM8fL.js.map +1 -0
  7. package/dist/assets/index-D4A_bq8m.css +1 -0
  8. package/dist/assets/motion-STUd1O46.js +10 -0
  9. package/dist/assets/motion-STUd1O46.js.map +1 -0
  10. package/dist/assets/plus-jakarta-sans-latin-ext-wght-normal-DmpS2jIq.woff2 +0 -0
  11. package/dist/assets/plus-jakarta-sans-latin-wght-normal-eXO_dkmS.woff2 +0 -0
  12. package/dist/assets/plus-jakarta-sans-vietnamese-wght-normal-qRpaaN48.woff2 +0 -0
  13. package/dist/assets/sora-latin-ext-wght-normal-CawQDOvP.woff2 +0 -0
  14. package/dist/assets/sora-latin-wght-normal-DdqRvwsR.woff2 +0 -0
  15. package/dist/assets/space-grotesk-latin-500-normal-CNSSEhBt.woff +0 -0
  16. package/dist/assets/space-grotesk-latin-500-normal-lFbtlQH6.woff2 +0 -0
  17. package/dist/assets/space-grotesk-latin-700-normal-CwsQ-cCU.woff +0 -0
  18. package/dist/assets/space-grotesk-latin-700-normal-RjhwGPKo.woff2 +0 -0
  19. package/dist/assets/space-grotesk-latin-ext-500-normal-3dgZTiw9.woff +0 -0
  20. package/dist/assets/space-grotesk-latin-ext-500-normal-DUe3BAxM.woff2 +0 -0
  21. package/dist/assets/space-grotesk-latin-ext-700-normal-BQnZhY3m.woff2 +0 -0
  22. package/dist/assets/space-grotesk-latin-ext-700-normal-HVCqSBdx.woff +0 -0
  23. package/dist/assets/space-grotesk-vietnamese-500-normal-BTqKIpxg.woff +0 -0
  24. package/dist/assets/space-grotesk-vietnamese-500-normal-BmEvtly_.woff2 +0 -0
  25. package/dist/assets/space-grotesk-vietnamese-700-normal-DMty7AZE.woff2 +0 -0
  26. package/dist/assets/space-grotesk-vietnamese-700-normal-Duxec5Rn.woff +0 -0
  27. package/dist/assets/table-CtNlETLc.js +23 -0
  28. package/dist/assets/table-CtNlETLc.js.map +1 -0
  29. package/dist/assets/ui-ThzkR_oW.js +46 -0
  30. package/dist/assets/ui-ThzkR_oW.js.map +1 -0
  31. package/dist/assets/vendor-CRS-psbw.css +1 -0
  32. package/dist/assets/vendor-DyHAI6nk.js +423 -0
  33. package/dist/assets/vendor-DyHAI6nk.js.map +1 -0
  34. package/dist/assets/viz-BJuBCz_G.js +34 -0
  35. package/dist/assets/viz-BJuBCz_G.js.map +1 -0
  36. package/dist/favicon.ico +0 -0
  37. package/dist/favicon.png +0 -0
  38. package/dist/index.html +29 -0
  39. package/dist/openclaw/api-client.d.ts +8 -0
  40. package/dist/openclaw/api-client.js +31 -4
  41. package/dist/openclaw/local-runtime.d.ts +3 -0
  42. package/dist/openclaw/local-runtime.js +135 -0
  43. package/dist/openclaw/parity.d.ts +4 -4
  44. package/dist/openclaw/parity.js +23 -33
  45. package/dist/openclaw/plugin-entry-shared.d.ts +5 -3
  46. package/dist/openclaw/plugin-entry-shared.js +52 -10
  47. package/dist/openclaw/routes.d.ts +12 -3
  48. package/dist/openclaw/routes.js +156 -924
  49. package/dist/openclaw/tools.js +242 -1100
  50. package/dist/server/app.js +2450 -0
  51. package/dist/server/db.js +313 -0
  52. package/dist/server/e2e-server.js +20 -0
  53. package/dist/server/errors.js +15 -0
  54. package/dist/server/index.js +16 -0
  55. package/dist/server/managers/base.js +17 -0
  56. package/dist/server/managers/contracts.js +47 -0
  57. package/dist/server/managers/platform/api-gateway-manager.js +11 -0
  58. package/dist/server/managers/platform/audit-manager.js +15 -0
  59. package/dist/server/managers/platform/authentication-manager.js +56 -0
  60. package/dist/server/managers/platform/authorization-manager.js +56 -0
  61. package/dist/server/managers/platform/background-job-manager.js +10 -0
  62. package/dist/server/managers/platform/configuration-manager.js +33 -0
  63. package/dist/server/managers/platform/database-manager.js +14 -0
  64. package/dist/server/managers/platform/event-bus-manager.js +7 -0
  65. package/dist/server/managers/platform/external-service-manager.js +11 -0
  66. package/dist/server/managers/platform/health-manager.js +7 -0
  67. package/dist/server/managers/platform/migration-manager.js +8 -0
  68. package/dist/server/managers/platform/search-index-manager.js +4 -0
  69. package/dist/server/managers/platform/secrets-manager.js +19 -0
  70. package/dist/server/managers/platform/session-manager.js +121 -0
  71. package/dist/server/managers/platform/storage-manager.js +16 -0
  72. package/dist/server/managers/platform/token-manager.js +37 -0
  73. package/dist/server/managers/platform/transaction-manager.js +8 -0
  74. package/dist/server/managers/platform/trusted-network.js +39 -0
  75. package/dist/server/managers/runtime.js +56 -0
  76. package/dist/server/managers/type-guards.js +4 -0
  77. package/dist/server/openapi.js +3512 -0
  78. package/dist/server/psyche-types.js +395 -0
  79. package/dist/server/repositories/activity-events.js +157 -0
  80. package/dist/server/repositories/collaboration.js +497 -0
  81. package/dist/server/repositories/comments.js +176 -0
  82. package/dist/server/repositories/deleted-entities.js +192 -0
  83. package/dist/server/repositories/domains.js +30 -0
  84. package/dist/server/repositories/event-log.js +64 -0
  85. package/dist/server/repositories/goals.js +159 -0
  86. package/dist/server/repositories/projects.js +214 -0
  87. package/dist/server/repositories/psyche.js +1356 -0
  88. package/dist/server/repositories/rewards.js +675 -0
  89. package/dist/server/repositories/settings.js +399 -0
  90. package/dist/server/repositories/tags.js +160 -0
  91. package/dist/server/repositories/task-runs.js +488 -0
  92. package/dist/server/repositories/tasks.js +413 -0
  93. package/dist/server/services/context.js +214 -0
  94. package/dist/server/services/dashboard.js +170 -0
  95. package/dist/server/services/entity-crud.js +576 -0
  96. package/dist/server/services/gamification.js +215 -0
  97. package/dist/server/services/insights.js +91 -0
  98. package/dist/server/services/projects.js +75 -0
  99. package/dist/server/services/psyche.js +63 -0
  100. package/dist/server/services/relations.js +28 -0
  101. package/dist/server/services/reviews.js +88 -0
  102. package/dist/server/services/run-recovery.js +13 -0
  103. package/dist/server/services/tagging.js +49 -0
  104. package/dist/server/services/task-run-watchdog.js +92 -0
  105. package/dist/server/services/work-time.js +176 -0
  106. package/dist/server/types.js +999 -0
  107. package/dist/server/web.js +91 -0
  108. package/openclaw.plugin.json +22 -10
  109. package/package.json +17 -4
  110. package/server/migrations/001_core.sql +333 -0
  111. package/server/migrations/002_psyche.sql +241 -0
  112. package/server/migrations/003_timer_execution.sql +18 -0
  113. package/server/migrations/004_psyche_linked_entities.sql +5 -0
  114. package/server/migrations/005_adaptive_schemas.sql +157 -0
  115. package/server/migrations/006_psyche_auth_setting.sql +4 -0
  116. package/server/migrations/007_deleted_entities.sql +16 -0
  117. package/skills/forge-openclaw/SKILL.md +189 -275
@@ -1,40 +1,60 @@
1
- import { canBootstrapOperatorSession, callForgeApi, expectForgeSuccess, readJsonRequestBody, readSingleHeaderValue, requireApiToken, writeForgeProxyResponse, writePluginError, writeJsonResponse } from "./api-client.js";
2
- import { FORGE_PLUGIN_ROUTE_EXCLUSIONS, makeApiRouteKey } from "./parity.js";
1
+ import { canBootstrapOperatorSession, callConfiguredForgeApi, expectForgeSuccess, readJsonRequestBody, readSingleHeaderValue, requireApiToken, writeForgeProxyResponse, writePluginError, writeRedirectResponse } from "./api-client.js";
2
+ import { collectSupportedPluginApiRouteKeys, makeApiRouteKey } from "./parity.js";
3
3
  function passthroughSearch(path, url) {
4
4
  return `${path}${url.search}`;
5
5
  }
6
- function encodePathSegment(value) {
7
- return encodeURIComponent(decodeURIComponent(value));
8
- }
9
6
  function methodNotAllowed(response, allowedMethods) {
10
7
  response.setHeader("allow", allowedMethods.join(", "));
11
- writeJsonResponse(response, 405, {
8
+ response.statusCode = 405;
9
+ response.setHeader("content-type", "application/json; charset=utf-8");
10
+ response.end(JSON.stringify({
12
11
  ok: false,
13
12
  error: {
14
13
  code: "forge_plugin_method_not_allowed",
15
14
  message: `Allowed methods: ${allowedMethods.join(", ")}`
16
15
  }
17
- });
16
+ }));
18
17
  }
19
18
  function routeNotFound(response, pathname) {
20
- writeJsonResponse(response, 404, {
19
+ response.statusCode = 404;
20
+ response.setHeader("content-type", "application/json; charset=utf-8");
21
+ response.end(JSON.stringify({
21
22
  ok: false,
22
23
  error: {
23
24
  code: "forge_plugin_route_not_found",
24
25
  message: `No Forge plugin route matches ${pathname}`
25
26
  }
26
- });
27
+ }));
28
+ }
29
+ async function resolveForgeUiUrl(config) {
30
+ try {
31
+ const onboarding = await runReadOnly(config, "/api/v1/agents/onboarding");
32
+ if (typeof onboarding === "object" &&
33
+ onboarding !== null &&
34
+ "onboarding" in onboarding &&
35
+ typeof onboarding.onboarding === "object" &&
36
+ onboarding.onboarding !== null &&
37
+ "webAppUrl" in onboarding.onboarding &&
38
+ typeof onboarding.onboarding.webAppUrl === "string" &&
39
+ onboarding.onboarding.webAppUrl.trim().length > 0) {
40
+ return onboarding.onboarding.webAppUrl;
41
+ }
42
+ }
43
+ catch {
44
+ // Use the configured fallback when onboarding is unavailable.
45
+ }
46
+ return config.webAppUrl;
27
47
  }
28
48
  async function forwardOperation(request, response, config, operation, match, url) {
49
+ if (operation.kind === "ui_redirect") {
50
+ writeRedirectResponse(response, await resolveForgeUiUrl(config));
51
+ return;
52
+ }
29
53
  if (operation.requiresToken) {
30
54
  requireApiToken(config);
31
55
  }
32
56
  const body = operation.requestBody === "json" ? await readJsonRequestBody(request, { emptyObject: true }) : undefined;
33
- const result = await callForgeApi({
34
- baseUrl: config.baseUrl,
35
- apiToken: config.apiToken,
36
- actorLabel: config.actorLabel,
37
- timeoutMs: config.timeoutMs,
57
+ const result = await callConfiguredForgeApi(config, {
38
58
  method: operation.method,
39
59
  path: operation.target(match, url),
40
60
  body,
@@ -82,106 +102,93 @@ export const FORGE_PLUGIN_ROUTE_GROUPS = [
82
102
  upstreamPath: "/api/v1/health",
83
103
  target: (_match, url) => passthroughSearch("/api/v1/health", url)
84
104
  }),
85
- exact("/forge/v1/openapi.json", {
105
+ exact("/forge/v1/operator/overview", {
106
+ method: "GET",
107
+ upstreamPath: "/api/v1/operator/overview",
108
+ target: (_match, url) => passthroughSearch("/api/v1/operator/overview", url)
109
+ }),
110
+ exact("/forge/v1/operator/context", {
111
+ method: "GET",
112
+ upstreamPath: "/api/v1/operator/context",
113
+ target: (_match, url) => passthroughSearch("/api/v1/operator/context", url)
114
+ }),
115
+ exact("/forge/v1/agents/onboarding", {
116
+ method: "GET",
117
+ upstreamPath: "/api/v1/agents/onboarding",
118
+ target: (_match, url) => passthroughSearch("/api/v1/agents/onboarding", url)
119
+ }),
120
+ exact("/forge/v1/psyche/overview", {
86
121
  method: "GET",
87
- upstreamPath: "/api/v1/openapi.json",
88
- target: (_match, url) => passthroughSearch("/api/v1/openapi.json", url)
122
+ upstreamPath: "/api/v1/psyche/overview",
123
+ target: (_match, url) => passthroughSearch("/api/v1/psyche/overview", url)
89
124
  }),
90
- exact("/forge/v1/context", {
125
+ exact("/forge/v1/metrics/xp", {
91
126
  method: "GET",
92
- upstreamPath: "/api/v1/context",
93
- target: (_match, url) => passthroughSearch("/api/v1/context", url)
127
+ upstreamPath: "/api/v1/metrics/xp",
128
+ target: (_match, url) => passthroughSearch("/api/v1/metrics/xp", url)
94
129
  }),
95
- exact("/forge/v1/domains", {
130
+ exact("/forge/v1/reviews/weekly", {
96
131
  method: "GET",
97
- upstreamPath: "/api/v1/domains",
98
- target: (_match, url) => passthroughSearch("/api/v1/domains", url)
132
+ upstreamPath: "/api/v1/reviews/weekly",
133
+ target: (_match, url) => passthroughSearch("/api/v1/reviews/weekly", url)
134
+ }),
135
+ exact("/forge/v1/operator/log-work", {
136
+ method: "POST",
137
+ upstreamPath: "/api/v1/operator/log-work",
138
+ requestBody: "json",
139
+ requiresToken: true,
140
+ target: (_match, url) => passthroughSearch("/api/v1/operator/log-work", url)
141
+ }),
142
+ exact("/forge/v1/insights", {
143
+ method: "POST",
144
+ upstreamPath: "/api/v1/insights",
145
+ requestBody: "json",
146
+ requiresToken: true,
147
+ target: (_match, url) => passthroughSearch("/api/v1/insights", url)
99
148
  }),
100
149
  {
101
- path: "/forge/v1/goals",
150
+ path: "/forge/v1/entities",
102
151
  match: "prefix",
103
152
  operations: [
104
- {
105
- method: "GET",
106
- pattern: /^\/forge\/v1\/goals$/,
107
- upstreamPath: "/api/v1/goals",
108
- target: (_match, url) => passthroughSearch("/api/v1/goals", url)
109
- },
110
153
  {
111
154
  method: "POST",
112
- pattern: /^\/forge\/v1\/goals$/,
113
- upstreamPath: "/api/v1/goals",
155
+ pattern: /^\/forge\/v1\/entities\/search$/,
156
+ upstreamPath: "/api/v1/entities/search",
114
157
  requestBody: "json",
115
158
  requiresToken: true,
116
- target: (_match, url) => passthroughSearch("/api/v1/goals", url)
117
- },
118
- {
119
- method: "GET",
120
- pattern: /^\/forge\/v1\/goals\/([^/]+)$/,
121
- upstreamPath: "/api/v1/goals/:id",
122
- target: (match, url) => passthroughSearch(`/api/v1/goals/${encodePathSegment(match[1] ?? "")}`, url)
159
+ target: (_match, url) => passthroughSearch("/api/v1/entities/search", url)
123
160
  },
124
161
  {
125
- method: "PATCH",
126
- pattern: /^\/forge\/v1\/goals\/([^/]+)$/,
127
- upstreamPath: "/api/v1/goals/:id",
162
+ method: "POST",
163
+ pattern: /^\/forge\/v1\/entities\/create$/,
164
+ upstreamPath: "/api/v1/entities/create",
128
165
  requestBody: "json",
129
166
  requiresToken: true,
130
- target: (match, url) => passthroughSearch(`/api/v1/goals/${encodePathSegment(match[1] ?? "")}`, url)
131
- },
132
- {
133
- method: "DELETE",
134
- pattern: /^\/forge\/v1\/goals\/([^/]+)$/,
135
- upstreamPath: "/api/v1/goals/:id",
136
- requiresToken: true,
137
- target: (match, url) => passthroughSearch(`/api/v1/goals/${encodePathSegment(match[1] ?? "")}`, url)
138
- }
139
- ]
140
- },
141
- {
142
- path: "/forge/v1/projects",
143
- match: "prefix",
144
- operations: [
145
- {
146
- method: "GET",
147
- pattern: /^\/forge\/v1\/projects$/,
148
- upstreamPath: "/api/v1/projects",
149
- target: (_match, url) => passthroughSearch("/api/v1/projects", url)
167
+ target: (_match, url) => passthroughSearch("/api/v1/entities/create", url)
150
168
  },
151
169
  {
152
170
  method: "POST",
153
- pattern: /^\/forge\/v1\/projects$/,
154
- upstreamPath: "/api/v1/projects",
171
+ pattern: /^\/forge\/v1\/entities\/update$/,
172
+ upstreamPath: "/api/v1/entities/update",
155
173
  requestBody: "json",
156
174
  requiresToken: true,
157
- target: (_match, url) => passthroughSearch("/api/v1/projects", url)
158
- },
159
- {
160
- method: "GET",
161
- pattern: /^\/forge\/v1\/projects\/([^/]+)$/,
162
- upstreamPath: "/api/v1/projects/:id",
163
- target: (match, url) => passthroughSearch(`/api/v1/projects/${encodePathSegment(match[1] ?? "")}`, url)
175
+ target: (_match, url) => passthroughSearch("/api/v1/entities/update", url)
164
176
  },
165
177
  {
166
- method: "PATCH",
167
- pattern: /^\/forge\/v1\/projects\/([^/]+)$/,
168
- upstreamPath: "/api/v1/projects/:id",
178
+ method: "POST",
179
+ pattern: /^\/forge\/v1\/entities\/delete$/,
180
+ upstreamPath: "/api/v1/entities/delete",
169
181
  requestBody: "json",
170
182
  requiresToken: true,
171
- target: (match, url) => passthroughSearch(`/api/v1/projects/${encodePathSegment(match[1] ?? "")}`, url)
183
+ target: (_match, url) => passthroughSearch("/api/v1/entities/delete", url)
172
184
  },
173
185
  {
174
- method: "DELETE",
175
- pattern: /^\/forge\/v1\/projects\/([^/]+)$/,
176
- upstreamPath: "/api/v1/projects/:id",
186
+ method: "POST",
187
+ pattern: /^\/forge\/v1\/entities\/restore$/,
188
+ upstreamPath: "/api/v1/entities/restore",
189
+ requestBody: "json",
177
190
  requiresToken: true,
178
- target: (match, url) => passthroughSearch(`/api/v1/projects/${encodePathSegment(match[1] ?? "")}`, url)
179
- },
180
- {
181
- method: "GET",
182
- pattern: /^\/forge\/v1\/projects\/([^/]+)\/board$/,
183
- upstreamPath: "/api/v1/projects/:id/board",
184
- target: (match, url) => passthroughSearch(`/api/v1/projects/${encodePathSegment(match[1] ?? "")}/board`, url)
191
+ target: (_match, url) => passthroughSearch("/api/v1/entities/restore", url)
185
192
  }
186
193
  ]
187
194
  },
@@ -189,62 +196,13 @@ export const FORGE_PLUGIN_ROUTE_GROUPS = [
189
196
  path: "/forge/v1/tasks",
190
197
  match: "prefix",
191
198
  operations: [
192
- {
193
- method: "GET",
194
- pattern: /^\/forge\/v1\/tasks$/,
195
- upstreamPath: "/api/v1/tasks",
196
- target: (_match, url) => passthroughSearch("/api/v1/tasks", url)
197
- },
198
- {
199
- method: "POST",
200
- pattern: /^\/forge\/v1\/tasks$/,
201
- upstreamPath: "/api/v1/tasks",
202
- requestBody: "json",
203
- requiresToken: true,
204
- target: (_match, url) => passthroughSearch("/api/v1/tasks", url)
205
- },
206
- {
207
- method: "GET",
208
- pattern: /^\/forge\/v1\/tasks\/([^/]+)$/,
209
- upstreamPath: "/api/v1/tasks/:id",
210
- target: (match, url) => passthroughSearch(`/api/v1/tasks/${encodePathSegment(match[1] ?? "")}`, url)
211
- },
212
- {
213
- method: "PATCH",
214
- pattern: /^\/forge\/v1\/tasks\/([^/]+)$/,
215
- upstreamPath: "/api/v1/tasks/:id",
216
- requestBody: "json",
217
- requiresToken: true,
218
- target: (match, url) => passthroughSearch(`/api/v1/tasks/${encodePathSegment(match[1] ?? "")}`, url)
219
- },
220
- {
221
- method: "DELETE",
222
- pattern: /^\/forge\/v1\/tasks\/([^/]+)$/,
223
- upstreamPath: "/api/v1/tasks/:id",
224
- requiresToken: true,
225
- target: (match, url) => passthroughSearch(`/api/v1/tasks/${encodePathSegment(match[1] ?? "")}`, url)
226
- },
227
- {
228
- method: "GET",
229
- pattern: /^\/forge\/v1\/tasks\/([^/]+)\/context$/,
230
- upstreamPath: "/api/v1/tasks/:id/context",
231
- target: (match, url) => passthroughSearch(`/api/v1/tasks/${encodePathSegment(match[1] ?? "")}/context`, url)
232
- },
233
199
  {
234
200
  method: "POST",
235
201
  pattern: /^\/forge\/v1\/tasks\/([^/]+)\/runs$/,
236
202
  upstreamPath: "/api/v1/tasks/:id/runs",
237
203
  requestBody: "json",
238
204
  requiresToken: true,
239
- target: (match, url) => passthroughSearch(`/api/v1/tasks/${encodePathSegment(match[1] ?? "")}/runs`, url)
240
- },
241
- {
242
- method: "POST",
243
- pattern: /^\/forge\/v1\/tasks\/([^/]+)\/uncomplete$/,
244
- upstreamPath: "/api/v1/tasks/:id/uncomplete",
245
- requestBody: "json",
246
- requiresToken: true,
247
- target: (match, url) => passthroughSearch(`/api/v1/tasks/${encodePathSegment(match[1] ?? "")}/uncomplete`, url)
205
+ target: (match, url) => passthroughSearch(`/api/v1/tasks/${match[1]}/runs`, url)
248
206
  }
249
207
  ]
250
208
  },
@@ -264,15 +222,7 @@ export const FORGE_PLUGIN_ROUTE_GROUPS = [
264
222
  upstreamPath: "/api/v1/task-runs/:id/heartbeat",
265
223
  requestBody: "json",
266
224
  requiresToken: true,
267
- target: (match, url) => passthroughSearch(`/api/v1/task-runs/${encodePathSegment(match[1] ?? "")}/heartbeat`, url)
268
- },
269
- {
270
- method: "POST",
271
- pattern: /^\/forge\/v1\/task-runs\/([^/]+)\/complete$/,
272
- upstreamPath: "/api/v1/task-runs/:id/complete",
273
- requestBody: "json",
274
- requiresToken: true,
275
- target: (match, url) => passthroughSearch(`/api/v1/task-runs/${encodePathSegment(match[1] ?? "")}/complete`, url)
225
+ target: (match, url) => passthroughSearch(`/api/v1/task-runs/${match[1]}/heartbeat`, url)
276
226
  },
277
227
  {
278
228
  method: "POST",
@@ -280,764 +230,49 @@ export const FORGE_PLUGIN_ROUTE_GROUPS = [
280
230
  upstreamPath: "/api/v1/task-runs/:id/focus",
281
231
  requestBody: "json",
282
232
  requiresToken: true,
283
- target: (match, url) => passthroughSearch(`/api/v1/task-runs/${encodePathSegment(match[1] ?? "")}/focus`, url)
284
- },
285
- {
286
- method: "POST",
287
- pattern: /^\/forge\/v1\/task-runs\/([^/]+)\/release$/,
288
- upstreamPath: "/api/v1/task-runs/:id/release",
289
- requestBody: "json",
290
- requiresToken: true,
291
- target: (match, url) => passthroughSearch(`/api/v1/task-runs/${encodePathSegment(match[1] ?? "")}/release`, url)
292
- }
293
- ]
294
- },
295
- {
296
- path: "/forge/v1/tags",
297
- match: "prefix",
298
- operations: [
299
- {
300
- method: "GET",
301
- pattern: /^\/forge\/v1\/tags$/,
302
- upstreamPath: "/api/v1/tags",
303
- target: (_match, url) => passthroughSearch("/api/v1/tags", url)
304
- },
305
- {
306
- method: "POST",
307
- pattern: /^\/forge\/v1\/tags$/,
308
- upstreamPath: "/api/v1/tags",
309
- requestBody: "json",
310
- requiresToken: true,
311
- target: (_match, url) => passthroughSearch("/api/v1/tags", url)
312
- },
313
- {
314
- method: "GET",
315
- pattern: /^\/forge\/v1\/tags\/([^/]+)$/,
316
- upstreamPath: "/api/v1/tags/:id",
317
- target: (match, url) => passthroughSearch(`/api/v1/tags/${encodePathSegment(match[1] ?? "")}`, url)
318
- },
319
- {
320
- method: "PATCH",
321
- pattern: /^\/forge\/v1\/tags\/([^/]+)$/,
322
- upstreamPath: "/api/v1/tags/:id",
323
- requestBody: "json",
324
- requiresToken: true,
325
- target: (match, url) => passthroughSearch(`/api/v1/tags/${encodePathSegment(match[1] ?? "")}`, url)
326
- },
327
- {
328
- method: "DELETE",
329
- pattern: /^\/forge\/v1\/tags\/([^/]+)$/,
330
- upstreamPath: "/api/v1/tags/:id",
331
- requiresToken: true,
332
- target: (match, url) => passthroughSearch(`/api/v1/tags/${encodePathSegment(match[1] ?? "")}`, url)
333
- }
334
- ]
335
- },
336
- {
337
- path: "/forge/v1/activity",
338
- match: "prefix",
339
- operations: [
340
- {
341
- method: "GET",
342
- pattern: /^\/forge\/v1\/activity$/,
343
- upstreamPath: "/api/v1/activity",
344
- target: (_match, url) => passthroughSearch("/api/v1/activity", url)
345
- },
346
- {
347
- method: "POST",
348
- pattern: /^\/forge\/v1\/activity\/([^/]+)\/remove$/,
349
- upstreamPath: "/api/v1/activity/:id/remove",
350
- requestBody: "json",
351
- requiresToken: true,
352
- target: (match, url) => passthroughSearch(`/api/v1/activity/${encodePathSegment(match[1] ?? "")}/remove`, url)
353
- }
354
- ]
355
- },
356
- {
357
- path: "/forge/v1/metrics",
358
- match: "prefix",
359
- operations: [
360
- {
361
- method: "GET",
362
- pattern: /^\/forge\/v1\/metrics$/,
363
- upstreamPath: "/api/v1/metrics",
364
- target: (_match, url) => passthroughSearch("/api/v1/metrics", url)
365
- },
366
- {
367
- method: "GET",
368
- pattern: /^\/forge\/v1\/metrics\/xp$/,
369
- upstreamPath: "/api/v1/metrics/xp",
370
- target: (_match, url) => passthroughSearch("/api/v1/metrics/xp", url)
371
- }
372
- ]
373
- },
374
- {
375
- path: "/forge/v1/operator",
376
- match: "prefix",
377
- operations: [
378
- {
379
- method: "GET",
380
- pattern: /^\/forge\/v1\/operator\/overview$/,
381
- upstreamPath: "/api/v1/operator/overview",
382
- target: (_match, url) => passthroughSearch("/api/v1/operator/overview", url)
383
- },
384
- {
385
- method: "GET",
386
- pattern: /^\/forge\/v1\/operator\/context$/,
387
- upstreamPath: "/api/v1/operator/context",
388
- target: (_match, url) => passthroughSearch("/api/v1/operator/context", url)
389
- },
390
- {
391
- method: "POST",
392
- pattern: /^\/forge\/v1\/operator\/log-work$/,
393
- upstreamPath: "/api/v1/operator/log-work",
394
- requestBody: "json",
395
- requiresToken: true,
396
- target: (_match, url) => passthroughSearch("/api/v1/operator/log-work", url)
397
- }
398
- ]
399
- },
400
- {
401
- path: "/forge/v1/comments",
402
- match: "prefix",
403
- operations: [
404
- {
405
- method: "GET",
406
- pattern: /^\/forge\/v1\/comments$/,
407
- upstreamPath: "/api/v1/comments",
408
- target: (_match, url) => passthroughSearch("/api/v1/comments", url)
409
- },
410
- {
411
- method: "POST",
412
- pattern: /^\/forge\/v1\/comments$/,
413
- upstreamPath: "/api/v1/comments",
414
- requestBody: "json",
415
- requiresToken: true,
416
- target: (_match, url) => passthroughSearch("/api/v1/comments", url)
417
- },
418
- {
419
- method: "PATCH",
420
- pattern: /^\/forge\/v1\/comments\/([^/]+)$/,
421
- upstreamPath: "/api/v1/comments/:id",
422
- requestBody: "json",
423
- requiresToken: true,
424
- target: (match, url) => passthroughSearch(`/api/v1/comments/${encodePathSegment(match[1] ?? "")}`, url)
425
- },
426
- {
427
- method: "GET",
428
- pattern: /^\/forge\/v1\/comments\/([^/]+)$/,
429
- upstreamPath: "/api/v1/comments/:id",
430
- target: (match, url) => passthroughSearch(`/api/v1/comments/${encodePathSegment(match[1] ?? "")}`, url)
431
- },
432
- {
433
- method: "DELETE",
434
- pattern: /^\/forge\/v1\/comments\/([^/]+)$/,
435
- upstreamPath: "/api/v1/comments/:id",
436
- requiresToken: true,
437
- target: (match, url) => passthroughSearch(`/api/v1/comments/${encodePathSegment(match[1] ?? "")}`, url)
438
- }
439
- ]
440
- },
441
- {
442
- path: "/forge/v1/insights",
443
- match: "prefix",
444
- operations: [
445
- {
446
- method: "GET",
447
- pattern: /^\/forge\/v1\/insights$/,
448
- upstreamPath: "/api/v1/insights",
449
- target: (_match, url) => passthroughSearch("/api/v1/insights", url)
233
+ target: (match, url) => passthroughSearch(`/api/v1/task-runs/${match[1]}/focus`, url)
450
234
  },
451
235
  {
452
236
  method: "POST",
453
- pattern: /^\/forge\/v1\/insights$/,
454
- upstreamPath: "/api/v1/insights",
455
- requestBody: "json",
456
- requiresToken: true,
457
- target: (_match, url) => passthroughSearch("/api/v1/insights", url)
458
- },
459
- {
460
- method: "GET",
461
- pattern: /^\/forge\/v1\/insights\/([^/]+)$/,
462
- upstreamPath: "/api/v1/insights/:id",
463
- target: (match, url) => passthroughSearch(`/api/v1/insights/${encodePathSegment(match[1] ?? "")}`, url)
464
- },
465
- {
466
- method: "PATCH",
467
- pattern: /^\/forge\/v1\/insights\/([^/]+)$/,
468
- upstreamPath: "/api/v1/insights/:id",
237
+ pattern: /^\/forge\/v1\/task-runs\/([^/]+)\/complete$/,
238
+ upstreamPath: "/api/v1/task-runs/:id/complete",
469
239
  requestBody: "json",
470
240
  requiresToken: true,
471
- target: (match, url) => passthroughSearch(`/api/v1/insights/${encodePathSegment(match[1] ?? "")}`, url)
472
- },
473
- {
474
- method: "DELETE",
475
- pattern: /^\/forge\/v1\/insights\/([^/]+)$/,
476
- upstreamPath: "/api/v1/insights/:id",
477
- requiresToken: true,
478
- target: (match, url) => passthroughSearch(`/api/v1/insights/${encodePathSegment(match[1] ?? "")}`, url)
241
+ target: (match, url) => passthroughSearch(`/api/v1/task-runs/${match[1]}/complete`, url)
479
242
  },
480
243
  {
481
244
  method: "POST",
482
- pattern: /^\/forge\/v1\/insights\/([^/]+)\/feedback$/,
483
- upstreamPath: "/api/v1/insights/:id/feedback",
245
+ pattern: /^\/forge\/v1\/task-runs\/([^/]+)\/release$/,
246
+ upstreamPath: "/api/v1/task-runs/:id/release",
484
247
  requestBody: "json",
485
248
  requiresToken: true,
486
- target: (match, url) => passthroughSearch(`/api/v1/insights/${encodePathSegment(match[1] ?? "")}/feedback`, url)
249
+ target: (match, url) => passthroughSearch(`/api/v1/task-runs/${match[1]}/release`, url)
487
250
  }
488
251
  ]
489
252
  },
490
- {
491
- path: "/forge/v1/psyche",
492
- match: "prefix",
493
- operations: [
494
- {
495
- method: "GET",
496
- pattern: /^\/forge\/v1\/psyche\/overview$/,
497
- upstreamPath: "/api/v1/psyche/overview",
498
- target: (_match, url) => passthroughSearch("/api/v1/psyche/overview", url)
499
- },
500
- {
501
- method: "GET",
502
- pattern: /^\/forge\/v1\/psyche\/values$/,
503
- upstreamPath: "/api/v1/psyche/values",
504
- target: (_match, url) => passthroughSearch("/api/v1/psyche/values", url)
505
- },
506
- {
507
- method: "POST",
508
- pattern: /^\/forge\/v1\/psyche\/values$/,
509
- upstreamPath: "/api/v1/psyche/values",
510
- requestBody: "json",
511
- requiresToken: true,
512
- target: (_match, url) => passthroughSearch("/api/v1/psyche/values", url)
513
- },
514
- {
515
- method: "GET",
516
- pattern: /^\/forge\/v1\/psyche\/values\/([^/]+)$/,
517
- upstreamPath: "/api/v1/psyche/values/:id",
518
- target: (match, url) => passthroughSearch(`/api/v1/psyche/values/${encodePathSegment(match[1] ?? "")}`, url)
519
- },
520
- {
521
- method: "PATCH",
522
- pattern: /^\/forge\/v1\/psyche\/values\/([^/]+)$/,
523
- upstreamPath: "/api/v1/psyche/values/:id",
524
- requestBody: "json",
525
- requiresToken: true,
526
- target: (match, url) => passthroughSearch(`/api/v1/psyche/values/${encodePathSegment(match[1] ?? "")}`, url)
527
- },
528
- {
529
- method: "DELETE",
530
- pattern: /^\/forge\/v1\/psyche\/values\/([^/]+)$/,
531
- upstreamPath: "/api/v1/psyche/values/:id",
532
- requiresToken: true,
533
- target: (match, url) => passthroughSearch(`/api/v1/psyche/values/${encodePathSegment(match[1] ?? "")}`, url)
534
- },
535
- {
536
- method: "GET",
537
- pattern: /^\/forge\/v1\/psyche\/patterns$/,
538
- upstreamPath: "/api/v1/psyche/patterns",
539
- target: (_match, url) => passthroughSearch("/api/v1/psyche/patterns", url)
540
- },
541
- {
542
- method: "POST",
543
- pattern: /^\/forge\/v1\/psyche\/patterns$/,
544
- upstreamPath: "/api/v1/psyche/patterns",
545
- requestBody: "json",
546
- requiresToken: true,
547
- target: (_match, url) => passthroughSearch("/api/v1/psyche/patterns", url)
548
- },
549
- {
550
- method: "GET",
551
- pattern: /^\/forge\/v1\/psyche\/patterns\/([^/]+)$/,
552
- upstreamPath: "/api/v1/psyche/patterns/:id",
553
- target: (match, url) => passthroughSearch(`/api/v1/psyche/patterns/${encodePathSegment(match[1] ?? "")}`, url)
554
- },
555
- {
556
- method: "PATCH",
557
- pattern: /^\/forge\/v1\/psyche\/patterns\/([^/]+)$/,
558
- upstreamPath: "/api/v1/psyche/patterns/:id",
559
- requestBody: "json",
560
- requiresToken: true,
561
- target: (match, url) => passthroughSearch(`/api/v1/psyche/patterns/${encodePathSegment(match[1] ?? "")}`, url)
562
- },
563
- {
564
- method: "DELETE",
565
- pattern: /^\/forge\/v1\/psyche\/patterns\/([^/]+)$/,
566
- upstreamPath: "/api/v1/psyche/patterns/:id",
567
- requiresToken: true,
568
- target: (match, url) => passthroughSearch(`/api/v1/psyche/patterns/${encodePathSegment(match[1] ?? "")}`, url)
569
- },
570
- {
571
- method: "GET",
572
- pattern: /^\/forge\/v1\/psyche\/behaviors$/,
573
- upstreamPath: "/api/v1/psyche/behaviors",
574
- target: (_match, url) => passthroughSearch("/api/v1/psyche/behaviors", url)
575
- },
576
- {
577
- method: "POST",
578
- pattern: /^\/forge\/v1\/psyche\/behaviors$/,
579
- upstreamPath: "/api/v1/psyche/behaviors",
580
- requestBody: "json",
581
- requiresToken: true,
582
- target: (_match, url) => passthroughSearch("/api/v1/psyche/behaviors", url)
583
- },
584
- {
585
- method: "GET",
586
- pattern: /^\/forge\/v1\/psyche\/behaviors\/([^/]+)$/,
587
- upstreamPath: "/api/v1/psyche/behaviors/:id",
588
- target: (match, url) => passthroughSearch(`/api/v1/psyche/behaviors/${encodePathSegment(match[1] ?? "")}`, url)
589
- },
590
- {
591
- method: "PATCH",
592
- pattern: /^\/forge\/v1\/psyche\/behaviors\/([^/]+)$/,
593
- upstreamPath: "/api/v1/psyche/behaviors/:id",
594
- requestBody: "json",
595
- requiresToken: true,
596
- target: (match, url) => passthroughSearch(`/api/v1/psyche/behaviors/${encodePathSegment(match[1] ?? "")}`, url)
597
- },
598
- {
599
- method: "DELETE",
600
- pattern: /^\/forge\/v1\/psyche\/behaviors\/([^/]+)$/,
601
- upstreamPath: "/api/v1/psyche/behaviors/:id",
602
- requiresToken: true,
603
- target: (match, url) => passthroughSearch(`/api/v1/psyche/behaviors/${encodePathSegment(match[1] ?? "")}`, url)
604
- },
605
- {
606
- method: "GET",
607
- pattern: /^\/forge\/v1\/psyche\/schema-catalog$/,
608
- upstreamPath: "/api/v1/psyche/schema-catalog",
609
- target: (_match, url) => passthroughSearch("/api/v1/psyche/schema-catalog", url)
610
- },
611
- {
612
- method: "GET",
613
- pattern: /^\/forge\/v1\/psyche\/beliefs$/,
614
- upstreamPath: "/api/v1/psyche/beliefs",
615
- target: (_match, url) => passthroughSearch("/api/v1/psyche/beliefs", url)
616
- },
617
- {
618
- method: "POST",
619
- pattern: /^\/forge\/v1\/psyche\/beliefs$/,
620
- upstreamPath: "/api/v1/psyche/beliefs",
621
- requestBody: "json",
622
- requiresToken: true,
623
- target: (_match, url) => passthroughSearch("/api/v1/psyche/beliefs", url)
624
- },
625
- {
626
- method: "GET",
627
- pattern: /^\/forge\/v1\/psyche\/beliefs\/([^/]+)$/,
628
- upstreamPath: "/api/v1/psyche/beliefs/:id",
629
- target: (match, url) => passthroughSearch(`/api/v1/psyche/beliefs/${encodePathSegment(match[1] ?? "")}`, url)
630
- },
631
- {
632
- method: "PATCH",
633
- pattern: /^\/forge\/v1\/psyche\/beliefs\/([^/]+)$/,
634
- upstreamPath: "/api/v1/psyche/beliefs/:id",
635
- requestBody: "json",
636
- requiresToken: true,
637
- target: (match, url) => passthroughSearch(`/api/v1/psyche/beliefs/${encodePathSegment(match[1] ?? "")}`, url)
638
- },
639
- {
640
- method: "DELETE",
641
- pattern: /^\/forge\/v1\/psyche\/beliefs\/([^/]+)$/,
642
- upstreamPath: "/api/v1/psyche/beliefs/:id",
643
- requiresToken: true,
644
- target: (match, url) => passthroughSearch(`/api/v1/psyche/beliefs/${encodePathSegment(match[1] ?? "")}`, url)
645
- },
646
- {
647
- method: "GET",
648
- pattern: /^\/forge\/v1\/psyche\/modes$/,
649
- upstreamPath: "/api/v1/psyche/modes",
650
- target: (_match, url) => passthroughSearch("/api/v1/psyche/modes", url)
651
- },
652
- {
653
- method: "POST",
654
- pattern: /^\/forge\/v1\/psyche\/modes$/,
655
- upstreamPath: "/api/v1/psyche/modes",
656
- requestBody: "json",
657
- requiresToken: true,
658
- target: (_match, url) => passthroughSearch("/api/v1/psyche/modes", url)
659
- },
660
- {
661
- method: "GET",
662
- pattern: /^\/forge\/v1\/psyche\/modes\/([^/]+)$/,
663
- upstreamPath: "/api/v1/psyche/modes/:id",
664
- target: (match, url) => passthroughSearch(`/api/v1/psyche/modes/${encodePathSegment(match[1] ?? "")}`, url)
665
- },
666
- {
667
- method: "PATCH",
668
- pattern: /^\/forge\/v1\/psyche\/modes\/([^/]+)$/,
669
- upstreamPath: "/api/v1/psyche/modes/:id",
670
- requestBody: "json",
671
- requiresToken: true,
672
- target: (match, url) => passthroughSearch(`/api/v1/psyche/modes/${encodePathSegment(match[1] ?? "")}`, url)
673
- },
674
- {
675
- method: "DELETE",
676
- pattern: /^\/forge\/v1\/psyche\/modes\/([^/]+)$/,
677
- upstreamPath: "/api/v1/psyche/modes/:id",
678
- requiresToken: true,
679
- target: (match, url) => passthroughSearch(`/api/v1/psyche/modes/${encodePathSegment(match[1] ?? "")}`, url)
680
- },
681
- {
682
- method: "GET",
683
- pattern: /^\/forge\/v1\/psyche\/mode-guides$/,
684
- upstreamPath: "/api/v1/psyche/mode-guides",
685
- target: (_match, url) => passthroughSearch("/api/v1/psyche/mode-guides", url)
686
- },
687
- {
688
- method: "POST",
689
- pattern: /^\/forge\/v1\/psyche\/mode-guides$/,
690
- upstreamPath: "/api/v1/psyche/mode-guides",
691
- requestBody: "json",
692
- requiresToken: true,
693
- target: (_match, url) => passthroughSearch("/api/v1/psyche/mode-guides", url)
694
- },
695
- {
696
- method: "GET",
697
- pattern: /^\/forge\/v1\/psyche\/mode-guides\/([^/]+)$/,
698
- upstreamPath: "/api/v1/psyche/mode-guides/:id",
699
- target: (match, url) => passthroughSearch(`/api/v1/psyche/mode-guides/${encodePathSegment(match[1] ?? "")}`, url)
700
- },
701
- {
702
- method: "PATCH",
703
- pattern: /^\/forge\/v1\/psyche\/mode-guides\/([^/]+)$/,
704
- upstreamPath: "/api/v1/psyche/mode-guides/:id",
705
- requestBody: "json",
706
- requiresToken: true,
707
- target: (match, url) => passthroughSearch(`/api/v1/psyche/mode-guides/${encodePathSegment(match[1] ?? "")}`, url)
708
- },
709
- {
710
- method: "DELETE",
711
- pattern: /^\/forge\/v1\/psyche\/mode-guides\/([^/]+)$/,
712
- upstreamPath: "/api/v1/psyche/mode-guides/:id",
713
- requiresToken: true,
714
- target: (match, url) => passthroughSearch(`/api/v1/psyche/mode-guides/${encodePathSegment(match[1] ?? "")}`, url)
715
- },
716
- {
717
- method: "GET",
718
- pattern: /^\/forge\/v1\/psyche\/event-types$/,
719
- upstreamPath: "/api/v1/psyche/event-types",
720
- target: (_match, url) => passthroughSearch("/api/v1/psyche/event-types", url)
721
- },
722
- {
723
- method: "POST",
724
- pattern: /^\/forge\/v1\/psyche\/event-types$/,
725
- upstreamPath: "/api/v1/psyche/event-types",
726
- requestBody: "json",
727
- requiresToken: true,
728
- target: (_match, url) => passthroughSearch("/api/v1/psyche/event-types", url)
729
- },
730
- {
731
- method: "GET",
732
- pattern: /^\/forge\/v1\/psyche\/event-types\/([^/]+)$/,
733
- upstreamPath: "/api/v1/psyche/event-types/:id",
734
- target: (match, url) => passthroughSearch(`/api/v1/psyche/event-types/${encodePathSegment(match[1] ?? "")}`, url)
735
- },
736
- {
737
- method: "PATCH",
738
- pattern: /^\/forge\/v1\/psyche\/event-types\/([^/]+)$/,
739
- upstreamPath: "/api/v1/psyche/event-types/:id",
740
- requestBody: "json",
741
- requiresToken: true,
742
- target: (match, url) => passthroughSearch(`/api/v1/psyche/event-types/${encodePathSegment(match[1] ?? "")}`, url)
743
- },
744
- {
745
- method: "DELETE",
746
- pattern: /^\/forge\/v1\/psyche\/event-types\/([^/]+)$/,
747
- upstreamPath: "/api/v1/psyche/event-types/:id",
748
- requiresToken: true,
749
- target: (match, url) => passthroughSearch(`/api/v1/psyche/event-types/${encodePathSegment(match[1] ?? "")}`, url)
750
- },
751
- {
752
- method: "GET",
753
- pattern: /^\/forge\/v1\/psyche\/emotions$/,
754
- upstreamPath: "/api/v1/psyche/emotions",
755
- target: (_match, url) => passthroughSearch("/api/v1/psyche/emotions", url)
756
- },
757
- {
758
- method: "POST",
759
- pattern: /^\/forge\/v1\/psyche\/emotions$/,
760
- upstreamPath: "/api/v1/psyche/emotions",
761
- requestBody: "json",
762
- requiresToken: true,
763
- target: (_match, url) => passthroughSearch("/api/v1/psyche/emotions", url)
764
- },
765
- {
766
- method: "GET",
767
- pattern: /^\/forge\/v1\/psyche\/emotions\/([^/]+)$/,
768
- upstreamPath: "/api/v1/psyche/emotions/:id",
769
- target: (match, url) => passthroughSearch(`/api/v1/psyche/emotions/${encodePathSegment(match[1] ?? "")}`, url)
770
- },
771
- {
772
- method: "PATCH",
773
- pattern: /^\/forge\/v1\/psyche\/emotions\/([^/]+)$/,
774
- upstreamPath: "/api/v1/psyche/emotions/:id",
775
- requestBody: "json",
776
- requiresToken: true,
777
- target: (match, url) => passthroughSearch(`/api/v1/psyche/emotions/${encodePathSegment(match[1] ?? "")}`, url)
778
- },
779
- {
780
- method: "DELETE",
781
- pattern: /^\/forge\/v1\/psyche\/emotions\/([^/]+)$/,
782
- upstreamPath: "/api/v1/psyche/emotions/:id",
783
- requiresToken: true,
784
- target: (match, url) => passthroughSearch(`/api/v1/psyche/emotions/${encodePathSegment(match[1] ?? "")}`, url)
785
- },
786
- {
787
- method: "GET",
788
- pattern: /^\/forge\/v1\/psyche\/reports$/,
789
- upstreamPath: "/api/v1/psyche/reports",
790
- target: (_match, url) => passthroughSearch("/api/v1/psyche/reports", url)
791
- },
792
- {
793
- method: "POST",
794
- pattern: /^\/forge\/v1\/psyche\/reports$/,
795
- upstreamPath: "/api/v1/psyche/reports",
796
- requestBody: "json",
797
- requiresToken: true,
798
- target: (_match, url) => passthroughSearch("/api/v1/psyche/reports", url)
799
- },
800
- {
801
- method: "GET",
802
- pattern: /^\/forge\/v1\/psyche\/reports\/([^/]+)$/,
803
- upstreamPath: "/api/v1/psyche/reports/:id",
804
- target: (match, url) => passthroughSearch(`/api/v1/psyche/reports/${encodePathSegment(match[1] ?? "")}`, url)
805
- },
806
- {
807
- method: "PATCH",
808
- pattern: /^\/forge\/v1\/psyche\/reports\/([^/]+)$/,
809
- upstreamPath: "/api/v1/psyche/reports/:id",
810
- requestBody: "json",
811
- requiresToken: true,
812
- target: (match, url) => passthroughSearch(`/api/v1/psyche/reports/${encodePathSegment(match[1] ?? "")}`, url)
813
- },
814
- {
815
- method: "DELETE",
816
- pattern: /^\/forge\/v1\/psyche\/reports\/([^/]+)$/,
817
- upstreamPath: "/api/v1/psyche/reports/:id",
818
- requiresToken: true,
819
- target: (match, url) => passthroughSearch(`/api/v1/psyche/reports/${encodePathSegment(match[1] ?? "")}`, url)
820
- }
821
- ]
822
- },
823
- {
824
- path: "/forge/v1/approval-requests",
825
- match: "prefix",
826
- operations: [
827
- {
828
- method: "GET",
829
- pattern: /^\/forge\/v1\/approval-requests$/,
830
- upstreamPath: "/api/v1/approval-requests",
831
- target: (_match, url) => passthroughSearch("/api/v1/approval-requests", url)
832
- },
833
- {
834
- method: "POST",
835
- pattern: /^\/forge\/v1\/approval-requests\/([^/]+)\/approve$/,
836
- upstreamPath: "/api/v1/approval-requests/:id/approve",
837
- requestBody: "json",
838
- requiresToken: true,
839
- target: (match, url) => passthroughSearch(`/api/v1/approval-requests/${encodePathSegment(match[1] ?? "")}/approve`, url)
840
- },
841
- {
842
- method: "POST",
843
- pattern: /^\/forge\/v1\/approval-requests\/([^/]+)\/reject$/,
844
- upstreamPath: "/api/v1/approval-requests/:id/reject",
845
- requestBody: "json",
846
- requiresToken: true,
847
- target: (match, url) => passthroughSearch(`/api/v1/approval-requests/${encodePathSegment(match[1] ?? "")}/reject`, url)
848
- }
849
- ]
850
- },
851
- {
852
- path: "/forge/v1/agents",
853
- match: "prefix",
854
- operations: [
855
- {
856
- method: "GET",
857
- pattern: /^\/forge\/v1\/agents\/onboarding$/,
858
- upstreamPath: "/api/v1/agents/onboarding",
859
- target: (_match, url) => passthroughSearch("/api/v1/agents/onboarding", url)
860
- },
861
- {
862
- method: "GET",
863
- pattern: /^\/forge\/v1\/agents$/,
864
- upstreamPath: "/api/v1/agents",
865
- target: (_match, url) => passthroughSearch("/api/v1/agents", url)
866
- },
867
- {
868
- method: "GET",
869
- pattern: /^\/forge\/v1\/agents\/([^/]+)\/actions$/,
870
- upstreamPath: "/api/v1/agents/:id/actions",
871
- target: (match, url) => passthroughSearch(`/api/v1/agents/${encodePathSegment(match[1] ?? "")}/actions`, url)
872
- }
873
- ]
874
- },
875
- exact("/forge/v1/agent-actions", {
876
- method: "POST",
877
- upstreamPath: "/api/v1/agent-actions",
878
- requestBody: "json",
879
- requiresToken: true,
880
- target: (_match, url) => passthroughSearch("/api/v1/agent-actions", url)
881
- }),
882
- {
883
- path: "/forge/v1/rewards",
884
- match: "prefix",
885
- operations: [
886
- {
887
- method: "GET",
888
- pattern: /^\/forge\/v1\/rewards\/rules$/,
889
- upstreamPath: "/api/v1/rewards/rules",
890
- target: (_match, url) => passthroughSearch("/api/v1/rewards/rules", url)
891
- },
892
- {
893
- method: "GET",
894
- pattern: /^\/forge\/v1\/rewards\/rules\/([^/]+)$/,
895
- upstreamPath: "/api/v1/rewards/rules/:id",
896
- target: (match, url) => passthroughSearch(`/api/v1/rewards/rules/${encodePathSegment(match[1] ?? "")}`, url)
897
- },
898
- {
899
- method: "PATCH",
900
- pattern: /^\/forge\/v1\/rewards\/rules\/([^/]+)$/,
901
- upstreamPath: "/api/v1/rewards/rules/:id",
902
- requestBody: "json",
903
- requiresToken: true,
904
- target: (match, url) => passthroughSearch(`/api/v1/rewards/rules/${encodePathSegment(match[1] ?? "")}`, url)
905
- },
906
- {
907
- method: "GET",
908
- pattern: /^\/forge\/v1\/rewards\/ledger$/,
909
- upstreamPath: "/api/v1/rewards/ledger",
910
- target: (_match, url) => passthroughSearch("/api/v1/rewards/ledger", url)
911
- },
912
- {
913
- method: "POST",
914
- pattern: /^\/forge\/v1\/rewards\/bonus$/,
915
- upstreamPath: "/api/v1/rewards/bonus",
916
- requestBody: "json",
917
- requiresToken: true,
918
- target: (_match, url) => passthroughSearch("/api/v1/rewards/bonus", url)
919
- }
920
- ]
921
- },
922
- {
923
- path: "/forge/v1/events",
924
- match: "prefix",
925
- operations: [
926
- {
927
- method: "GET",
928
- pattern: /^\/forge\/v1\/events$/,
929
- upstreamPath: "/api/v1/events",
930
- target: (_match, url) => passthroughSearch("/api/v1/events", url)
931
- },
932
- {
933
- method: "GET",
934
- pattern: /^\/forge\/v1\/events\/meta$/,
935
- upstreamPath: "/api/v1/events/meta",
936
- target: (_match, url) => passthroughSearch("/api/v1/events/meta", url)
937
- }
938
- ]
939
- },
940
- {
941
- path: "/forge/v1/reviews",
942
- match: "prefix",
943
- operations: [
944
- {
945
- method: "GET",
946
- pattern: /^\/forge\/v1\/reviews\/weekly$/,
947
- upstreamPath: "/api/v1/reviews/weekly",
948
- target: (_match, url) => passthroughSearch("/api/v1/reviews/weekly", url)
949
- }
950
- ]
951
- },
952
- {
953
- path: "/forge/v1/settings",
954
- match: "exact",
955
- operations: [
956
- {
957
- method: "GET",
958
- pattern: /^\/forge\/v1\/settings$/,
959
- upstreamPath: "/api/v1/settings",
960
- target: (_match, url) => passthroughSearch("/api/v1/settings", url)
961
- },
962
- {
963
- method: "PATCH",
964
- pattern: /^\/forge\/v1\/settings$/,
965
- upstreamPath: "/api/v1/settings",
966
- requestBody: "json",
967
- requiresToken: true,
968
- target: (_match, url) => passthroughSearch("/api/v1/settings", url)
969
- }
970
- ]
971
- },
972
- exact("/forge/v1/settings/bin", {
973
- method: "GET",
974
- upstreamPath: "/api/v1/settings/bin",
975
- target: (_match, url) => passthroughSearch("/api/v1/settings/bin", url)
976
- }),
977
- {
978
- path: "/forge/v1/entities",
979
- match: "prefix",
980
- operations: [
981
- {
982
- method: "POST",
983
- pattern: /^\/forge\/v1\/entities\/create$/,
984
- upstreamPath: "/api/v1/entities/create",
985
- requestBody: "json",
986
- requiresToken: true,
987
- target: (_match, url) => passthroughSearch("/api/v1/entities/create", url)
988
- },
989
- {
990
- method: "POST",
991
- pattern: /^\/forge\/v1\/entities\/update$/,
992
- upstreamPath: "/api/v1/entities/update",
993
- requestBody: "json",
994
- requiresToken: true,
995
- target: (_match, url) => passthroughSearch("/api/v1/entities/update", url)
996
- },
997
- {
998
- method: "POST",
999
- pattern: /^\/forge\/v1\/entities\/delete$/,
1000
- upstreamPath: "/api/v1/entities/delete",
1001
- requestBody: "json",
1002
- requiresToken: true,
1003
- target: (_match, url) => passthroughSearch("/api/v1/entities/delete", url)
1004
- },
1005
- {
1006
- method: "POST",
1007
- pattern: /^\/forge\/v1\/entities\/restore$/,
1008
- upstreamPath: "/api/v1/entities/restore",
1009
- requestBody: "json",
1010
- requiresToken: true,
1011
- target: (_match, url) => passthroughSearch("/api/v1/entities/restore", url)
1012
- },
1013
- {
1014
- method: "POST",
1015
- pattern: /^\/forge\/v1\/entities\/search$/,
1016
- upstreamPath: "/api/v1/entities/search",
1017
- requestBody: "json",
1018
- requiresToken: true,
1019
- target: (_match, url) => passthroughSearch("/api/v1/entities/search", url)
1020
- }
1021
- ]
1022
- }
253
+ exact("/forge/v1/ui", {
254
+ kind: "ui_redirect",
255
+ method: "GET"
256
+ })
1023
257
  ];
1024
258
  export function collectMirroredApiRouteKeys() {
1025
- return new Set(FORGE_PLUGIN_ROUTE_GROUPS.flatMap((group) => group.operations.map((operation) => makeApiRouteKey(operation.method, operation.upstreamPath))));
259
+ return new Set(FORGE_PLUGIN_ROUTE_GROUPS.flatMap((group) => group.operations.flatMap((operation) => ("upstreamPath" in operation ? [makeApiRouteKey(operation.method, operation.upstreamPath)] : []))));
1026
260
  }
1027
261
  export function buildRouteParityReport(pathMap) {
1028
262
  const mirrored = collectMirroredApiRouteKeys();
1029
- const excluded = new Set(FORGE_PLUGIN_ROUTE_EXCLUSIONS.map((route) => makeApiRouteKey(route.method, route.path)));
1030
- const allRelevant = Object.entries(pathMap)
263
+ const supported = collectSupportedPluginApiRouteKeys();
264
+ const openApiRoutes = new Set(Object.entries(pathMap)
1031
265
  .flatMap(([path, methods]) => Object.keys(methods).map((method) => makeApiRouteKey(method, path)))
1032
- .filter((key) => key.startsWith("GET /api/v1") ||
1033
- key.startsWith("POST /api/v1") ||
1034
- key.startsWith("PATCH /api/v1") ||
1035
- key.startsWith("DELETE /api/v1"));
1036
- const uncovered = allRelevant.filter((key) => !mirrored.has(key) && !excluded.has(key));
266
+ .filter((key) => key.startsWith("GET /api/v1") || key.startsWith("POST /api/v1") || key.startsWith("PATCH /api/v1") || key.startsWith("DELETE /api/v1")));
267
+ const missingFromPlugin = [...supported].filter((key) => !mirrored.has(key)).sort();
268
+ const missingFromOpenApi = [...supported].filter((key) => !openApiRoutes.has(key)).sort();
269
+ const unexpectedMirrors = [...mirrored].filter((key) => !supported.has(key)).sort();
1037
270
  return {
271
+ supported: [...supported].sort(),
1038
272
  mirrored: [...mirrored].sort(),
1039
- excluded: [...excluded].sort(),
1040
- uncovered: uncovered.sort()
273
+ missingFromPlugin,
274
+ missingFromOpenApi,
275
+ unexpectedMirrors
1041
276
  };
1042
277
  }
1043
278
  export function registerForgePluginRoutes(api, config) {
@@ -1052,11 +287,7 @@ export function registerForgePluginRoutes(api, config) {
1052
287
  }
1053
288
  function createCliAction(config, path) {
1054
289
  return async () => {
1055
- const result = await callForgeApi({
1056
- baseUrl: config.baseUrl,
1057
- apiToken: config.apiToken,
1058
- actorLabel: config.actorLabel,
1059
- timeoutMs: config.timeoutMs,
290
+ const result = await callConfiguredForgeApi(config, {
1060
291
  method: "GET",
1061
292
  path
1062
293
  });
@@ -1064,12 +295,26 @@ function createCliAction(config, path) {
1064
295
  console.log(JSON.stringify(data, null, 2));
1065
296
  };
1066
297
  }
298
+ async function runReadOnly(config, path) {
299
+ return expectForgeSuccess(await callConfiguredForgeApi(config, {
300
+ method: "GET",
301
+ path
302
+ }));
303
+ }
304
+ async function runRouteCheck(config) {
305
+ const openapi = await runReadOnly(config, "/api/v1/openapi.json");
306
+ const pathMap = typeof openapi === "object" && openapi !== null && "paths" in openapi && typeof openapi.paths === "object" && openapi.paths !== null
307
+ ? openapi.paths
308
+ : {};
309
+ return buildRouteParityReport(pathMap);
310
+ }
1067
311
  async function runDoctor(config) {
1068
- const [health, overview, onboarding, routeParity] = await Promise.all([
312
+ const [health, overview, onboarding, routeParity, uiUrl] = await Promise.all([
1069
313
  runReadOnly(config, "/api/v1/health"),
1070
314
  runReadOnly(config, "/api/v1/operator/overview"),
1071
315
  runReadOnly(config, "/api/v1/agents/onboarding"),
1072
- runRouteCheck(config)
316
+ runRouteCheck(config),
317
+ resolveForgeUiUrl(config)
1073
318
  ]);
1074
319
  const overviewBody = typeof overview === "object" && overview !== null && "overview" in overview && typeof overview.overview === "object" && overview.overview !== null
1075
320
  ? overview.overview
@@ -1078,30 +323,38 @@ async function runDoctor(config) {
1078
323
  ? overviewBody.capabilities
1079
324
  : null;
1080
325
  const overviewWarnings = Array.isArray(overviewBody?.warnings) ? overviewBody.warnings.filter((entry) => typeof entry === "string") : [];
1081
- const uncoveredRoutes = routeParity.uncovered;
1082
326
  const warnings = [];
1083
327
  const canBootstrap = canBootstrapOperatorSession(config.baseUrl);
1084
328
  if (config.apiToken.trim().length === 0 && canBootstrap) {
1085
- warnings.push("Forge apiToken is blank, but this base URL can bootstrap a local or Tailscale operator session for protected reads and writes.");
329
+ warnings.push("Forge apiToken is blank, but this target can bootstrap a local or Tailscale operator session for protected reads and writes.");
1086
330
  }
1087
331
  else if (config.apiToken.trim().length === 0) {
1088
- warnings.push("Forge apiToken is missing, and this base URL cannot use local or Tailscale operator-session bootstrap. Protected writes will fail.");
332
+ warnings.push("Forge apiToken is missing, and this target cannot use local or Tailscale operator-session bootstrap. Protected writes will fail.");
1089
333
  }
1090
334
  if (overviewWarnings.length > 0) {
1091
335
  warnings.push(...overviewWarnings);
1092
336
  }
1093
337
  if (capabilities && capabilities.canReadPsyche === false) {
1094
- warnings.push("The configured token cannot read Psyche state. Sensitive reflection routes and summaries will stay partial.");
338
+ warnings.push("The configured token cannot read Psyche state. Sensitive reflection summaries will stay partial.");
1095
339
  }
1096
- if (capabilities && capabilities.canManageRewards === false) {
1097
- warnings.push("The configured token cannot manage rewards. Reward-rule tuning and manual bonus XP are unavailable.");
340
+ if (routeParity.missingFromPlugin.length > 0) {
341
+ warnings.push(`Plugin route coverage is missing ${routeParity.missingFromPlugin.length} curated route${routeParity.missingFromPlugin.length === 1 ? "" : "s"}. Run forge route-check.`);
1098
342
  }
1099
- if (uncoveredRoutes.length > 0) {
1100
- warnings.push(`Plugin parity is incomplete for ${uncoveredRoutes.length} stable API route${uncoveredRoutes.length === 1 ? "" : "s"}. Run forge route-check.`);
343
+ if (routeParity.missingFromOpenApi.length > 0) {
344
+ warnings.push(`Forge OpenAPI is missing ${routeParity.missingFromOpenApi.length} curated route${routeParity.missingFromOpenApi.length === 1 ? "" : "s"} expected by the plugin.`);
345
+ }
346
+ if (routeParity.unexpectedMirrors.length > 0) {
347
+ warnings.push(`Plugin still mirrors ${routeParity.unexpectedMirrors.length} unexpected route${routeParity.unexpectedMirrors.length === 1 ? "" : "s"} outside the curated contract.`);
1101
348
  }
1102
349
  return {
1103
- ok: (config.apiToken.trim().length > 0 || canBootstrap) && uncoveredRoutes.length === 0,
350
+ ok: (config.apiToken.trim().length > 0 || canBootstrap) &&
351
+ routeParity.missingFromPlugin.length === 0 &&
352
+ routeParity.missingFromOpenApi.length === 0 &&
353
+ routeParity.unexpectedMirrors.length === 0,
354
+ origin: config.origin,
355
+ port: config.port,
1104
356
  baseUrl: config.baseUrl,
357
+ webAppUrl: uiUrl,
1105
358
  actorLabel: config.actorLabel,
1106
359
  apiTokenConfigured: config.apiToken.trim().length > 0,
1107
360
  operatorSessionBootstrapAvailable: canBootstrap,
@@ -1112,40 +365,19 @@ async function runDoctor(config) {
1112
365
  routeParity
1113
366
  };
1114
367
  }
1115
- async function runReadOnly(config, path) {
1116
- return expectForgeSuccess(await callForgeApi({
1117
- baseUrl: config.baseUrl,
1118
- apiToken: config.apiToken,
1119
- actorLabel: config.actorLabel,
1120
- timeoutMs: config.timeoutMs,
1121
- method: "GET",
1122
- path
1123
- }));
1124
- }
1125
- async function runRouteCheck(config) {
1126
- const openapi = await runReadOnly(config, "/api/v1/openapi.json");
1127
- const pathMap = typeof openapi === "object" && openapi !== null && "paths" in openapi && typeof openapi.paths === "object" && openapi.paths !== null
1128
- ? openapi.paths
1129
- : {};
1130
- return buildRouteParityReport(pathMap);
1131
- }
1132
368
  export function registerForgePluginCli(api, config) {
1133
369
  api.registerCli?.(({ program }) => {
1134
370
  const command = program.command("forge").description("Inspect and operate Forge through the OpenClaw plugin");
1135
371
  command.command("health").description("Check Forge health").action(createCliAction(config, "/api/v1/health"));
1136
- command.command("context").description("Fetch the Forge operating context").action(createCliAction(config, "/api/v1/context"));
1137
372
  command.command("overview").description("Fetch the one-shot Forge operator overview").action(createCliAction(config, "/api/v1/operator/overview"));
1138
- command.command("openapi").description("Print the live Forge OpenAPI document").action(createCliAction(config, "/api/v1/openapi.json"));
1139
- command.command("goals").description("List Forge life goals").action(createCliAction(config, "/api/v1/goals"));
1140
- command.command("projects").description("List Forge projects").action(createCliAction(config, "/api/v1/projects"));
1141
- command.command("metrics-xp").description("Inspect Forge XP metrics").action(createCliAction(config, "/api/v1/metrics/xp"));
1142
- command.command("doctor").description("Run plugin connectivity and onboarding diagnostics").action(async () => {
373
+ command.command("onboarding").description("Print the Forge agent onboarding contract").action(createCliAction(config, "/api/v1/agents/onboarding"));
374
+ command.command("ui").description("Print the Forge UI entrypoint").action(async () => {
375
+ console.log(JSON.stringify({ webAppUrl: await resolveForgeUiUrl(config), pluginUiRoute: "/forge/v1/ui" }, null, 2));
376
+ });
377
+ command.command("doctor").description("Run plugin connectivity and curated route diagnostics").action(async () => {
1143
378
  console.log(JSON.stringify(await runDoctor(config), null, 2));
1144
379
  });
1145
- command.command("onboarding").description("Print the Forge agent onboarding contract").action(createCliAction(config, "/api/v1/agents/onboarding"));
1146
- command.command("comments").description("List all visible Forge comments").action(createCliAction(config, "/api/v1/comments"));
1147
- command.command("psyche-overview").description("Inspect the Psyche overview read model").action(createCliAction(config, "/api/v1/psyche/overview"));
1148
- command.command("route-check").description("Compare plugin route coverage against the live Forge OpenAPI paths").action(async () => {
380
+ command.command("route-check").description("Compare curated plugin route coverage against the live Forge OpenAPI paths").action(async () => {
1149
381
  console.log(JSON.stringify(await runRouteCheck(config), null, 2));
1150
382
  });
1151
383
  }, { commands: ["forge"] });