@pleri/olam-cli 0.1.186 → 0.1.195
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/README.md +1 -1
- package/dist/ask/knowledge-pack-builder.d.ts.map +1 -1
- package/dist/ask/knowledge-pack-builder.js +5 -0
- package/dist/ask/knowledge-pack-builder.js.map +1 -1
- package/dist/ask/knowledge-pack.generated.d.ts.map +1 -1
- package/dist/ask/knowledge-pack.generated.js +442 -33
- package/dist/ask/knowledge-pack.generated.js.map +1 -1
- package/dist/commands/auth-status.js +2 -2
- package/dist/commands/auth-status.js.map +1 -1
- package/dist/commands/auth.js +1 -1
- package/dist/commands/auth.js.map +1 -1
- package/dist/commands/bootstrap.d.ts +4 -0
- package/dist/commands/bootstrap.d.ts.map +1 -1
- package/dist/commands/bootstrap.js +6 -9
- package/dist/commands/bootstrap.js.map +1 -1
- package/dist/commands/clean.js +1 -1
- package/dist/commands/clean.js.map +1 -1
- package/dist/commands/completion.d.ts.map +1 -1
- package/dist/commands/completion.js +1 -4
- package/dist/commands/completion.js.map +1 -1
- package/dist/commands/create.d.ts.map +1 -1
- package/dist/commands/create.js +10 -0
- package/dist/commands/create.js.map +1 -1
- package/dist/commands/crystallize.js +12 -14
- package/dist/commands/crystallize.js.map +1 -1
- package/dist/commands/destroy.d.ts +13 -1
- package/dist/commands/destroy.d.ts.map +1 -1
- package/dist/commands/destroy.js +52 -6
- package/dist/commands/destroy.js.map +1 -1
- package/dist/commands/dispatch.d.ts +9 -0
- package/dist/commands/dispatch.d.ts.map +1 -1
- package/dist/commands/dispatch.js +21 -2
- package/dist/commands/dispatch.js.map +1 -1
- package/dist/commands/doctor.d.ts +1 -1
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +29 -22
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/enter.d.ts +3 -3
- package/dist/commands/enter.d.ts.map +1 -1
- package/dist/commands/enter.js +57 -44
- package/dist/commands/enter.js.map +1 -1
- package/dist/commands/flywheel/index.d.ts.map +1 -1
- package/dist/commands/flywheel/index.js +1 -1
- package/dist/commands/flywheel/index.js.map +1 -1
- package/dist/commands/host-cp.d.ts.map +1 -1
- package/dist/commands/host-cp.js +2 -1
- package/dist/commands/host-cp.js.map +1 -1
- package/dist/commands/implode.d.ts.map +1 -1
- package/dist/commands/implode.js +1 -1
- package/dist/commands/implode.js.map +1 -1
- package/dist/commands/init.d.ts +20 -0
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +102 -9
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/install.js +2 -2
- package/dist/commands/install.js.map +1 -1
- package/dist/commands/kg-build.d.ts.map +1 -1
- package/dist/commands/kg-build.js +3 -0
- package/dist/commands/kg-build.js.map +1 -1
- package/dist/commands/kg-classify.d.ts +20 -0
- package/dist/commands/kg-classify.d.ts.map +1 -1
- package/dist/commands/kg-classify.js +59 -42
- package/dist/commands/kg-classify.js.map +1 -1
- package/dist/commands/kg-mirror.d.ts +40 -0
- package/dist/commands/kg-mirror.d.ts.map +1 -0
- package/dist/commands/kg-mirror.js +228 -0
- package/dist/commands/kg-mirror.js.map +1 -0
- package/dist/commands/mcp/index.js +1 -1
- package/dist/commands/mcp/index.js.map +1 -1
- package/dist/commands/memory/index.d.ts.map +1 -1
- package/dist/commands/memory/index.js +1 -1
- package/dist/commands/memory/index.js.map +1 -1
- package/dist/commands/resume.d.ts.map +1 -1
- package/dist/commands/resume.js +1 -1
- package/dist/commands/resume.js.map +1 -1
- package/dist/commands/services-tls.d.ts +120 -0
- package/dist/commands/services-tls.d.ts.map +1 -0
- package/dist/commands/services-tls.js +434 -0
- package/dist/commands/services-tls.js.map +1 -0
- package/dist/commands/services.d.ts.map +1 -1
- package/dist/commands/services.js +40 -1
- package/dist/commands/services.js.map +1 -1
- package/dist/commands/setup-linux-gate.d.ts.map +1 -1
- package/dist/commands/setup-linux-gate.js +1 -3
- package/dist/commands/setup-linux-gate.js.map +1 -1
- package/dist/commands/setup-metrics.d.ts.map +1 -1
- package/dist/commands/setup-metrics.js +1 -2
- package/dist/commands/setup-metrics.js.map +1 -1
- package/dist/commands/setup-phase-5a-skill-source.d.ts +17 -1
- package/dist/commands/setup-phase-5a-skill-source.d.ts.map +1 -1
- package/dist/commands/setup-phase-5a-skill-source.js +69 -6
- package/dist/commands/setup-phase-5a-skill-source.js.map +1 -1
- package/dist/commands/setup.d.ts +26 -1
- package/dist/commands/setup.d.ts.map +1 -1
- package/dist/commands/setup.js +189 -47
- package/dist/commands/setup.js.map +1 -1
- package/dist/commands/skills-onboard.d.ts.map +1 -1
- package/dist/commands/skills-onboard.js +4 -1
- package/dist/commands/skills-onboard.js.map +1 -1
- package/dist/commands/skills-source.d.ts.map +1 -1
- package/dist/commands/skills-source.js +20 -4
- package/dist/commands/skills-source.js.map +1 -1
- package/dist/commands/status.d.ts.map +1 -1
- package/dist/commands/status.js +5 -1
- package/dist/commands/status.js.map +1 -1
- package/dist/commands/upgrade.d.ts.map +1 -1
- package/dist/commands/upgrade.js +1 -3
- package/dist/commands/upgrade.js.map +1 -1
- package/dist/commands/yolo.d.ts.map +1 -1
- package/dist/commands/yolo.js +1 -1
- package/dist/commands/yolo.js.map +1 -1
- package/dist/context.d.ts +4 -0
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +3 -2
- package/dist/context.js.map +1 -1
- package/dist/image-digests.json +8 -8
- package/dist/index.js +4409 -2375
- package/dist/index.js.map +1 -1
- package/dist/lib/auth-refresh-kubernetes.d.ts.map +1 -1
- package/dist/lib/auth-refresh-kubernetes.js +14 -5
- package/dist/lib/auth-refresh-kubernetes.js.map +1 -1
- package/dist/lib/bootstrap-kubernetes.d.ts +41 -0
- package/dist/lib/bootstrap-kubernetes.d.ts.map +1 -1
- package/dist/lib/bootstrap-kubernetes.js +289 -36
- package/dist/lib/bootstrap-kubernetes.js.map +1 -1
- package/dist/lib/cf-access-token.d.ts.map +1 -1
- package/dist/lib/cf-access-token.js +2 -3
- package/dist/lib/cf-access-token.js.map +1 -1
- package/dist/lib/health-probes.d.ts +14 -0
- package/dist/lib/health-probes.d.ts.map +1 -1
- package/dist/lib/health-probes.js +41 -3
- package/dist/lib/health-probes.js.map +1 -1
- package/dist/lib/help-groups.d.ts +36 -0
- package/dist/lib/help-groups.d.ts.map +1 -0
- package/dist/lib/help-groups.js +124 -0
- package/dist/lib/help-groups.js.map +1 -0
- package/dist/lib/k8s-bootstrap.d.ts +6 -0
- package/dist/lib/k8s-bootstrap.d.ts.map +1 -1
- package/dist/lib/k8s-bootstrap.js +15 -2
- package/dist/lib/k8s-bootstrap.js.map +1 -1
- package/dist/lib/k8s-secret-render.d.ts.map +1 -1
- package/dist/lib/k8s-secret-render.js +17 -10
- package/dist/lib/k8s-secret-render.js.map +1 -1
- package/dist/lib/memory-secret.d.ts +15 -2
- package/dist/lib/memory-secret.d.ts.map +1 -1
- package/dist/lib/memory-secret.js +25 -8
- package/dist/lib/memory-secret.js.map +1 -1
- package/dist/lib/upgrade-check.d.ts +60 -0
- package/dist/lib/upgrade-check.d.ts.map +1 -0
- package/dist/lib/upgrade-check.js +169 -0
- package/dist/lib/upgrade-check.js.map +1 -0
- package/dist/lib/upgrade-kubernetes.d.ts +17 -0
- package/dist/lib/upgrade-kubernetes.d.ts.map +1 -1
- package/dist/lib/upgrade-kubernetes.js +125 -1
- package/dist/lib/upgrade-kubernetes.js.map +1 -1
- package/dist/mcp-server.js +2687 -2818
- package/hermes-bundle/version.json +1 -1
- package/host-cp/k8s/manifests/30-configmap.yaml +8 -1
- package/host-cp/k8s/manifests/50-deployment.yaml +1 -1
- package/host-cp/k8s/manifests/60-service.yaml +12 -4
- package/host-cp/k8s/manifests/70-ingressroute.yaml +58 -0
- package/host-cp/k8s/manifests/auth-service/50-deployment.yaml +1 -1
- package/host-cp/k8s/manifests/chunks-electric/10-serviceaccount.yaml +8 -0
- package/host-cp/k8s/manifests/chunks-electric/20-rbac.yaml +27 -0
- package/host-cp/k8s/manifests/chunks-electric/30-configmap.yaml +23 -0
- package/host-cp/k8s/manifests/chunks-electric/45-pvc.yaml +19 -0
- package/host-cp/k8s/manifests/chunks-electric/50-deployment.yaml +84 -0
- package/host-cp/k8s/manifests/chunks-electric/60-service.yaml +17 -0
- package/host-cp/k8s/manifests/chunks-postgres/10-serviceaccount.yaml +8 -0
- package/host-cp/k8s/manifests/chunks-postgres/20-rbac.yaml +29 -0
- package/host-cp/k8s/manifests/chunks-postgres/30-configmap.yaml +185 -0
- package/host-cp/k8s/manifests/chunks-postgres/45-pvc.yaml +24 -0
- package/host-cp/k8s/manifests/chunks-postgres/50-deployment.yaml +101 -0
- package/host-cp/k8s/manifests/chunks-postgres/60-service.yaml +24 -0
- package/host-cp/k8s/manifests/kg-service/50-deployment.yaml +1 -1
- package/host-cp/k8s/manifests/mcp-auth-service/50-deployment.yaml +1 -1
- package/host-cp/k8s/manifests/memory-service/50-deployment.yaml +1 -1
- package/host-cp/k8s/manifests/plan-chat-service/10-serviceaccount.yaml +8 -0
- package/host-cp/k8s/manifests/plan-chat-service/20-rbac.yaml +29 -0
- package/host-cp/k8s/manifests/plan-chat-service/30-configmap.yaml +36 -0
- package/host-cp/k8s/manifests/plan-chat-service/45-pvc.yaml +24 -0
- package/host-cp/k8s/manifests/plan-chat-service/50-deployment.yaml +135 -0
- package/host-cp/k8s/manifests/plan-chat-service/60-service.yaml +17 -0
- package/host-cp/src/plan-chat-secret.mjs +16 -1
- package/host-cp/src/plan-chat-service.mjs +709 -11
- package/host-cp/src/planning-sessions.mjs +252 -0
- package/host-cp/src/pr-cache.mjs +11 -2
- package/host-cp/src/server.mjs +128 -22
- package/package.json +2 -1
|
@@ -175,6 +175,216 @@ export async function setCrystallizeStatus({ pool, sessionId, status, worldId =
|
|
|
175
175
|
);
|
|
176
176
|
}
|
|
177
177
|
|
|
178
|
+
/**
|
|
179
|
+
* Create a new multi-turn DISPATCH session (multi-turn-cloud-sandbox-dispatch
|
|
180
|
+
* Phase A2 — distinct from createPlanningSession which is for planning-flow
|
|
181
|
+
* crystallization sessions under world_id='_planning').
|
|
182
|
+
*
|
|
183
|
+
* Allocates a UUID session_id, INSERTs a planning_sessions row with
|
|
184
|
+
* session_type='dispatch' + caller-supplied world_id, applies operator-supplied
|
|
185
|
+
* budget_usd_cap / allow_unpriced_models defaults, returns the session_id.
|
|
186
|
+
*
|
|
187
|
+
* No seed chunk: dispatch sessions accumulate chunks from the agent runtime
|
|
188
|
+
* (via /v1/chunks); we don't pre-seed a system chunk because Electric shape
|
|
189
|
+
* subscribers for dispatch sessions can wait for the first real agent chunk.
|
|
190
|
+
*
|
|
191
|
+
* @param {object} opts
|
|
192
|
+
* @param {object} opts.pool
|
|
193
|
+
* @param {string} opts.actorId
|
|
194
|
+
* @param {string} opts.worldId — operator-supplied; identifies the dispatch
|
|
195
|
+
* target world (NOT the '_planning' sentinel used by createPlanningSession).
|
|
196
|
+
* @param {number | null} [opts.budgetUsdCap=null] — per-session budget cap;
|
|
197
|
+
* null = uncapped. When null AND `OLAM_SESSION_BUDGET_DEFAULT_USD` is set,
|
|
198
|
+
* the env-default applies at /v1/dispatch-turn check time (Phase D); here
|
|
199
|
+
* we record the row exactly as supplied.
|
|
200
|
+
* @param {boolean} [opts.allowUnpricedModels=false] — opt session into the
|
|
201
|
+
* pricingForModel-returns-null fallback (Plan A T11 mitigation; default
|
|
202
|
+
* refuses unknown models with 502).
|
|
203
|
+
* @returns {Promise<{ session_id: string }>}
|
|
204
|
+
*/
|
|
205
|
+
export async function createDispatchSession({
|
|
206
|
+
pool,
|
|
207
|
+
actorId,
|
|
208
|
+
worldId,
|
|
209
|
+
budgetUsdCap = null,
|
|
210
|
+
allowUnpricedModels = false,
|
|
211
|
+
sessionId: providedSessionId = null,
|
|
212
|
+
}) {
|
|
213
|
+
if (!actorId || typeof actorId !== 'string') {
|
|
214
|
+
throw new Error('createDispatchSession: actorId required');
|
|
215
|
+
}
|
|
216
|
+
if (!worldId || typeof worldId !== 'string') {
|
|
217
|
+
throw new Error('createDispatchSession: worldId required');
|
|
218
|
+
}
|
|
219
|
+
// A6 (Decision 9 always-on threading): callers MAY supply session_id to
|
|
220
|
+
// upsert an existing planning_sessions row (e.g. /api/cloud-dispatch
|
|
221
|
+
// pre-creating the thread before forwarding to plan-DO). When omitted,
|
|
222
|
+
// we generate a UUID. ON CONFLICT DO NOTHING handles the race where
|
|
223
|
+
// the SPA called /v1/sessions/create concurrently AND server-side
|
|
224
|
+
// cloud-dispatch tried to pre-create the same row.
|
|
225
|
+
const sessionId = providedSessionId ?? randomUUID();
|
|
226
|
+
await pool.query(
|
|
227
|
+
`INSERT INTO planning_sessions
|
|
228
|
+
(session_id, actor_id, session_type, world_id, budget_usd_cap, allow_unpriced_models)
|
|
229
|
+
VALUES ($1, $2, 'dispatch', $3, $4, $5)
|
|
230
|
+
ON CONFLICT (session_id) DO NOTHING`,
|
|
231
|
+
[sessionId, actorId, worldId, budgetUsdCap, allowUnpricedModels],
|
|
232
|
+
);
|
|
233
|
+
return { session_id: sessionId };
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Atomic test-and-set lock claim on a dispatch session
|
|
238
|
+
* (multi-turn-cloud-sandbox-dispatch Phase A3 — Decision 4 + T5 mitigation).
|
|
239
|
+
*
|
|
240
|
+
* Pattern: single-statement UPDATE ... WHERE in_flight_turn_id IS NULL RETURNING.
|
|
241
|
+
* Two concurrent attempts: first claim wins (RETURNING yields 1 row); second
|
|
242
|
+
* sees empty result + must return 409 to caller. Matches the established
|
|
243
|
+
* planning-sessions.mjs:169 setCrystallizeStatus atomic-write idiom.
|
|
244
|
+
*
|
|
245
|
+
* @param {object} opts
|
|
246
|
+
* @param {object} opts.pool
|
|
247
|
+
* @param {string} opts.sessionId
|
|
248
|
+
* @param {string} opts.turnId — operator-or-server-generated turn UUID
|
|
249
|
+
* @returns {Promise<boolean>} true if lock claimed, false if already held
|
|
250
|
+
*/
|
|
251
|
+
export async function claimDispatchTurnLock({ pool, sessionId, turnId }) {
|
|
252
|
+
const result = await pool.query(
|
|
253
|
+
`UPDATE planning_sessions
|
|
254
|
+
SET in_flight_turn_id = $1,
|
|
255
|
+
in_flight_turn_started_at = NOW(),
|
|
256
|
+
last_turn_at = NOW()
|
|
257
|
+
WHERE session_id = $2
|
|
258
|
+
AND session_type = 'dispatch'
|
|
259
|
+
AND in_flight_turn_id IS NULL
|
|
260
|
+
RETURNING session_id`,
|
|
261
|
+
[turnId, sessionId],
|
|
262
|
+
);
|
|
263
|
+
return (result.rows?.length ?? 0) > 0;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Clear the in-flight turn lock after dispatch completes (success OR failure).
|
|
268
|
+
*
|
|
269
|
+
* @param {object} opts
|
|
270
|
+
* @param {object} opts.pool
|
|
271
|
+
* @param {string} opts.sessionId
|
|
272
|
+
*/
|
|
273
|
+
export async function clearDispatchTurnLock({ pool, sessionId }) {
|
|
274
|
+
await pool.query(
|
|
275
|
+
`UPDATE planning_sessions
|
|
276
|
+
SET in_flight_turn_id = NULL,
|
|
277
|
+
in_flight_turn_started_at = NULL
|
|
278
|
+
WHERE session_id = $1
|
|
279
|
+
AND session_type = 'dispatch'`,
|
|
280
|
+
[sessionId],
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Halt a dispatch session — operator-driven "block next turn" state (T13).
|
|
286
|
+
*
|
|
287
|
+
* Sets halted_at to NOW() AND clears in_flight_turn_id. Future /v1/dispatch-turn
|
|
288
|
+
* calls return 409 'session_halted' until reactivateDispatchSession clears
|
|
289
|
+
* halted_at. Does NOT stop an in-flight container — the running container
|
|
290
|
+
* completes its current turn naturally. UX is "Block next turn" not "Stop"
|
|
291
|
+
* (Plan A Phase C C6).
|
|
292
|
+
*
|
|
293
|
+
* Scoped by actor_id for ownership isolation.
|
|
294
|
+
*
|
|
295
|
+
* @param {object} opts
|
|
296
|
+
* @param {object} opts.pool
|
|
297
|
+
* @param {string} opts.sessionId
|
|
298
|
+
* @param {string} opts.actorId
|
|
299
|
+
* @returns {Promise<boolean>} true if a session row was updated; false if
|
|
300
|
+
* the session_id was not found / not owned by actorId.
|
|
301
|
+
*/
|
|
302
|
+
export async function haltDispatchSession({ pool, sessionId, actorId }) {
|
|
303
|
+
const result = await pool.query(
|
|
304
|
+
`UPDATE planning_sessions
|
|
305
|
+
SET halted_at = NOW(),
|
|
306
|
+
in_flight_turn_id = NULL,
|
|
307
|
+
in_flight_turn_started_at = NULL
|
|
308
|
+
WHERE session_id = $1
|
|
309
|
+
AND session_type = 'dispatch'
|
|
310
|
+
AND actor_id = $2
|
|
311
|
+
RETURNING session_id`,
|
|
312
|
+
[sessionId, actorId],
|
|
313
|
+
);
|
|
314
|
+
return (result.rows?.length ?? 0) > 0;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Reactivate a halted dispatch session — clears halted_at so subsequent
|
|
319
|
+
* /v1/dispatch-turn calls can claim the lock again. Idempotent (clearing an
|
|
320
|
+
* already-null halted_at is a no-op).
|
|
321
|
+
*
|
|
322
|
+
* @param {object} opts
|
|
323
|
+
* @param {object} opts.pool
|
|
324
|
+
* @param {string} opts.sessionId
|
|
325
|
+
* @param {string} opts.actorId
|
|
326
|
+
* @returns {Promise<boolean>} true if a session row was updated; false if
|
|
327
|
+
* the session_id was not found / not owned by actorId.
|
|
328
|
+
*/
|
|
329
|
+
export async function reactivateDispatchSession({ pool, sessionId, actorId }) {
|
|
330
|
+
const result = await pool.query(
|
|
331
|
+
`UPDATE planning_sessions
|
|
332
|
+
SET halted_at = NULL
|
|
333
|
+
WHERE session_id = $1
|
|
334
|
+
AND session_type = 'dispatch'
|
|
335
|
+
AND actor_id = $2
|
|
336
|
+
RETURNING session_id`,
|
|
337
|
+
[sessionId, actorId],
|
|
338
|
+
);
|
|
339
|
+
return (result.rows?.length ?? 0) > 0;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Read a dispatch session by session_id + scope to caller's actor_id
|
|
344
|
+
* (ownership check). Returns the session metadata needed for budget check
|
|
345
|
+
* + plan-DO forward, OR null when not found / not owned.
|
|
346
|
+
*
|
|
347
|
+
* @param {object} opts
|
|
348
|
+
* @param {object} opts.pool
|
|
349
|
+
* @param {string} opts.sessionId
|
|
350
|
+
* @param {string} opts.actorId
|
|
351
|
+
* @returns {Promise<null | {
|
|
352
|
+
* session_id: string,
|
|
353
|
+
* world_id: string | null,
|
|
354
|
+
* actor_id: string,
|
|
355
|
+
* total_usd: number,
|
|
356
|
+
* budget_usd_cap: number | null,
|
|
357
|
+
* allow_unpriced_models: boolean,
|
|
358
|
+
* halted_at: string | null,
|
|
359
|
+
* }>}
|
|
360
|
+
*/
|
|
361
|
+
export async function getDispatchSession({ pool, sessionId, actorId }) {
|
|
362
|
+
const result = await pool.query(
|
|
363
|
+
`SELECT session_id, world_id, actor_id,
|
|
364
|
+
total_usd, budget_usd_cap, allow_unpriced_models,
|
|
365
|
+
halted_at
|
|
366
|
+
FROM planning_sessions
|
|
367
|
+
WHERE session_id = $1
|
|
368
|
+
AND session_type = 'dispatch'
|
|
369
|
+
AND actor_id = $2`,
|
|
370
|
+
[sessionId, actorId],
|
|
371
|
+
);
|
|
372
|
+
const row = result.rows?.[0];
|
|
373
|
+
if (!row) return null;
|
|
374
|
+
return {
|
|
375
|
+
session_id: row.session_id,
|
|
376
|
+
world_id: row.world_id ?? null,
|
|
377
|
+
actor_id: row.actor_id,
|
|
378
|
+
total_usd: Number(row.total_usd ?? 0),
|
|
379
|
+
budget_usd_cap:
|
|
380
|
+
row.budget_usd_cap === null || row.budget_usd_cap === undefined
|
|
381
|
+
? null
|
|
382
|
+
: Number(row.budget_usd_cap),
|
|
383
|
+
allow_unpriced_models: Boolean(row.allow_unpriced_models),
|
|
384
|
+
halted_at: row.halted_at ?? null,
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
|
|
178
388
|
/**
|
|
179
389
|
* List planning sessions for a given actorId, ordered by created_at DESC.
|
|
180
390
|
*
|
|
@@ -204,6 +414,48 @@ export async function listPlanningSessions({ pool, actorId, limit = 50 }) {
|
|
|
204
414
|
return result.rows;
|
|
205
415
|
}
|
|
206
416
|
|
|
417
|
+
/**
|
|
418
|
+
* List multi-turn DISPATCH sessions for a given actorId, ordered by
|
|
419
|
+
* last_turn_at DESC (most recently active first), excluding archived sessions.
|
|
420
|
+
*
|
|
421
|
+
* Distinct from listPlanningSessions: this returns only `session_type='dispatch'`
|
|
422
|
+
* rows + projects the multi-turn-specific columns (total_usd, in_flight_turn_id,
|
|
423
|
+
* halted_at, etc.) that the SPA's SessionsListView (Phase C C3) renders.
|
|
424
|
+
*
|
|
425
|
+
* @param {object} opts
|
|
426
|
+
* @param {object} opts.pool
|
|
427
|
+
* @param {string} opts.actorId
|
|
428
|
+
* @param {number} [opts.limit=50]
|
|
429
|
+
* @returns {Promise<Array<{
|
|
430
|
+
* session_id: string,
|
|
431
|
+
* world_id: string | null,
|
|
432
|
+
* total_usd: string,
|
|
433
|
+
* budget_usd_cap: string | null,
|
|
434
|
+
* in_flight_turn_id: string | null,
|
|
435
|
+
* halted_at: string | null,
|
|
436
|
+
* last_turn_at: string | null,
|
|
437
|
+
* created_at: string,
|
|
438
|
+
* summary: string | null,
|
|
439
|
+
* }>>}
|
|
440
|
+
*/
|
|
441
|
+
export async function listDispatchSessions({ pool, actorId, limit = 50 }) {
|
|
442
|
+
const result = await pool.query(
|
|
443
|
+
`SELECT session_id, world_id,
|
|
444
|
+
total_usd, budget_usd_cap,
|
|
445
|
+
in_flight_turn_id, halted_at,
|
|
446
|
+
last_turn_at, created_at,
|
|
447
|
+
summary
|
|
448
|
+
FROM planning_sessions
|
|
449
|
+
WHERE actor_id = $1
|
|
450
|
+
AND session_type = 'dispatch'
|
|
451
|
+
AND archived_at IS NULL
|
|
452
|
+
ORDER BY last_turn_at DESC NULLS LAST, created_at DESC
|
|
453
|
+
LIMIT $2`,
|
|
454
|
+
[actorId, limit],
|
|
455
|
+
);
|
|
456
|
+
return result.rows;
|
|
457
|
+
}
|
|
458
|
+
|
|
207
459
|
/**
|
|
208
460
|
* Load lightweight metadata for an existing in-flight planning session.
|
|
209
461
|
*
|
package/host-cp/src/pr-cache.mjs
CHANGED
|
@@ -127,7 +127,7 @@ async function fetchPrData(prUrl, getToken) {
|
|
|
127
127
|
/**
|
|
128
128
|
* Create a PR data cache with TTL and concurrent-fetch coalescing.
|
|
129
129
|
*
|
|
130
|
-
* @returns {{ getPr: (prUrl: string, getToken: () => Promise<string|null>) => Promise<PrData|null
|
|
130
|
+
* @returns {{ getPr: (prUrl: string, getToken: () => Promise<string|null>) => Promise<PrData|null>, deletePr: (prUrl: string) => void }}
|
|
131
131
|
*/
|
|
132
132
|
export function createPrCache() {
|
|
133
133
|
/** @type {Map<string, PrCacheEntry>} */
|
|
@@ -197,5 +197,14 @@ export function createPrCache() {
|
|
|
197
197
|
}
|
|
198
198
|
}
|
|
199
199
|
|
|
200
|
-
|
|
200
|
+
/**
|
|
201
|
+
* Evict a PR entry from the cache (call on world destroy).
|
|
202
|
+
*
|
|
203
|
+
* @param {string} prUrl
|
|
204
|
+
*/
|
|
205
|
+
function deletePr(prUrl) {
|
|
206
|
+
cache.delete(prUrl);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
return { getPr, deletePr };
|
|
201
210
|
}
|
package/host-cp/src/server.mjs
CHANGED
|
@@ -354,6 +354,50 @@ function readDogfoodRepoUrl() {
|
|
|
354
354
|
return '';
|
|
355
355
|
}
|
|
356
356
|
|
|
357
|
+
/**
|
|
358
|
+
* Resolve the cloud-kg-mirror classifier proxy URL the runner forwards
|
|
359
|
+
* into spawned CF Sandbox child worlds. When set, host-cp enriches
|
|
360
|
+
* cloud-dispatch bodies with `kgProxyUrl` + `kgProxyBearer`; the runner
|
|
361
|
+
* injects those into the sandbox env so the PreToolUse hook's
|
|
362
|
+
* cloud-sandbox flavor can POST /v1/classify against the
|
|
363
|
+
* kg-mirror-worker. Source order: OLAM_KG_PROXY_URL env, then
|
|
364
|
+
* ~/.olam/kg-proxy-url file. Absent → no injection (dispatched worlds
|
|
365
|
+
* see no proxy URL → hook falls through to grep via existing fail-open).
|
|
366
|
+
* Mirrors readAnthropicBaseUrl() — operators have ONE pattern.
|
|
367
|
+
*
|
|
368
|
+
* Phase 1 (cloud-kg-mirror): docs/plans/cloud-kg-mirror/README.md §6-7.
|
|
369
|
+
*/
|
|
370
|
+
function readKgProxyUrl() {
|
|
371
|
+
const fromOlamEnv = process.env['OLAM_KG_PROXY_URL'];
|
|
372
|
+
if (fromOlamEnv && fromOlamEnv.length > 0) return fromOlamEnv.trim();
|
|
373
|
+
try {
|
|
374
|
+
const file = path.join(os.homedir(), '.olam', 'kg-proxy-url');
|
|
375
|
+
const content = fs.readFileSync(file, 'utf-8').trim();
|
|
376
|
+
if (content.length > 0) return content;
|
|
377
|
+
} catch {
|
|
378
|
+
// file absent — fall through
|
|
379
|
+
}
|
|
380
|
+
return '';
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* Bearer that pairs with readKgProxyUrl(). Stored in
|
|
385
|
+
* ~/.olam/kg-proxy-bearer (chmod 600 by operator). Mirrors the
|
|
386
|
+
* source-order convention; absent → no injection.
|
|
387
|
+
*/
|
|
388
|
+
function readKgProxyBearer() {
|
|
389
|
+
const fromOlamEnv = process.env['OLAM_KG_PROXY_BEARER'];
|
|
390
|
+
if (fromOlamEnv && fromOlamEnv.length > 0) return fromOlamEnv.trim();
|
|
391
|
+
try {
|
|
392
|
+
const file = path.join(os.homedir(), '.olam', 'kg-proxy-bearer');
|
|
393
|
+
const content = fs.readFileSync(file, 'utf-8').trim();
|
|
394
|
+
if (content.length > 0) return content;
|
|
395
|
+
} catch {
|
|
396
|
+
// file absent — fall through
|
|
397
|
+
}
|
|
398
|
+
return '';
|
|
399
|
+
}
|
|
400
|
+
|
|
357
401
|
/** @type {Record<string, number>} */
|
|
358
402
|
let WORLDS = {};
|
|
359
403
|
|
|
@@ -510,6 +554,9 @@ const prPoller = createPrMergePoller({
|
|
|
510
554
|
WORLDS = next;
|
|
511
555
|
persistRegistry();
|
|
512
556
|
}
|
|
557
|
+
const prState = prStateStore.get(worldId);
|
|
558
|
+
if (prState?.pr_url) prCache.deletePr(prState.pr_url);
|
|
559
|
+
progressCache.delete(worldId);
|
|
513
560
|
},
|
|
514
561
|
pollIntervalMs: PR_POLL_INTERVAL_MS,
|
|
515
562
|
gracePeriodMs: MERGE_GRACE_MS,
|
|
@@ -1366,6 +1413,9 @@ const server = http.createServer(instrumentHandler('host-cp', async (req, res) =
|
|
|
1366
1413
|
WORLDS = next;
|
|
1367
1414
|
persistRegistry();
|
|
1368
1415
|
}
|
|
1416
|
+
const prStateForDelete = prStateStore.get(id);
|
|
1417
|
+
if (prStateForDelete?.pr_url) prCache.deletePr(prStateForDelete.pr_url);
|
|
1418
|
+
progressCache.delete(id);
|
|
1369
1419
|
return jsonReply(res, 200, { worlds: WORLDS });
|
|
1370
1420
|
}
|
|
1371
1421
|
|
|
@@ -1526,7 +1576,7 @@ const server = http.createServer(instrumentHandler('host-cp', async (req, res) =
|
|
|
1526
1576
|
if (url.pathname === '/api/repos' && req.method === 'GET') {
|
|
1527
1577
|
const config = loadGlobalConfig();
|
|
1528
1578
|
if ('error' in config) {
|
|
1529
|
-
return jsonReply(res, 500, { error: config.error });
|
|
1579
|
+
return jsonReply(res, 500, { error: config.error, message: config.error });
|
|
1530
1580
|
}
|
|
1531
1581
|
return jsonReply(res, 200, { repos: config.repos });
|
|
1532
1582
|
}
|
|
@@ -1536,7 +1586,7 @@ const server = http.createServer(instrumentHandler('host-cp', async (req, res) =
|
|
|
1536
1586
|
if (url.pathname === '/api/runbooks' && req.method === 'GET') {
|
|
1537
1587
|
const config = loadGlobalConfig();
|
|
1538
1588
|
if ('error' in config) {
|
|
1539
|
-
return jsonReply(res, 500, { error: config.error });
|
|
1589
|
+
return jsonReply(res, 500, { error: config.error, message: config.error });
|
|
1540
1590
|
}
|
|
1541
1591
|
return jsonReply(res, 200, { runbooks: config.runbooks });
|
|
1542
1592
|
}
|
|
@@ -1679,7 +1729,7 @@ const server = http.createServer(instrumentHandler('host-cp', async (req, res) =
|
|
|
1679
1729
|
const body = await readRequestBody(req);
|
|
1680
1730
|
const provider = body && typeof body === 'object' && body.provider ? String(body.provider) : 'claude';
|
|
1681
1731
|
const label = body && typeof body === 'object' && body.label ? String(body.label) : '';
|
|
1682
|
-
if (!label) return jsonReply(res, 400, { error: 'label_required' });
|
|
1732
|
+
if (!label) return jsonReply(res, 400, { error: 'label_required', message: 'label field is required' });
|
|
1683
1733
|
const upstream = await authServiceFetch('POST', '/credentials/add', { provider, label });
|
|
1684
1734
|
const data = await upstream.json();
|
|
1685
1735
|
return jsonReply(res, upstream.status, data);
|
|
@@ -1996,9 +2046,9 @@ const server = http.createServer(instrumentHandler('host-cp', async (req, res) =
|
|
|
1996
2046
|
req.on('data', (c) => { body += c; });
|
|
1997
2047
|
req.on('end', () => {
|
|
1998
2048
|
let parsed;
|
|
1999
|
-
try { parsed = JSON.parse(body || '{}'); } catch { return jsonReply(res, 400, { error: 'invalid_json' }); }
|
|
2049
|
+
try { parsed = JSON.parse(body || '{}'); } catch (e) { return jsonReply(res, 400, { error: 'invalid_json', message: e.message }); }
|
|
2000
2050
|
const { worldId, prUrl, prNumber, prRepo } = parsed ?? {};
|
|
2001
|
-
if (!worldId || !prUrl) return jsonReply(res, 400, { error: 'worldId and prUrl required' });
|
|
2051
|
+
if (!worldId || !prUrl) return jsonReply(res, 400, { error: 'missing_fields', message: 'worldId and prUrl required' });
|
|
2002
2052
|
prStateStore.set(worldId, {
|
|
2003
2053
|
pr_url: prUrl,
|
|
2004
2054
|
pr_number: prNumber ?? null,
|
|
@@ -2023,9 +2073,9 @@ const server = http.createServer(instrumentHandler('host-cp', async (req, res) =
|
|
|
2023
2073
|
req.on('data', (c) => { body += c; });
|
|
2024
2074
|
req.on('end', () => {
|
|
2025
2075
|
let parsed;
|
|
2026
|
-
try { parsed = JSON.parse(body || '{}'); } catch { return jsonReply(res, 400, { error: 'invalid_json' }); }
|
|
2076
|
+
try { parsed = JSON.parse(body || '{}'); } catch (e) { return jsonReply(res, 400, { error: 'invalid_json', message: e.message }); }
|
|
2027
2077
|
const existing = prStateStore.get(worldId);
|
|
2028
|
-
if (!existing) return jsonReply(res, 404, { error: 'world not in PR state store' });
|
|
2078
|
+
if (!existing) return jsonReply(res, 404, { error: 'not_found', message: 'world not in PR state store' });
|
|
2029
2079
|
const updates = {};
|
|
2030
2080
|
if (typeof parsed.autoDestroyOnMerge === 'boolean') updates.auto_destroy_on_merge = parsed.autoDestroyOnMerge;
|
|
2031
2081
|
prStateStore.set(worldId, updates);
|
|
@@ -2108,7 +2158,7 @@ const server = http.createServer(instrumentHandler('host-cp', async (req, res) =
|
|
|
2108
2158
|
const id = decodeURIComponent(planConvMatch[1]);
|
|
2109
2159
|
try {
|
|
2110
2160
|
const conv = planOrchestrator.getConversation(id);
|
|
2111
|
-
if (!conv) return jsonReply(res, 404, { error: 'not_found', id });
|
|
2161
|
+
if (!conv) return jsonReply(res, 404, { error: 'not_found', id, message: 'conversation not found' });
|
|
2112
2162
|
// Return persisted turns alongside conversation metadata.
|
|
2113
2163
|
const turns = planOrchestrator.getTurns(id);
|
|
2114
2164
|
return jsonReply(res, 200, { ...conv, turns });
|
|
@@ -2128,7 +2178,7 @@ const server = http.createServer(instrumentHandler('host-cp', async (req, res) =
|
|
|
2128
2178
|
const content = (body && typeof body === 'object' && typeof body.content === 'string')
|
|
2129
2179
|
? body.content.trim()
|
|
2130
2180
|
: '';
|
|
2131
|
-
if (!content) return jsonReply(res, 400, { error: 'content_required' });
|
|
2181
|
+
if (!content) return jsonReply(res, 400, { error: 'content_required', message: 'turn content must be a non-empty string' });
|
|
2132
2182
|
const personaOverride = (body && typeof body === 'object' && typeof body.personaOverride === 'string')
|
|
2133
2183
|
? body.personaOverride
|
|
2134
2184
|
: undefined;
|
|
@@ -2139,7 +2189,7 @@ const server = http.createServer(instrumentHandler('host-cp', async (req, res) =
|
|
|
2139
2189
|
const result = await planOrchestrator.submitTurn({ conversationId, content, personaOverride, mentionedPersonas });
|
|
2140
2190
|
return jsonReply(res, 202, result);
|
|
2141
2191
|
} catch (err) {
|
|
2142
|
-
if (err.code === 'NOT_FOUND') return jsonReply(res, 404, { error: 'not_found', id: conversationId });
|
|
2192
|
+
if (err.code === 'NOT_FOUND') return jsonReply(res, 404, { error: 'not_found', id: conversationId, message: 'conversation not found' });
|
|
2143
2193
|
return jsonReply(res, 500, { error: 'submit_failed', message: err.message });
|
|
2144
2194
|
}
|
|
2145
2195
|
}
|
|
@@ -2154,7 +2204,7 @@ const server = http.createServer(instrumentHandler('host-cp', async (req, res) =
|
|
|
2154
2204
|
}
|
|
2155
2205
|
const toPersona = body?.toPersona;
|
|
2156
2206
|
if (typeof toPersona !== 'string' || !toPersona) {
|
|
2157
|
-
return jsonReply(res, 400, { error: 'toPersona_required' });
|
|
2207
|
+
return jsonReply(res, 400, { error: 'toPersona_required', message: 'toPersona field is required' });
|
|
2158
2208
|
}
|
|
2159
2209
|
const mode = ['full', 'distilled', 'quoted'].includes(body?.mode) ? body.mode : 'full';
|
|
2160
2210
|
const selectedTurnIds = Array.isArray(body?.selectedTurnIds) ? body.selectedTurnIds : [];
|
|
@@ -2164,7 +2214,7 @@ const server = http.createServer(instrumentHandler('host-cp', async (req, res) =
|
|
|
2164
2214
|
});
|
|
2165
2215
|
return jsonReply(res, 200, result);
|
|
2166
2216
|
} catch (err) {
|
|
2167
|
-
if (err.code === 'NOT_FOUND') return jsonReply(res, 404, { error: 'not_found', id: conversationId });
|
|
2217
|
+
if (err.code === 'NOT_FOUND') return jsonReply(res, 404, { error: 'not_found', id: conversationId, message: 'conversation not found' });
|
|
2168
2218
|
return jsonReply(res, 500, { error: 'handoff_failed', message: err.message });
|
|
2169
2219
|
}
|
|
2170
2220
|
}
|
|
@@ -2174,7 +2224,7 @@ const server = http.createServer(instrumentHandler('host-cp', async (req, res) =
|
|
|
2174
2224
|
if (!await requirePlanCredential(res)) return;
|
|
2175
2225
|
const conversationId = decodeURIComponent(planStreamMatch[1]);
|
|
2176
2226
|
const conv = planOrchestrator.getConversation(conversationId);
|
|
2177
|
-
if (!conv) return jsonReply(res, 404, { error: 'not_found', id: conversationId });
|
|
2227
|
+
if (!conv) return jsonReply(res, 404, { error: 'not_found', id: conversationId, message: 'conversation not found' });
|
|
2178
2228
|
|
|
2179
2229
|
res.writeHead(200, {
|
|
2180
2230
|
'Content-Type': 'text/event-stream; charset=utf-8',
|
|
@@ -2214,7 +2264,7 @@ const server = http.createServer(instrumentHandler('host-cp', async (req, res) =
|
|
|
2214
2264
|
const conversationId = decodeURIComponent(planAgentInviteMatch[1]);
|
|
2215
2265
|
const personaId = decodeURIComponent(planAgentInviteMatch[2]);
|
|
2216
2266
|
const conv = planOrchestrator.getConversation(conversationId);
|
|
2217
|
-
if (!conv) return jsonReply(res, 404, { error: 'not_found', id: conversationId });
|
|
2267
|
+
if (!conv) return jsonReply(res, 404, { error: 'not_found', id: conversationId, message: 'conversation not found' });
|
|
2218
2268
|
const agent = planOrchestrator.inviteLookout(conversationId, personaId);
|
|
2219
2269
|
return jsonReply(res, 200, agent);
|
|
2220
2270
|
}
|
|
@@ -2232,7 +2282,7 @@ const server = http.createServer(instrumentHandler('host-cp', async (req, res) =
|
|
|
2232
2282
|
if (typeof body?.muted === 'boolean') updates.muted = body.muted;
|
|
2233
2283
|
if (typeof body?.mode === 'string') updates.mode = body.mode;
|
|
2234
2284
|
const agent = planOrchestrator.updateLookout(conversationId, personaId, updates);
|
|
2235
|
-
if (!agent) return jsonReply(res, 404, { error: 'not_found' });
|
|
2285
|
+
if (!agent) return jsonReply(res, 404, { error: 'not_found', message: 'lookout agent not found' });
|
|
2236
2286
|
return jsonReply(res, 200, agent);
|
|
2237
2287
|
}
|
|
2238
2288
|
|
|
@@ -2253,7 +2303,7 @@ const server = http.createServer(instrumentHandler('host-cp', async (req, res) =
|
|
|
2253
2303
|
const changed = action === 'dismiss'
|
|
2254
2304
|
? planOrchestrator.dismissSignal(conversationId, signalId)
|
|
2255
2305
|
: planOrchestrator.useSignal(conversationId, signalId);
|
|
2256
|
-
if (!changed) return jsonReply(res, 404, { error: 'not_found' });
|
|
2306
|
+
if (!changed) return jsonReply(res, 404, { error: 'not_found', message: 'signal not found' });
|
|
2257
2307
|
return jsonReply(res, 200, { ok: true });
|
|
2258
2308
|
}
|
|
2259
2309
|
|
|
@@ -2578,6 +2628,35 @@ const server = http.createServer(instrumentHandler('host-cp', async (req, res) =
|
|
|
2578
2628
|
// Keep `messages` too — plan-DO may use it for multi-turn fidelity later.
|
|
2579
2629
|
}
|
|
2580
2630
|
|
|
2631
|
+
// multi-turn-cloud-sandbox-dispatch Phase A6 (Decision 9 always-on
|
|
2632
|
+
// threading): pre-create the planning_sessions row server-side so
|
|
2633
|
+
// EVERY cloud dispatch surfaces as a SPA thread — including pre-PR
|
|
2634
|
+
// #1182-style one-shot calls that never invoked /v1/sessions/create
|
|
2635
|
+
// directly. Idempotent (ON CONFLICT DO NOTHING in createDispatchSession).
|
|
2636
|
+
// Fail-soft: thread-creation failure does NOT block the dispatch
|
|
2637
|
+
// (the operator's task still ships); we log + continue.
|
|
2638
|
+
try {
|
|
2639
|
+
if (parsed.world_id && parsed.session_id) {
|
|
2640
|
+
const planChatBearer = readPlanChatSecret();
|
|
2641
|
+
const planChatBase = process.env.PLAN_CHAT_SERVICE_URL || 'http://127.0.0.1:3200';
|
|
2642
|
+
await fetch(`${planChatBase}/v1/sessions/create`, {
|
|
2643
|
+
method: 'POST',
|
|
2644
|
+
headers: {
|
|
2645
|
+
'content-type': 'application/json',
|
|
2646
|
+
authorization: `Bearer ${planChatBearer}`,
|
|
2647
|
+
},
|
|
2648
|
+
body: JSON.stringify({
|
|
2649
|
+
world_id: parsed.world_id,
|
|
2650
|
+
session_id: parsed.session_id,
|
|
2651
|
+
}),
|
|
2652
|
+
});
|
|
2653
|
+
}
|
|
2654
|
+
} catch (threadErr) {
|
|
2655
|
+
console.warn(
|
|
2656
|
+
`[cloud-dispatch] thread pre-create failed (continuing): ${threadErr?.message ?? threadErr}`,
|
|
2657
|
+
);
|
|
2658
|
+
}
|
|
2659
|
+
|
|
2581
2660
|
// Gap 3: enrich the dispatch body with the operator's anthropicBaseUrl
|
|
2582
2661
|
// so plan-DO can propagate it to spawned CF Sandbox child worlds.
|
|
2583
2662
|
// Only injected when not already set by the SPA (SPA has no auth-worker
|
|
@@ -2590,9 +2669,18 @@ const server = http.createServer(instrumentHandler('host-cp', async (req, res) =
|
|
|
2590
2669
|
// ~/.olam/dogfood-repo-url file. Absent → no injection (the dispatch
|
|
2591
2670
|
// runs text-only, like before this change).
|
|
2592
2671
|
const dogfoodRepoUrl = readDogfoodRepoUrl();
|
|
2672
|
+
// cloud-kg-mirror Phase 1: propagate the classifier Worker URL +
|
|
2673
|
+
// bearer so the runner can inject them into the sandbox env. When
|
|
2674
|
+
// either is missing the dispatched world's PreToolUse hook falls
|
|
2675
|
+
// through to grep via the existing fail-open path. Operator opts
|
|
2676
|
+
// in by writing ~/.olam/kg-proxy-url + ~/.olam/kg-proxy-bearer.
|
|
2677
|
+
const kgProxyUrl = readKgProxyUrl();
|
|
2678
|
+
const kgProxyBearer = readKgProxyBearer();
|
|
2593
2679
|
let enrichedObj = null;
|
|
2594
2680
|
if (anthropicBaseUrl && !parsed.anthropicBaseUrl) enrichedObj = { ...(enrichedObj ?? parsed), anthropicBaseUrl };
|
|
2595
2681
|
if (dogfoodRepoUrl && !parsed.repoUrl) enrichedObj = { ...(enrichedObj ?? parsed), repoUrl: dogfoodRepoUrl };
|
|
2682
|
+
if (kgProxyUrl && !parsed.kgProxyUrl) enrichedObj = { ...(enrichedObj ?? parsed), kgProxyUrl };
|
|
2683
|
+
if (kgProxyBearer && !parsed.kgProxyBearer) enrichedObj = { ...(enrichedObj ?? parsed), kgProxyBearer };
|
|
2596
2684
|
// Use `parsed` (not raw `body`) as the no-enrichment fallback so that the
|
|
2597
2685
|
// messages→prompt normalisation above is always forwarded even when no
|
|
2598
2686
|
// env-var enrichments are applied.
|
|
@@ -2661,10 +2749,18 @@ const server = http.createServer(instrumentHandler('host-cp', async (req, res) =
|
|
|
2661
2749
|
// bearer registration without additional plan metadata.
|
|
2662
2750
|
}
|
|
2663
2751
|
|
|
2664
|
-
// Enrich with anthropicBaseUrl from the host config.
|
|
2752
|
+
// Enrich with anthropicBaseUrl + kgProxy from the host config.
|
|
2665
2753
|
const anthropicBaseUrl = readAnthropicBaseUrl();
|
|
2754
|
+
const kgProxyUrl = readKgProxyUrl();
|
|
2755
|
+
const kgProxyBearer = readKgProxyBearer();
|
|
2666
2756
|
const planId = parsed.planId ?? parsed.session_id ?? `plan-${Date.now()}`;
|
|
2667
|
-
const requestBody = {
|
|
2757
|
+
const requestBody = {
|
|
2758
|
+
...parsed,
|
|
2759
|
+
planId,
|
|
2760
|
+
...(anthropicBaseUrl ? { anthropicBaseUrl } : {}),
|
|
2761
|
+
...(kgProxyUrl ? { kgProxyUrl } : {}),
|
|
2762
|
+
...(kgProxyBearer ? { kgProxyBearer } : {}),
|
|
2763
|
+
};
|
|
2668
2764
|
|
|
2669
2765
|
const basicAuth = Buffer.from(`operator:${showcasePw}`).toString('base64');
|
|
2670
2766
|
// Phase H h2: attach CF Access service-token headers when configured.
|
|
@@ -2717,7 +2813,7 @@ const server = http.createServer(instrumentHandler('host-cp', async (req, res) =
|
|
|
2717
2813
|
if (serversMatch && req.method === 'GET') {
|
|
2718
2814
|
const worldId = decodeURIComponent(serversMatch[1]);
|
|
2719
2815
|
if (!(worldId in WORLDS)) {
|
|
2720
|
-
return jsonReply(res, 404, { error: 'unknown_world', worldId });
|
|
2816
|
+
return jsonReply(res, 404, { error: 'unknown_world', worldId, message: 'world not in registry' });
|
|
2721
2817
|
}
|
|
2722
2818
|
return handleListServers(req, res, worldId);
|
|
2723
2819
|
}
|
|
@@ -2727,7 +2823,7 @@ const server = http.createServer(instrumentHandler('host-cp', async (req, res) =
|
|
|
2727
2823
|
const worldId = decodeURIComponent(bridgesMatch[1]);
|
|
2728
2824
|
const portSegment = bridgesMatch[3] ? parseInt(bridgesMatch[3], 10) : null;
|
|
2729
2825
|
if (!(worldId in WORLDS)) {
|
|
2730
|
-
return jsonReply(res, 404, { error: 'unknown_world', worldId });
|
|
2826
|
+
return jsonReply(res, 404, { error: 'unknown_world', worldId, message: 'world not in registry' });
|
|
2731
2827
|
}
|
|
2732
2828
|
return handleServerBridges(req, res, worldId, portSegment);
|
|
2733
2829
|
}
|
|
@@ -2798,7 +2894,7 @@ const server = http.createServer(instrumentHandler('host-cp', async (req, res) =
|
|
|
2798
2894
|
if (v1WorldsStatusMatch && req.method === 'GET') {
|
|
2799
2895
|
const worldId = decodeURIComponent(v1WorldsStatusMatch[1]);
|
|
2800
2896
|
if (!V1_WORLD_ID.test(worldId)) {
|
|
2801
|
-
return jsonReply(res, 400, { error: 'invalid_world_id', worldId });
|
|
2897
|
+
return jsonReply(res, 400, { error: 'invalid_world_id', worldId, message: 'worldId contains invalid characters' });
|
|
2802
2898
|
}
|
|
2803
2899
|
try {
|
|
2804
2900
|
const report = await hostCpEngine.getWorldStatus(worldId);
|
|
@@ -2816,7 +2912,7 @@ const server = http.createServer(instrumentHandler('host-cp', async (req, res) =
|
|
|
2816
2912
|
if (v1WorldsLogsMatch && req.method === 'GET') {
|
|
2817
2913
|
const worldId = decodeURIComponent(v1WorldsLogsMatch[1]);
|
|
2818
2914
|
if (!V1_WORLD_ID.test(worldId)) {
|
|
2819
|
-
return jsonReply(res, 400, { error: 'invalid_world_id', worldId });
|
|
2915
|
+
return jsonReply(res, 400, { error: 'invalid_world_id', worldId, message: 'worldId contains invalid characters' });
|
|
2820
2916
|
}
|
|
2821
2917
|
const tailParam = url.searchParams.get('tail');
|
|
2822
2918
|
const followParam = url.searchParams.get('follow');
|
|
@@ -3466,6 +3562,7 @@ async function renderSpaShell(filePath, pathname) {
|
|
|
3466
3562
|
// block comment above (Phase E5 cutover).
|
|
3467
3563
|
void skipBootstrap; // wildcard invariant: always true; documents intent
|
|
3468
3564
|
html = html.replace(/<head>/i, `<head>\n ${bearerInjection}${cloudInjection}`);
|
|
3565
|
+
if (_spaCacheByKey.size > 10) _spaCacheByKey.clear();
|
|
3469
3566
|
_spaCacheByKey.set(cacheKey, html);
|
|
3470
3567
|
return html;
|
|
3471
3568
|
}
|
|
@@ -3660,6 +3757,15 @@ server.listen(PORT, '0.0.0.0', () => {
|
|
|
3660
3757
|
} else if (AUTH_SERVICE_URL) {
|
|
3661
3758
|
console.log(` auth-service=${AUTH_SERVICE_URL} (X-Olam-Secret configured)`);
|
|
3662
3759
|
}
|
|
3760
|
+
// Warn when only one of the two cloud env vars is set — the most common
|
|
3761
|
+
// misconfiguration that silently disables the Cloud toggle in the SPA.
|
|
3762
|
+
const hasCloudUrl = Boolean(process.env.OLAM_CLOUD_URL);
|
|
3763
|
+
const hasShowcasePw = Boolean(process.env.OLAM_SHOWCASE_PASSWORD);
|
|
3764
|
+
if (hasCloudUrl && !hasShowcasePw) {
|
|
3765
|
+
console.warn(' [cloud] OLAM_CLOUD_URL set but OLAM_SHOWCASE_PASSWORD missing — cloud_enabled will be false');
|
|
3766
|
+
} else if (hasShowcasePw && !hasCloudUrl) {
|
|
3767
|
+
console.warn(' [cloud] OLAM_SHOWCASE_PASSWORD set but OLAM_CLOUD_URL missing — cloud_enabled will be false');
|
|
3768
|
+
}
|
|
3663
3769
|
});
|
|
3664
3770
|
|
|
3665
3771
|
// Graceful shutdown so docker compose down → SIGTERM → flush + close.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pleri/olam-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.195",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"olam": "./bin/olam.cjs"
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
"test": "vitest run --passWithNoTests",
|
|
33
33
|
"test:ci": "vitest run --reporter=basic --passWithNoTests",
|
|
34
34
|
"test:docker": "vitest run --config vitest.config.docker.ts",
|
|
35
|
+
"test:e2e:k3d-https": "node e2e/k3d-https-e2e.spec.mjs",
|
|
35
36
|
"audit:publish-deps": "node scripts/audit-publish-deps.mjs",
|
|
36
37
|
"audit:cli-bundle-k8s": "node scripts/audit-cli-bundle-k8s.mjs",
|
|
37
38
|
"audit:cli-package-contents": "node scripts/audit-cli-package-contents.mjs"
|