wicked-crew 0.16.4 → 0.16.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/ask-paths.d.ts +5 -2
- package/dist/api/ask-paths.d.ts.map +1 -1
- package/dist/api/ask-paths.js +6 -3
- package/dist/api/ask-paths.js.map +1 -1
- package/dist/api/chat-scope.d.ts +8 -3
- package/dist/api/chat-scope.d.ts.map +1 -1
- package/dist/api/chat-scope.js +17 -5
- package/dist/api/chat-scope.js.map +1 -1
- package/dist/api/chat-workflow.d.ts +60 -0
- package/dist/api/chat-workflow.d.ts.map +1 -0
- package/dist/api/chat-workflow.js +80 -0
- package/dist/api/chat-workflow.js.map +1 -0
- package/dist/api/endpoint-manifest-live.d.ts.map +1 -1
- package/dist/api/endpoint-manifest-live.js +2 -0
- package/dist/api/endpoint-manifest-live.js.map +1 -1
- package/dist/api/needs.d.ts +116 -0
- package/dist/api/needs.d.ts.map +1 -0
- package/dist/api/needs.js +618 -0
- package/dist/api/needs.js.map +1 -0
- package/dist/api/routes.d.ts +23 -1
- package/dist/api/routes.d.ts.map +1 -1
- package/dist/api/routes.js +297 -25
- package/dist/api/routes.js.map +1 -1
- package/dist/api/run-liveness.d.ts +8 -0
- package/dist/api/run-liveness.d.ts.map +1 -1
- package/dist/api/run-liveness.js +39 -7
- package/dist/api/run-liveness.js.map +1 -1
- package/dist/api/run-timing-index.d.ts +6 -0
- package/dist/api/run-timing-index.d.ts.map +1 -1
- package/dist/api/run-timing-index.js +18 -0
- package/dist/api/run-timing-index.js.map +1 -1
- package/dist/api/server.d.ts +8 -0
- package/dist/api/server.d.ts.map +1 -1
- package/dist/api/server.js +149 -3
- package/dist/api/server.js.map +1 -1
- package/dist/api/system-snapshot.d.ts +7 -15
- package/dist/api/system-snapshot.d.ts.map +1 -1
- package/dist/api/system-snapshot.js +7 -115
- package/dist/api/system-snapshot.js.map +1 -1
- package/dist/core/run-identity.d.ts.map +1 -1
- package/dist/core/run-identity.js +3 -0
- package/dist/core/run-identity.js.map +1 -1
- package/dist/evals/capture.d.ts +93 -0
- package/dist/evals/capture.d.ts.map +1 -0
- package/dist/evals/capture.js +419 -0
- package/dist/evals/capture.js.map +1 -0
- package/dist/evals/replay.d.ts +33 -0
- package/dist/evals/replay.d.ts.map +1 -0
- package/dist/evals/replay.js +87 -0
- package/dist/evals/replay.js.map +1 -0
- package/dist/evals/routes.d.ts +48 -0
- package/dist/evals/routes.d.ts.map +1 -0
- package/dist/evals/routes.js +121 -0
- package/dist/evals/routes.js.map +1 -0
- package/dist/evals/run-cli.d.ts +31 -0
- package/dist/evals/run-cli.d.ts.map +1 -0
- package/dist/evals/run-cli.js +128 -0
- package/dist/evals/run-cli.js.map +1 -0
- package/dist/evals/runner.d.ts +34 -0
- package/dist/evals/runner.d.ts.map +1 -0
- package/dist/evals/runner.js +181 -0
- package/dist/evals/runner.js.map +1 -0
- package/dist/evals/score.d.ts +31 -0
- package/dist/evals/score.d.ts.map +1 -0
- package/dist/evals/score.js +191 -0
- package/dist/evals/score.js.map +1 -0
- package/dist/evals/seed-needs-you.d.ts +17 -0
- package/dist/evals/seed-needs-you.d.ts.map +1 -0
- package/dist/evals/seed-needs-you.js +57 -0
- package/dist/evals/seed-needs-you.js.map +1 -0
- package/dist/evals/seed-rig.d.ts +9 -0
- package/dist/evals/seed-rig.d.ts.map +1 -0
- package/dist/evals/seed-rig.js +156 -0
- package/dist/evals/seed-rig.js.map +1 -0
- package/dist/evals/stats.d.ts +31 -0
- package/dist/evals/stats.d.ts.map +1 -0
- package/dist/evals/stats.js +59 -0
- package/dist/evals/stats.js.map +1 -0
- package/dist/evals/steering.d.ts +9 -0
- package/dist/evals/steering.d.ts.map +1 -0
- package/dist/evals/steering.js +25 -0
- package/dist/evals/steering.js.map +1 -0
- package/dist/evals/store.d.ts +26 -0
- package/dist/evals/store.d.ts.map +1 -0
- package/dist/evals/store.js +96 -0
- package/dist/evals/store.js.map +1 -0
- package/dist/evals/synthetic.d.ts +17 -0
- package/dist/evals/synthetic.d.ts.map +1 -0
- package/dist/evals/synthetic.js +71 -0
- package/dist/evals/synthetic.js.map +1 -0
- package/dist/evals/types.d.ts +29 -0
- package/dist/evals/types.d.ts.map +1 -0
- package/dist/evals/types.js +25 -0
- package/dist/evals/types.js.map +1 -0
- package/dist/evals/workflow.d.ts +33 -0
- package/dist/evals/workflow.d.ts.map +1 -0
- package/dist/evals/workflow.js +66 -0
- package/dist/evals/workflow.js.map +1 -0
- package/dist/insights/analyze-cli.d.ts +18 -0
- package/dist/insights/analyze-cli.d.ts.map +1 -0
- package/dist/insights/analyze-cli.js +71 -0
- package/dist/insights/analyze-cli.js.map +1 -0
- package/dist/insights/analyze.d.ts +136 -0
- package/dist/insights/analyze.d.ts.map +1 -0
- package/dist/insights/analyze.js +358 -0
- package/dist/insights/analyze.js.map +1 -0
- package/dist/insights/review-service.d.ts +127 -0
- package/dist/insights/review-service.d.ts.map +1 -0
- package/dist/insights/review-service.js +392 -0
- package/dist/insights/review-service.js.map +1 -0
- package/dist/insights/routes.d.ts +15 -0
- package/dist/insights/routes.d.ts.map +1 -0
- package/dist/insights/routes.js +66 -0
- package/dist/insights/routes.js.map +1 -0
- package/dist/insights/run-facts.d.ts +64 -0
- package/dist/insights/run-facts.d.ts.map +1 -0
- package/dist/insights/run-facts.js +152 -0
- package/dist/insights/run-facts.js.map +1 -0
- package/dist/insights/stats.d.ts +38 -0
- package/dist/insights/stats.d.ts.map +1 -0
- package/dist/insights/stats.js +102 -0
- package/dist/insights/stats.js.map +1 -0
- package/dist/insights/store.d.ts +89 -0
- package/dist/insights/store.d.ts.map +1 -0
- package/dist/insights/store.js +87 -0
- package/dist/insights/store.js.map +1 -0
- package/dist/insights/workflow.d.ts +22 -0
- package/dist/insights/workflow.d.ts.map +1 -0
- package/dist/insights/workflow.js +39 -0
- package/dist/insights/workflow.js.map +1 -0
- package/dist/qe/ledger.d.ts +15 -0
- package/dist/qe/ledger.d.ts.map +1 -1
- package/dist/qe/ledger.js +24 -0
- package/dist/qe/ledger.js.map +1 -1
- package/dist/schedules/routes.d.ts +48 -0
- package/dist/schedules/routes.d.ts.map +1 -0
- package/dist/schedules/routes.js +72 -0
- package/dist/schedules/routes.js.map +1 -0
- package/dist/schedules/scheduler.d.ts +58 -0
- package/dist/schedules/scheduler.d.ts.map +1 -0
- package/dist/schedules/scheduler.js +194 -0
- package/dist/schedules/scheduler.js.map +1 -0
- package/dist/schedules/store.d.ts +67 -0
- package/dist/schedules/store.d.ts.map +1 -0
- package/dist/schedules/store.js +148 -0
- package/dist/schedules/store.js.map +1 -0
- package/dist/skills/runtime.d.ts +12 -2
- package/dist/skills/runtime.d.ts.map +1 -1
- package/dist/skills/runtime.js +27 -1
- package/dist/skills/runtime.js.map +1 -1
- package/dist/team/routes.d.ts +10 -0
- package/dist/team/routes.d.ts.map +1 -1
- package/dist/team/routes.js +3 -0
- package/dist/team/routes.js.map +1 -1
- package/endpoint-manifest.json +132 -1
- package/package.json +4 -3
- package/workflows/review-recent-work.json +54 -0
- package/workflows/run-evals.json +29 -0
package/dist/api/routes.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { REVIEW_WORKFLOW } from '../insights/workflow.js';
|
|
1
2
|
import { busSeamsOffWarning, busUnavailableWarning } from '../core/bus-notice.js';
|
|
2
3
|
import { vaultBinWarning, vaultDiagnostics } from '../core/vault-bin.js';
|
|
3
4
|
import { PlanSchema, toLaunchPlan } from './plan-schema.js';
|
|
@@ -36,7 +37,8 @@ import { ROSTER_REFRESH_WAIT_MS, rosterWithStandingFactory } from './roster-stan
|
|
|
36
37
|
import { SeatContainerStatus, gatewayDirOf, isContainerLoginCli, isContainerSeatFloor, isHostSeatFloor, loginDetail, runSeatLogin, seatLoginDepsFor, } from './seat-container-login.js';
|
|
37
38
|
import { ChatTurnIndex } from './chat-turns.js';
|
|
38
39
|
import { ChatScopeIndex, chatScopeDeps, chatScopeStatement, prepareChatScratch, removeChatScratch, namedKindRefusal, resolveChatScope, } from './chat-scope.js';
|
|
39
|
-
import { ASK_TURN_BUDGET_SECS, AskPathIndex,
|
|
40
|
+
import { ASK_TURN_BUDGET_SECS, AskPathIndex, isAskTurn } from './ask-paths.js';
|
|
41
|
+
import { chatTurnStep, resolveChatWorkflow, servedChatWorkflows, writeCapableStep } from './chat-workflow.js';
|
|
40
42
|
import { reassignRefusedSeats } from '../core/reassign-refusal.js';
|
|
41
43
|
import { allowedRootsFor, isInsideRoot, openWithSystemDefault } from './open-path.js';
|
|
42
44
|
import { InvalidDiffBaseError, MERGE_BASE_LITERAL, NotARegularFileError, UnresolvableDiffBaseError, branchDiff, isGitStopped, isPlainRef, readFileCapped, worktreeDiff, } from './run-files.js';
|
|
@@ -57,6 +59,10 @@ import { isSteeringAuthorRun, landSteeringProposal } from './steering-landing.js
|
|
|
57
59
|
import { inventoryComplete, parseInventoryClaims } from '../core/inventory-claim.js';
|
|
58
60
|
import { ApproveEditSchema, approveEdited, captureLaunchRoots, editInFlight, FileProposalSchema, registerCaptureRoutes } from './capture.js';
|
|
59
61
|
import { registerTestingRoutes } from './testing.js';
|
|
62
|
+
import { registerDefaultEvalRoutes, runAndRecord } from '../evals/routes.js';
|
|
63
|
+
import { prepareRunEvalsLaunch, RUN_EVALS_WORKFLOW } from '../evals/workflow.js';
|
|
64
|
+
import { defaultReplaySet } from '../evals/replay.js';
|
|
65
|
+
import { gardenRootFor } from '../learn/scheduler.js';
|
|
60
66
|
import { DEMO_PRESET, demoLaunchRoots, registerDemoDocumentRoute, registerDemoRoutes, registerWalkthroughRoutes, walkthroughAcceptance } from './recording.js';
|
|
61
67
|
import { projectDocsRoot } from '../interactive/project-root.js';
|
|
62
68
|
import { registerSkillsRoutes } from './skills.js';
|
|
@@ -94,7 +100,8 @@ import { AcpFoldCache, EngineVersionCache, eventsDirOf, installedPackageVersion,
|
|
|
94
100
|
import { GovernanceDiagnostics } from './governance-health.js';
|
|
95
101
|
import { ATTACHMENT_LIMITS, ATTACHMENT_MAX_MESSAGE_BYTES, AttachmentError, ChatAttachmentStore, attachmentWire, attachmentsBlock, boundaryOf, parseMultipart, stageForSeat } from './chat-attachments.js';
|
|
96
102
|
import { CHAT_HISTORY_MAX_LIMIT, ChatHistoryStore, decodeCursor } from './chat-history.js';
|
|
97
|
-
import { PROBE_TIMEOUT_MS, SYSTEM_BUDGET_MS, SYSTEM_STATS_DAYS, WeekFoldCache, assembleSnapshot,
|
|
103
|
+
import { PROBE_TIMEOUT_MS, SYSTEM_BUDGET_MS, SYSTEM_STATS_DAYS, SYSTEM_STATS_TTL_MS, WeekFoldCache, assembleSnapshot, buildNeeds, buildClis, buildGovernance, diskRow, estateReadonlyCall, infraRow, judgeGaps, livenessThresholds, loadRow, probeBroker, probeBus, probeEstate, probeGateway, probeInteractive, probeVault, readBridgeLockAsync, readWeek, resolveContainerRuntime, runLiveness, settleWithin, skillsRow, sortRuns, stateHomeRow, unfinished, } from './system-snapshot.js';
|
|
104
|
+
import { RunCostCache } from './needs.js';
|
|
98
105
|
import { governanceStoreFix, legacyHomeOutboxPath } from '../core/governance-store.js';
|
|
99
106
|
import { UsageError, replayOutboxInto } from '../cli/governance.js';
|
|
100
107
|
import { foldDeadletters } from './governance-health.js';
|
|
@@ -721,6 +728,9 @@ export const ChatOpenSchema = z.object({
|
|
|
721
728
|
primary: z.string().min(1).optional(),
|
|
722
729
|
/** (api-types 0.137.0, DES-SYSTEM-CHAT) The page the chat was started on, for the history list. */
|
|
723
730
|
startedOn: z.object({ route: z.string().min(1).max(512), label: z.string().min(1).max(200) }).strict().optional(),
|
|
731
|
+
/** (api-types 0.141.0, crew#1157) The read-only workflow every turn runs (`analyze`); see
|
|
732
|
+
* `chat-workflow.ts`. Omitted ⇒ a plain chat. */
|
|
733
|
+
workflow: z.string().min(1).max(64).optional(),
|
|
724
734
|
}).strict();
|
|
725
735
|
/**
|
|
726
736
|
* The `singleSeat` disclosure (crew#641, hole closed in crew#650) — built in ONE place so the 201
|
|
@@ -1218,6 +1228,8 @@ runtime = {}) {
|
|
|
1218
1228
|
const healthWarningsOf = (stateHome, baseSkill) => [
|
|
1219
1229
|
...(stateHome === null ? [] : stateHome.findings.map((f) => ({ kind: f.kind, severity: f.severity, message: f.message }))),
|
|
1220
1230
|
...(baseSkill?.finding ? [{ kind: baseSkill.finding.kind, severity: baseSkill.finding.severity, message: baseSkill.finding.message }] : []),
|
|
1231
|
+
// crew#1149: while the boot ladder publishes, one info line instead of a base-skill error.
|
|
1232
|
+
...[runtime.skills?.publishingFinding() ?? null].filter((f) => f !== null).map((f) => ({ kind: f.kind, severity: f.severity, message: f.message })),
|
|
1221
1233
|
// crew#753: a garden older than the minimum is a blocking finding on the first screen too.
|
|
1222
1234
|
...[gardenBlocker()].filter((f) => f !== null),
|
|
1223
1235
|
// DES-TEAMING-002 T0: the daemon's bus did not open at boot, so the engine was handed none
|
|
@@ -1235,6 +1247,22 @@ runtime = {}) {
|
|
|
1235
1247
|
// crew#1068: the engine has no wicked-vault CLI to record held boundary findings with.
|
|
1236
1248
|
...[adapter.vaultBin != null ? vaultBinWarning(adapter.vaultBin) : null].filter((w) => w !== null),
|
|
1237
1249
|
];
|
|
1250
|
+
// crew#1157: the engine's built-in presets are compiled in, so the chat workflows it carries are
|
|
1251
|
+
// read once (on the first probe that can read them) and held until restart.
|
|
1252
|
+
let chatWorkflowsHeld = null;
|
|
1253
|
+
const chatWorkflowsNow = async () => {
|
|
1254
|
+
if (chatWorkflowsHeld !== null)
|
|
1255
|
+
return chatWorkflowsHeld;
|
|
1256
|
+
if (typeof adapter.listPresets !== 'function')
|
|
1257
|
+
return [];
|
|
1258
|
+
try {
|
|
1259
|
+
chatWorkflowsHeld = servedChatWorkflows(await adapter.listPresets());
|
|
1260
|
+
return chatWorkflowsHeld;
|
|
1261
|
+
}
|
|
1262
|
+
catch {
|
|
1263
|
+
return [];
|
|
1264
|
+
}
|
|
1265
|
+
};
|
|
1238
1266
|
app.get(`${V}/health`, { config: { manifest: { statusCodes: [200] } } }, async () => {
|
|
1239
1267
|
const ping = await enginePing();
|
|
1240
1268
|
// F-E2E-030: what this deployment can keep. A composer reads `capabilities.deliverGate`
|
|
@@ -1268,9 +1296,19 @@ runtime = {}) {
|
|
|
1268
1296
|
launchPrimary: true,
|
|
1269
1297
|
// api-types 0.136.0 (DES-SYSTEM-VIEW): `GET /system`, the Nerve Center snapshot, is served.
|
|
1270
1298
|
system: true,
|
|
1299
|
+
// api-types 0.140.0: attention[] is the one needs-you list; unresolved[] + toReview; POST /runs/:id/resolve.
|
|
1300
|
+
attentionRanked: true,
|
|
1271
1301
|
// api-types 0.137.0 (DES-SYSTEM-CHAT): the chat history (list, reopen, close) and attachments.
|
|
1272
1302
|
chatList: chatHistory !== null,
|
|
1273
1303
|
chatAttachments: true,
|
|
1304
|
+
// api-types 0.141.0 (crew#1157): the read-only workflows `POST /chats {workflow}` runs on THIS
|
|
1305
|
+
// engine (`analyze` on a core-ts that carries it); `[]` = plain chats only.
|
|
1306
|
+
chatWorkflows: await chatWorkflowsNow(),
|
|
1307
|
+
// api-types 0.145.0: the evals, insights and schedules surfaces, read off the route table this
|
|
1308
|
+
// daemon serves, so a route set booted without one says false rather than an invented true.
|
|
1309
|
+
defaultEvals: app.hasRoute({ method: 'GET', url: `${V}/testing/default-evals/suites` }),
|
|
1310
|
+
insights: app.hasRoute({ method: 'GET', url: `${V}/insights` }),
|
|
1311
|
+
schedules: app.hasRoute({ method: 'GET', url: `${V}/schedules` }),
|
|
1274
1312
|
};
|
|
1275
1313
|
// wicked-core#411 / crew#497: the state-home blocker rides the health probe as a WARNING. The
|
|
1276
1314
|
// daemon still SERVES (status stays ok — studio must load and show the blocker) but refuses to
|
|
@@ -1280,7 +1318,7 @@ runtime = {}) {
|
|
|
1280
1318
|
const stateHome = runtime.stateHome !== undefined ? await runtime.stateHome.refresh() : null;
|
|
1281
1319
|
// crew#554: the BASE skill posture for the next launch — the composer's confirm line
|
|
1282
1320
|
// ("discipline skill: <name> gen N" / "MISSING — …"). Cached by the skills runtime: no I/O.
|
|
1283
|
-
const baseSkill = runtime.skills?.
|
|
1321
|
+
const baseSkill = runtime.skills?.reportedBaseSkill() ?? null;
|
|
1284
1322
|
// F-W1-102: the base skill's ERROR rides `warnings` too. A daemon under `require` with no published
|
|
1285
1323
|
// generation holding the skill (the crew-only install: no wicked-garden) REFUSES every launch at
|
|
1286
1324
|
// intake — it must never read "status ok, no warnings". The SAME finding object as
|
|
@@ -2205,6 +2243,29 @@ runtime = {}) {
|
|
|
2205
2243
|
req.log.warn(`linked-issue resolution failed; launching on the intent alone: ${message(err)}`);
|
|
2206
2244
|
}
|
|
2207
2245
|
}
|
|
2246
|
+
// DES-RUNS-REVIEW-001 §3: a hand launch of the run review (`/workflow-review-recent-work`) is prepared like a
|
|
2247
|
+
// scheduled one: its snapshot written, repo-less, one seat, its review directory as the only write root.
|
|
2248
|
+
// DES-DEFAULT-EVALS-001 §7: a launch of `run-evals` (`/workflow-run-evals`) is prepared by the daemon, which runs
|
|
2249
|
+
// the evals in-process and writes the report copy its Tool step reads; it delivers nothing.
|
|
2250
|
+
if (b.workflow === RUN_EVALS_WORKFLOW) {
|
|
2251
|
+
try {
|
|
2252
|
+
await runEvalsPrepare(input);
|
|
2253
|
+
}
|
|
2254
|
+
catch (err) {
|
|
2255
|
+
return reply.code(409).send({ error: `the default evals could not be prepared: ${message(err)}` });
|
|
2256
|
+
}
|
|
2257
|
+
deliver = 'none';
|
|
2258
|
+
}
|
|
2259
|
+
const isReview = b.workflow === REVIEW_WORKFLOW && runtime.reviewLaunch !== undefined;
|
|
2260
|
+
if (isReview) {
|
|
2261
|
+
try {
|
|
2262
|
+
await runtime.reviewLaunch(input);
|
|
2263
|
+
}
|
|
2264
|
+
catch (err) {
|
|
2265
|
+
return reply.code(409).send({ error: `the review could not be prepared: ${message(err)}` });
|
|
2266
|
+
}
|
|
2267
|
+
deliver = 'none';
|
|
2268
|
+
}
|
|
2208
2269
|
try {
|
|
2209
2270
|
const runId = await adapter.launchRun(input);
|
|
2210
2271
|
// crew#641/#642 (item 5): when a run is promoted from a chat, record how many seats
|
|
@@ -2314,6 +2375,8 @@ runtime = {}) {
|
|
|
2314
2375
|
}
|
|
2315
2376
|
catch (err) {
|
|
2316
2377
|
const msg = message(err);
|
|
2378
|
+
if (isReview)
|
|
2379
|
+
runtime.reviewLaunchRefused?.(input.sessionId, msg);
|
|
2317
2380
|
// DES-TEAMING-002 T3: a plan on an engine without the plan approval gate is an "upgrade the
|
|
2318
2381
|
// engine" 501 — never a free-text run launched in its place.
|
|
2319
2382
|
if (err instanceof PlanLaunchUnsupportedError) {
|
|
@@ -2451,6 +2514,60 @@ runtime = {}) {
|
|
|
2451
2514
|
return reply.code(500).send({ error: msg });
|
|
2452
2515
|
}
|
|
2453
2516
|
});
|
|
2517
|
+
// ── Resolve an unresolved run (api-types 0.140.0) ─────────────────────────────
|
|
2518
|
+
// Unresolved work (`GET /system.unresolved[]`) is resolved, never hidden: archived WITH a reason,
|
|
2519
|
+
// or closed as superseded naming the run that replaced it (retry is `POST /runs {retryOf}`). A run
|
|
2520
|
+
// parked at a gate is stopped first (no worker is running), then archived; a run that is
|
|
2521
|
+
// executing is refused, so a resolve never ends live work. The note keeps the reason.
|
|
2522
|
+
const ResolveSchema = z.discriminatedUnion('action', [
|
|
2523
|
+
z.object({ action: z.literal('archive'), reason: z.string().trim().min(1, 'a reason is required to archive').max(480) }).strict(),
|
|
2524
|
+
z.object({ action: z.literal('superseded'), by: z.string().min(1), reason: z.string().trim().max(400).optional() }).strict(),
|
|
2525
|
+
]);
|
|
2526
|
+
app.post(`${V}/runs/:id/resolve`, { config: { manifest: { requestType: 'ResolveRunRequest', responseType: 'ResolveRunResponse', statusCodes: [200, 400, 404, 409, 500] } } }, async (req, reply) => {
|
|
2527
|
+
const { id } = req.params;
|
|
2528
|
+
const parsed = ResolveSchema.safeParse(req.body ?? {});
|
|
2529
|
+
if (!parsed.success)
|
|
2530
|
+
return reply.code(400).send(invalidBody(parsed.error, 'Invalid request body'));
|
|
2531
|
+
const b = parsed.data;
|
|
2532
|
+
const note = b.action === 'archive' ? `resolved: ${b.reason}` : `superseded by ${b.by}${b.reason !== undefined && b.reason !== '' ? `: ${b.reason}` : ''}`;
|
|
2533
|
+
// Inside the run's decision lock (the gate path's): the status read, the stop and the archive
|
|
2534
|
+
// are one step, so a gate answered at the same moment can never be stopped by a resolve. The
|
|
2535
|
+
// status is read again inside the lock, right before the stop.
|
|
2536
|
+
const outcome = await decideExclusively(id, async () => {
|
|
2537
|
+
const views = await adapter.sessionsDetail();
|
|
2538
|
+
const run = views.find((v) => v.session.id === id);
|
|
2539
|
+
if (run === undefined)
|
|
2540
|
+
return { code: 404, body: { error: 'Run not found' } };
|
|
2541
|
+
if (b.action === 'superseded') {
|
|
2542
|
+
if (b.by === id)
|
|
2543
|
+
return { code: 400, body: { error: '`by` must name another run' } };
|
|
2544
|
+
if (!views.some((v) => v.session.id === b.by))
|
|
2545
|
+
return { code: 400, body: { error: `\`by\` names an unknown run: ${b.by}` } };
|
|
2546
|
+
}
|
|
2547
|
+
const status = run.session.status;
|
|
2548
|
+
if (status !== 'awaiting_human' && status !== 'failed' && status !== 'completed' && status !== 'cancelled') {
|
|
2549
|
+
return { code: 409, body: { error: `run ${id} is ${status}: a running job cannot be resolved; stop it first` } };
|
|
2550
|
+
}
|
|
2551
|
+
let cancelled = false;
|
|
2552
|
+
try {
|
|
2553
|
+
if (status === 'awaiting_human') {
|
|
2554
|
+
await adapter.cancelRun(id);
|
|
2555
|
+
cancelled = true;
|
|
2556
|
+
}
|
|
2557
|
+
const found = await adapter.archiveRun(id, true, note);
|
|
2558
|
+
if (!found)
|
|
2559
|
+
return { code: 404, body: { error: 'Run not found' } };
|
|
2560
|
+
}
|
|
2561
|
+
catch (e) {
|
|
2562
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
2563
|
+
return { code: /only a terminal run/i.test(msg) ? 409 : 500, body: { error: msg } };
|
|
2564
|
+
}
|
|
2565
|
+
audit.record('run.resolved', actorOf(req), { runId: id, detail: { action: b.action, note, cancelled, ...(b.action === 'superseded' ? { by: b.by } : {}) } });
|
|
2566
|
+
const out = { runId: id, action: b.action, archived: true, cancelled, note };
|
|
2567
|
+
return { code: 200, body: out };
|
|
2568
|
+
});
|
|
2569
|
+
return reply.code(outcome.code).send(outcome.body);
|
|
2570
|
+
});
|
|
2454
2571
|
// Bulk write-off for campaign backlogs — explicit ids only, never implicit age selection.
|
|
2455
2572
|
const BulkArchiveSchema = z.object({
|
|
2456
2573
|
ids: z.array(z.string().min(1)).min(1).max(200),
|
|
@@ -2926,6 +3043,23 @@ runtime = {}) {
|
|
|
2926
3043
|
return reply.code(501).send({ error: message(err) });
|
|
2927
3044
|
}
|
|
2928
3045
|
}
|
|
3046
|
+
// crew#1157: a chat that runs a workflow runs a BUILT-IN read-only one, resolved before anything
|
|
3047
|
+
// is reserved or warmed (a 400 for a name chats do not run, a 409 for an engine without it).
|
|
3048
|
+
let workflow;
|
|
3049
|
+
if (b.workflow !== undefined) {
|
|
3050
|
+
let presets = [];
|
|
3051
|
+
try {
|
|
3052
|
+
presets = typeof adapter.listPresets === 'function' ? await adapter.listPresets() : [];
|
|
3053
|
+
}
|
|
3054
|
+
catch {
|
|
3055
|
+
presets = [];
|
|
3056
|
+
}
|
|
3057
|
+
const resolved = resolveChatWorkflow(b.workflow, presets);
|
|
3058
|
+
if (!resolved.ok)
|
|
3059
|
+
return reply.code(resolved.status).send({ error: resolved.error });
|
|
3060
|
+
workflow = resolved.workflow;
|
|
3061
|
+
}
|
|
3062
|
+
const statementOpts = { analysis: workflow !== undefined };
|
|
2929
3063
|
// A chat id this daemon already holds a scope for is LIVE: re-preparing its scratch root and
|
|
2930
3064
|
// re-opening it would overwrite the statement its seats are reading and (on a changed scope)
|
|
2931
3065
|
// evict them mid-conversation (Copilot, #518). Close it first, or let the daemon mint the id.
|
|
@@ -2973,7 +3107,7 @@ runtime = {}) {
|
|
|
2973
3107
|
if (!chatScopes.owns(chatId, token)) {
|
|
2974
3108
|
return reply.code(409).send({ error: `chat ${chatId} was closed while it was being opened; open it again` });
|
|
2975
3109
|
}
|
|
2976
|
-
prepareChatScratch(chatId, scope, inForceAtOpen);
|
|
3110
|
+
prepareChatScratch(chatId, scope, inForceAtOpen, statementOpts);
|
|
2977
3111
|
}
|
|
2978
3112
|
catch (err) {
|
|
2979
3113
|
// The helper removes only what it created itself (a pre-existing root is preserved) —
|
|
@@ -3044,6 +3178,12 @@ runtime = {}) {
|
|
|
3044
3178
|
refused,
|
|
3045
3179
|
});
|
|
3046
3180
|
}
|
|
3181
|
+
// crew#1157: a workflow chat is ONE seat — the operator's pick (`primary`), else the first
|
|
3182
|
+
// admitted seat in roster order. The others are not refused; they simply do not sit this chat.
|
|
3183
|
+
if (workflow !== undefined) {
|
|
3184
|
+
const one = b.primary !== undefined && clis.includes(b.primary) ? b.primary : clis[0];
|
|
3185
|
+
clis.splice(0, clis.length, one);
|
|
3186
|
+
}
|
|
3047
3187
|
try {
|
|
3048
3188
|
// DES-ASK-TEAM-CHAT-001 §5.1 (ASK-C1): an open resolves the scope and ELIGIBILITY and warms
|
|
3049
3189
|
// nothing — the chat is a path its first message launches, and the engine picks the PA
|
|
@@ -3111,7 +3251,7 @@ runtime = {}) {
|
|
|
3111
3251
|
askPaths.open(chatId, clis, b.primary, {
|
|
3112
3252
|
seats: standing.filter((s) => clis.includes(String(s.key))),
|
|
3113
3253
|
scopeKind: !scoped ? 'unscoped' : admissionKind === 'path-bound' ? 'scoped_bound' : 'scoped',
|
|
3114
|
-
});
|
|
3254
|
+
}, workflow);
|
|
3115
3255
|
// Register roots for path rewriting (crew#618): absolute host paths in seat replies are
|
|
3116
3256
|
// rewritten to repo-relative form before being stored in the transcript.
|
|
3117
3257
|
if (chatTranscripts !== undefined && scope.repos.length > 0) {
|
|
@@ -3154,6 +3294,7 @@ runtime = {}) {
|
|
|
3154
3294
|
seats,
|
|
3155
3295
|
scope,
|
|
3156
3296
|
refused,
|
|
3297
|
+
...(workflow !== undefined ? { workflow: workflow.name } : {}),
|
|
3157
3298
|
...(projectAttachError !== undefined ? { projectAttachError } : {}),
|
|
3158
3299
|
...(singleSeat !== undefined ? { singleSeat } : {}),
|
|
3159
3300
|
});
|
|
@@ -3378,7 +3519,7 @@ runtime = {}) {
|
|
|
3378
3519
|
}
|
|
3379
3520
|
if (storedScope !== undefined && scope !== undefined && engine !== undefined &&
|
|
3380
3521
|
(JSON.stringify(scope) !== JSON.stringify(storedScope) || JSON.stringify(engine) !== JSON.stringify(chatScopes.engineOf(id)))) {
|
|
3381
|
-
prepareChatScratch(id, scope, rules);
|
|
3522
|
+
prepareChatScratch(id, scope, rules, { analysis: path.workflow !== undefined });
|
|
3382
3523
|
if (chatScopes.refresh(id, storedScope, scope, engine)) {
|
|
3383
3524
|
chatTranscripts?.registerRoots(id, scope.repos.map((r) => ({ absRoot: resolve(r.rootPath), name: r.name })));
|
|
3384
3525
|
}
|
|
@@ -3404,8 +3545,11 @@ runtime = {}) {
|
|
|
3404
3545
|
audience = voice.length > 0 ? voice : path.eligible;
|
|
3405
3546
|
turn = chatTurns.begin(id, audience, text, ASK_TURN_BUDGET_SECS);
|
|
3406
3547
|
turnId = turn?.turnId ?? turnId;
|
|
3407
|
-
// A repo-bound path can deliver; project and multi-repo scopes remain read-only asks.
|
|
3548
|
+
// A repo-bound path can deliver; project and multi-repo scopes remain read-only asks. A workflow
|
|
3549
|
+
// chat (crew#1157) never delivers: it is bound to its repository read-only, with no deliver.
|
|
3408
3550
|
const repoRef = scope?.kind === 'repos' && scope.repos.length === 1 ? scope.repos[0].id : undefined;
|
|
3551
|
+
const delivers = repoRef !== undefined && path.workflow === undefined;
|
|
3552
|
+
const statementOpts = { analysis: path.workflow !== undefined };
|
|
3409
3553
|
const projectId = scope?.projectId;
|
|
3410
3554
|
if (path.runId === undefined) {
|
|
3411
3555
|
const stepId = 'answer-1';
|
|
@@ -3414,14 +3558,18 @@ runtime = {}) {
|
|
|
3414
3558
|
// crew#823: the run is titled by the operator's question (every reader titles a run from
|
|
3415
3559
|
// `problem`); the scope statement grounds the seats from the first answer step's
|
|
3416
3560
|
// instructions instead of standing in as the run's title.
|
|
3417
|
-
const instructions = scope !== undefined ? `${chatScopeStatement(id, scope, rules)}\n\n${withPreface(pending.preface)}` : withPreface(pending.preface);
|
|
3561
|
+
const instructions = scope !== undefined ? `${chatScopeStatement(id, scope, rules, statementOpts)}\n\n${withPreface(pending.preface)}` : withPreface(pending.preface);
|
|
3418
3562
|
const input = {
|
|
3419
3563
|
problem: text,
|
|
3420
3564
|
sessionId: randomUUID(),
|
|
3421
3565
|
clisJson: JSON.stringify(roster),
|
|
3422
|
-
|
|
3566
|
+
// A workflow chat is one seat and asks for no reviewer (crew#1157).
|
|
3567
|
+
plan: {
|
|
3568
|
+
steps: [chatTurnStep(stepId, instructions, path.workflow)],
|
|
3569
|
+
...(path.workflow === undefined ? { monitors: { asked: 1 } } : {}),
|
|
3570
|
+
},
|
|
3423
3571
|
...(path.primary !== undefined ? { primary: path.primary } : {}),
|
|
3424
|
-
...(repoRef !== undefined ? { repoRef, deliver: 'pr' } : {}),
|
|
3572
|
+
...(repoRef !== undefined ? (delivers ? { repoRef, deliver: 'pr' } : { repoRef }) : {}),
|
|
3425
3573
|
...(projectId !== undefined ? { projectId } : {}),
|
|
3426
3574
|
...(scope?.kind === 'project' && codeGraphDb !== null ? { projectGraph: { dbPath: codeGraphDb } } : {}),
|
|
3427
3575
|
// crew#1034: the scope's repositories are the run's read roots, graph or no graph — the
|
|
@@ -3456,7 +3604,8 @@ runtime = {}) {
|
|
|
3456
3604
|
askPath: true,
|
|
3457
3605
|
...(repoRef !== undefined ? { repoRef } : {}),
|
|
3458
3606
|
...(projectId !== undefined ? { projectId } : {}),
|
|
3459
|
-
|
|
3607
|
+
...(path.workflow !== undefined ? { chatWorkflow: path.workflow.name } : {}),
|
|
3608
|
+
deliver: delivers ? 'pr' : 'none',
|
|
3460
3609
|
});
|
|
3461
3610
|
});
|
|
3462
3611
|
return reply.code(202).send({
|
|
@@ -3467,7 +3616,7 @@ runtime = {}) {
|
|
|
3467
3616
|
// crew#1033: EVERY answer step carries the scope statement, as the first one does. A seat on a
|
|
3468
3617
|
// single-shot carrier (codex on the wrapped carrier) remembers nothing of the first turn, so a
|
|
3469
3618
|
// follow-up told only the question answered "the supplied repository contains only tmp".
|
|
3470
|
-
const instructions = scope !== undefined ? `${chatScopeStatement(id, scope, rules)}\n\n${withPreface(pending.preface)}` : withPreface(pending.preface);
|
|
3619
|
+
const instructions = scope !== undefined ? `${chatScopeStatement(id, scope, rules, statementOpts)}\n\n${withPreface(pending.preface)}` : withPreface(pending.preface);
|
|
3471
3620
|
// The continuation answers the path run's gate: under the per-run decision lock it re-reads the
|
|
3472
3621
|
// run and is judged by the record before it proposes or confirms anything (crew#1083 round 7).
|
|
3473
3622
|
// The status read above is a pre-check that changes nothing; this read is the one that counts.
|
|
@@ -3485,7 +3634,7 @@ runtime = {}) {
|
|
|
3485
3634
|
const decided = await gateForDecision(view, undefined);
|
|
3486
3635
|
if ('refusal' in decided)
|
|
3487
3636
|
return decided.refusal;
|
|
3488
|
-
await adapter.proposePlan(pathRunId, { steps: [
|
|
3637
|
+
await adapter.proposePlan(pathRunId, { steps: [chatTurnStep(stepId, instructions, path.workflow)] }, turnId);
|
|
3489
3638
|
await adapter.confirmGate(pathRunId, true);
|
|
3490
3639
|
return null;
|
|
3491
3640
|
});
|
|
@@ -3557,6 +3706,15 @@ runtime = {}) {
|
|
|
3557
3706
|
runId: launched.runId,
|
|
3558
3707
|
});
|
|
3559
3708
|
}
|
|
3709
|
+
// crew#1157 (xreview on #1160): an analysis chat seats ONE helper; a re-seat may re-warm that
|
|
3710
|
+
// seat, never add another.
|
|
3711
|
+
if (launched?.workflow !== undefined && !(clis.length === 1 && launched.eligible.includes(clis[0]))) {
|
|
3712
|
+
return reply.code(409).send({
|
|
3713
|
+
code: 'analysis_one_seat',
|
|
3714
|
+
error: `chat '${id}' is an analysis (${launched.workflow}) and seats one helper (${launched.eligible.join(', ')}); open a new chat to ask another`,
|
|
3715
|
+
chatId: id,
|
|
3716
|
+
});
|
|
3717
|
+
}
|
|
3560
3718
|
if (launched?.busy === true) {
|
|
3561
3719
|
// The first message is launching with the roster as it stands (codex on #808 r3, 4).
|
|
3562
3720
|
return reply.code(409).send({
|
|
@@ -4191,7 +4349,19 @@ runtime = {}) {
|
|
|
4191
4349
|
* A decision without one keeps the pre-table resolution (the cache, then the log, then the
|
|
4192
4350
|
* cursor unit) — an engine, or a run, older than the table.
|
|
4193
4351
|
*/
|
|
4194
|
-
async function gateForDecision(run, namedOrd) {
|
|
4352
|
+
async function gateForDecision(run, namedOrd, opts = {}) {
|
|
4353
|
+
const decided = await gateOfRecord(run, namedOrd);
|
|
4354
|
+
// crew#1157: every APPROVING path (the gate route, standing orders, a gated resume or reassign, a
|
|
4355
|
+
// plan edit at the gate, the next message of a chat) is judged here, so no path approves a held
|
|
4356
|
+
// plan change on an analysis run. Declining stays open.
|
|
4357
|
+
if (!('refusal' in decided) && opts.declining !== true) {
|
|
4358
|
+
const analysis = analysisPlanRefusal(run.session.id, { gateKind: decided.gateKind });
|
|
4359
|
+
if (analysis !== null)
|
|
4360
|
+
return { refusal: analysis };
|
|
4361
|
+
}
|
|
4362
|
+
return decided;
|
|
4363
|
+
}
|
|
4364
|
+
async function gateOfRecord(run, namedOrd) {
|
|
4195
4365
|
const id = run.session.id;
|
|
4196
4366
|
const unitAt = (ord) => (run.units ?? []).find((u) => u.ord === ord);
|
|
4197
4367
|
const unknown = (why) => ({ refusal: { code: 409, body: { error: `Gate unknown: ${why} — refresh and decide again.`, code: 'gate_unknown' } } });
|
|
@@ -4275,6 +4445,30 @@ runtime = {}) {
|
|
|
4275
4445
|
});
|
|
4276
4446
|
return result;
|
|
4277
4447
|
}
|
|
4448
|
+
/**
|
|
4449
|
+
* crew#1157 (xreview on #1160): an ANALYSIS chat's run never takes on work. Its seat is told not to
|
|
4450
|
+
* propose any, but a `PLAN+` line in an answer would still hold a creator addition at a
|
|
4451
|
+
* `plan_approval` gate; approving it (or approving with an edited plan) would let the run write.
|
|
4452
|
+
* Refused here, for every decision path, by the durable launch record: declining stays allowed.
|
|
4453
|
+
*/
|
|
4454
|
+
function analysisPlanRefusal(runId, held) {
|
|
4455
|
+
const workflow = runTimingIndex.chatWorkflowOf(runId);
|
|
4456
|
+
if (workflow === undefined)
|
|
4457
|
+
return null;
|
|
4458
|
+
// A held plan change (the gate the engine opens on a `PLAN+` line), or a plan edit with any step
|
|
4459
|
+
// that could write (the same read-only predicate the chat open applies to the workflow).
|
|
4460
|
+
const writes = held.plan?.steps.some((s) => writeCapableStep(s) !== null) === true;
|
|
4461
|
+
if (held.gateKind !== 'plan_approval' && !writes)
|
|
4462
|
+
return null;
|
|
4463
|
+
return {
|
|
4464
|
+
code: 409,
|
|
4465
|
+
body: {
|
|
4466
|
+
code: 'analysis_read_only',
|
|
4467
|
+
error: `run ${runId} is an analysis (${workflow}): it answers questions and changes nothing, so it cannot take on ` +
|
|
4468
|
+
'the work its answer proposed. Decline it here, and start a job for the change.',
|
|
4469
|
+
},
|
|
4470
|
+
};
|
|
4471
|
+
}
|
|
4278
4472
|
/**
|
|
4279
4473
|
* THE gate decision path. `POST /runs/:id/gate` answers with it, and so does a standing order
|
|
4280
4474
|
* (behaviour 10) — the same awaiting check, the same ord check and 409 `gate_changed` /
|
|
@@ -4298,9 +4492,12 @@ runtime = {}) {
|
|
|
4298
4492
|
// Decision integrity (api-types 0.44.0; crew#1083 rounds 3–5): the gate this decision answers
|
|
4299
4493
|
// is the RECORD's — read first thing inside the lock, so the gated unit and its delivery
|
|
4300
4494
|
// checks below are the ones the record confirms ({@link gateForDecision}).
|
|
4301
|
-
const decided = await gateForDecision(run, parsed.data.ord);
|
|
4495
|
+
const decided = await gateForDecision(run, parsed.data.ord, { declining: !parsed.data.approve });
|
|
4302
4496
|
if ('refusal' in decided)
|
|
4303
4497
|
return decided.refusal;
|
|
4498
|
+
const analysis = parsed.data.approve ? analysisPlanRefusal(id, { plan: parsed.data.plan }) : null;
|
|
4499
|
+
if (analysis !== null)
|
|
4500
|
+
return analysis;
|
|
4304
4501
|
// The gated unit and its receipt as it stands when the person decides (wicked-core#850: every
|
|
4305
4502
|
// gate answer carries it). An APPROVE that would run the deliver unit passes the delivery
|
|
4306
4503
|
// checks first.
|
|
@@ -7061,7 +7258,12 @@ runtime = {}) {
|
|
|
7061
7258
|
registerPresetRoutes(app, adapter, security);
|
|
7062
7259
|
// ── Team (DES-TEAMING-002 T8) — reads and commands over the engine; the live stream is the
|
|
7063
7260
|
// teamEvent relay (team/ws-relay.ts). Crew publishes no team fact (§4.0).
|
|
7064
|
-
registerTeamRoutes(app, adapter, security, {
|
|
7261
|
+
registerTeamRoutes(app, adapter, security, {
|
|
7262
|
+
exclusive: decideExclusively,
|
|
7263
|
+
judge: gateForDecision,
|
|
7264
|
+
// crew#1157: a plan edit on an analysis run may not add a step that could write.
|
|
7265
|
+
readOnlyRefusal: (runId, plan) => analysisPlanRefusal(runId, { plan }),
|
|
7266
|
+
});
|
|
7065
7267
|
// ── Campaigns (crew#342 + TH-9) — the engine's durable Run-DAG scheduler over REST ──────────
|
|
7066
7268
|
// Progress streams as campaign* CoreEvents on the existing allowlist-free /ws relay; these
|
|
7067
7269
|
// routes are launch/resume/cancel + the reads a scoreboard builds from. The GET routes join
|
|
@@ -7124,6 +7326,19 @@ runtime = {}) {
|
|
|
7124
7326
|
// directly-driven route set records nothing unless it injects one (no `~/.wicked-crew` writes).
|
|
7125
7327
|
...(runtime.evalStore !== undefined ? { evalStore: runtime.evalStore } : {}),
|
|
7126
7328
|
});
|
|
7329
|
+
// ── Default evals (DES-DEFAULT-EVALS-001) — the system's own judgments, scored against what happened ─────
|
|
7330
|
+
// Read-only over the run store (sessions, events, the operator's decisions, QE ledgers, the latest steering
|
|
7331
|
+
// eval); the only write is the report, under the eval store's root.
|
|
7332
|
+
const defaultEvalDeps = {
|
|
7333
|
+
...(runtime.defaultEvalStore !== undefined ? { store: runtime.defaultEvalStore } : {}),
|
|
7334
|
+
// Served runs, as GET /runs serves them: the derived title and the project membership join (codex r1).
|
|
7335
|
+
runs: async () => (await adapter.sessionsDetail()).map(decorateRun),
|
|
7336
|
+
events: async (id) => (typeof adapter.runEvents === 'function' ? (await adapter.runEvents(id)) : null),
|
|
7337
|
+
decisions: () => (runtime.decisionLedger?.list({}) ?? []),
|
|
7338
|
+
...(runtime.evalStore !== undefined ? { steeringEvals: runtime.evalStore } : {}),
|
|
7339
|
+
replay: defaultReplaySet({ gardenRoot: gardenRootFor(process.env) }),
|
|
7340
|
+
};
|
|
7341
|
+
registerDefaultEvalRoutes(app, defaultEvalDeps);
|
|
7127
7342
|
// ── Skills (skills keystone) — the file manager over the daemon-owned garden plugin root ─────
|
|
7128
7343
|
// Manifest + typed reads, guarded CAS writes, refresh/publish/analyze. `createServer` injects the
|
|
7129
7344
|
// runtime it booted (store seeded from the installed plugin, snapshot published); a
|
|
@@ -7182,6 +7397,8 @@ runtime = {}) {
|
|
|
7182
7397
|
// store is kept for SYSTEM_STATS_TTL_MS, with its `asOf`. The whole answer is held to
|
|
7183
7398
|
// SYSTEM_BUDGET_MS: a part that does not settle in time reads `unknown` (or `null`) with why.
|
|
7184
7399
|
const weekFoldCache = new WeekFoldCache();
|
|
7400
|
+
const runCosts = new RunCostCache();
|
|
7401
|
+
let toReviewCache = null;
|
|
7185
7402
|
/** One stat of a run's durable event log: read before the 7-day fold, so the fold can never eat
|
|
7186
7403
|
* the time a liveness verdict needs. */
|
|
7187
7404
|
const EVIDENCE_TIMEOUT_MS = 1_000;
|
|
@@ -7217,6 +7434,17 @@ runtime = {}) {
|
|
|
7217
7434
|
const rules = await adapter.listConformanceRules();
|
|
7218
7435
|
return rules.filter((r) => r.retired !== true).length;
|
|
7219
7436
|
}, PROBE_TIMEOUT_MS);
|
|
7437
|
+
// Pending proposals: "N to review", never in the needs-you number (no run waits on them).
|
|
7438
|
+
// Kept for SYSTEM_STATS_TTL_MS: a skin polls /system, and each read would spawn the estate client.
|
|
7439
|
+
const toReviewP = settleWithin(async () => {
|
|
7440
|
+
if (toReviewCache !== null && Date.now() - toReviewCache.at < SYSTEM_STATS_TTL_MS)
|
|
7441
|
+
return toReviewCache.value;
|
|
7442
|
+
const got = (await estateTool('proposal.list', { state: 'pending' }));
|
|
7443
|
+
const value = Array.isArray(got?.proposals) ? got.proposals.length : null;
|
|
7444
|
+
if (value !== null)
|
|
7445
|
+
toReviewCache = { at: Date.now(), value }; // a failed read is never kept
|
|
7446
|
+
return value;
|
|
7447
|
+
}, PROBE_TIMEOUT_MS);
|
|
7220
7448
|
const gatesByYouP = settleWithin(async () => {
|
|
7221
7449
|
const since = Date.now() - SYSTEM_STATS_DAYS * 86_400_000;
|
|
7222
7450
|
const entries = await audit.readAll({ action: 'gate.decided', since });
|
|
@@ -7275,7 +7503,14 @@ runtime = {}) {
|
|
|
7275
7503
|
if (openGates.kind === 'ok' && openGates.value !== null) {
|
|
7276
7504
|
const at = (r) => new Date(r.created_at).getTime() || 0;
|
|
7277
7505
|
for (const r of [...openGates.value].sort((a, b) => at(a) - at(b))) {
|
|
7278
|
-
|
|
7506
|
+
const arrived = at(r);
|
|
7507
|
+
openByRun.set(r.session_id, {
|
|
7508
|
+
interactionId: r.id,
|
|
7509
|
+
ord: r.ord,
|
|
7510
|
+
kind: r.gate_kind ?? (r.kind === 'elicitation' ? 'question' : null),
|
|
7511
|
+
prompt: r.prompt.slice(0, 280),
|
|
7512
|
+
receivedAt: arrived > 0 ? new Date(arrived).toISOString() : (gateCache.get(r.session_id)?.receivedAt ?? null),
|
|
7513
|
+
});
|
|
7279
7514
|
}
|
|
7280
7515
|
}
|
|
7281
7516
|
const now = Date.now();
|
|
@@ -7296,7 +7531,7 @@ runtime = {}) {
|
|
|
7296
7531
|
const cached = gateCache.get(view.session.id);
|
|
7297
7532
|
const gate = openByRun.get(view.session.id) ??
|
|
7298
7533
|
(view.session.status === 'awaiting_human' && cached !== undefined
|
|
7299
|
-
? { interactionId: null, ord: cached.ord, kind: cached.gateKind ?? null, prompt: cached.prompt.slice(0, 280) }
|
|
7534
|
+
? { interactionId: null, ord: cached.ord, kind: cached.gateKind ?? null, prompt: cached.prompt.slice(0, 280), receivedAt: cached.receivedAt }
|
|
7300
7535
|
: null);
|
|
7301
7536
|
const run = runLiveness({ view, now, evidence: await evidenceOf(view.session.id), gate: view.session.status === 'awaiting_human' ? gate : null, quietAfterMs, stallAfterMs });
|
|
7302
7537
|
return { view, run };
|
|
@@ -7344,7 +7579,7 @@ runtime = {}) {
|
|
|
7344
7579
|
const daemonDetails = {
|
|
7345
7580
|
version: PKG_VERSION,
|
|
7346
7581
|
ping: pingWord,
|
|
7347
|
-
warnings: healthWarningsOf(runtime.stateHome !== undefined ? (d?.stateHome ?? null) : null, runtime.skills?.
|
|
7582
|
+
warnings: healthWarningsOf(runtime.stateHome !== undefined ? (d?.stateHome ?? null) : null, runtime.skills?.reportedBaseSkill() ?? null),
|
|
7348
7583
|
...(d !== null ? { components: d.components, daemon: d.daemon, recentErrors: d.recentErrors } : {}),
|
|
7349
7584
|
};
|
|
7350
7585
|
const daemonRow = views.kind !== 'ok'
|
|
@@ -7393,16 +7628,50 @@ runtime = {}) {
|
|
|
7393
7628
|
steeringRules: rules.kind === 'ok' ? rules.value : null,
|
|
7394
7629
|
...(rules.kind === 'ok' && rules.value === null ? { steeringNote: 'this engine cannot list steering rules' } : rules.kind === 'timeout' ? { steeringNote: `the rule store did not answer within ${PROBE_TIMEOUT_MS / 1000} s` } : rules.kind === 'error' ? { steeringNote: `the rule store could not be read: ${rules.error}` } : {}),
|
|
7395
7630
|
});
|
|
7396
|
-
|
|
7397
|
-
|
|
7631
|
+
// The one "needs you" rule (needs.ts): root causes ranked by impact, plus the unresolved work
|
|
7632
|
+
// and what it cost. Per-run cost is read from the run's own log, cached, never waited on past
|
|
7633
|
+
// the budget (an unread run's cost reads unknown and the next read has it).
|
|
7634
|
+
const first = buildNeeds({
|
|
7635
|
+
now,
|
|
7636
|
+
views: allViews,
|
|
7637
|
+
runs,
|
|
7638
|
+
infra,
|
|
7639
|
+
clis,
|
|
7640
|
+
gaps,
|
|
7641
|
+
infraHoldsLive: (key) => (key === 'gateway' || key === 'broker' ? containerFloor : key === 'skills' || key === 'state_home' || key === 'disk' || key === 'daemon'),
|
|
7642
|
+
costOf: (id) => runCosts.get(id),
|
|
7643
|
+
});
|
|
7644
|
+
if (typeof adapter.runEvents === 'function' && first.unresolved.length > 0) {
|
|
7645
|
+
const byId = new Map(allViews.map((v) => [v.session.id, v]));
|
|
7646
|
+
await settleWithin(() => runCosts.fill(first.unresolved.map((u) => {
|
|
7647
|
+
const s = byId.get(u.runId)?.session;
|
|
7648
|
+
return { id: u.runId, final: ['failed', 'completed', 'cancelled'].includes(s?.status ?? ''), stamp: `${s?.status ?? ''}:${s?.ended_at ?? ''}` };
|
|
7649
|
+
}), (id) => adapter.runEvents(id)), Math.min(left(), 2_000));
|
|
7650
|
+
}
|
|
7651
|
+
const needs = buildNeeds({
|
|
7652
|
+
now,
|
|
7653
|
+
views: allViews,
|
|
7654
|
+
runs,
|
|
7398
7655
|
infra,
|
|
7399
7656
|
clis,
|
|
7657
|
+
gaps,
|
|
7658
|
+
infraHoldsLive: (key) => (key === 'gateway' || key === 'broker' ? containerFloor : key === 'skills' || key === 'state_home' || key === 'disk' || key === 'daemon'),
|
|
7659
|
+
costOf: (id) => runCosts.get(id),
|
|
7660
|
+
});
|
|
7661
|
+
const toReview = await toReviewP;
|
|
7662
|
+
// A run list that could not be read never reads green: the daemon row (degraded, "did not list
|
|
7663
|
+
// its runs") is in attention as a slowdown, and the level stays at least yellow.
|
|
7664
|
+
return assembleSnapshot({
|
|
7665
|
+
startedAt,
|
|
7666
|
+
clis,
|
|
7400
7667
|
runs,
|
|
7668
|
+
infra,
|
|
7401
7669
|
governance,
|
|
7402
|
-
|
|
7403
|
-
|
|
7670
|
+
attention: needs.attention,
|
|
7671
|
+
unresolved: needs.unresolved,
|
|
7672
|
+
unresolvedSpend: needs.unresolvedSpend,
|
|
7673
|
+
toReview: toReview.kind === 'ok' ? toReview.value : null,
|
|
7404
7674
|
});
|
|
7405
|
-
return assembleSnapshot({ startedAt, clis, runs, infra, governance, attention });
|
|
7406
7675
|
});
|
|
7407
7676
|
registerInteractiveProxy(app, adapter, {
|
|
7408
7677
|
settings: projectSettings,
|
|
@@ -7501,6 +7770,9 @@ runtime = {}) {
|
|
|
7501
7770
|
log: (m) => app.log.warn(m),
|
|
7502
7771
|
});
|
|
7503
7772
|
registerSearchRoutes(app, search);
|
|
7504
|
-
|
|
7773
|
+
const runEvalsPrepare = async (input) => {
|
|
7774
|
+
await prepareRunEvalsLaunch(input, () => runAndRecord(defaultEvalDeps, { project: null, baseline: false, id: randomUUID(), now: new Date() }), runtime.evalRunsDir);
|
|
7775
|
+
};
|
|
7776
|
+
return { decideGate, decisions, chatRecorder, considerations, search, decorateRun, runEvalsPrepare };
|
|
7505
7777
|
}
|
|
7506
7778
|
//# sourceMappingURL=routes.js.map
|