wicked-crew 0.16.3 → 0.16.5
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-mcp.d.ts +42 -0
- package/dist/api/chat-mcp.d.ts.map +1 -0
- package/dist/api/chat-mcp.js +93 -0
- package/dist/api/chat-mcp.js.map +1 -0
- 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/delivery-assurance.d.ts +3 -0
- package/dist/api/delivery-assurance.d.ts.map +1 -1
- package/dist/api/delivery-assurance.js +11 -0
- package/dist/api/delivery-assurance.js.map +1 -1
- 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/mcp.d.ts +5 -5
- package/dist/api/mcp.js +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/project-aggregates.d.ts +2 -2
- package/dist/api/routes.d.ts +114 -3
- package/dist/api/routes.d.ts.map +1 -1
- package/dist/api/routes.js +327 -31
- package/dist/api/routes.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 +15 -1
- package/dist/api/server.d.ts.map +1 -1
- package/dist/api/server.js +147 -2
- 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/editors/conformance-runtime.d.ts +55 -0
- package/dist/editors/conformance-runtime.d.ts.map +1 -0
- package/dist/editors/conformance-runtime.js +138 -0
- package/dist/editors/conformance-runtime.js.map +1 -0
- package/dist/editors/pack-install.d.ts +6 -2
- package/dist/editors/pack-install.d.ts.map +1 -1
- package/dist/editors/pack-install.js +63 -45
- package/dist/editors/pack-install.js.map +1 -1
- package/dist/editors/registry.d.ts +10 -10
- 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/mcp/probe.d.ts +18 -3
- package/dist/mcp/probe.d.ts.map +1 -1
- package/dist/mcp/probe.js +61 -9
- package/dist/mcp/probe.js.map +1 -1
- package/dist/projects/state-home-preflight.d.ts.map +1 -1
- package/dist/projects/state-home-preflight.js +8 -4
- package/dist/projects/state-home-preflight.js.map +1 -1
- package/dist/projects/state-home-registry.d.ts +12 -1
- package/dist/projects/state-home-registry.d.ts.map +1 -1
- package/dist/projects/state-home-registry.js +24 -2
- package/dist/projects/state-home-registry.js.map +1 -1
- package/dist/qe/acceptance.d.ts.map +1 -1
- package/dist/qe/acceptance.js +10 -2
- package/dist/qe/acceptance.js.map +1 -1
- package/dist/qe/ledger.d.ts +20 -0
- package/dist/qe/ledger.d.ts.map +1 -1
- package/dist/qe/ledger.js +83 -3
- 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/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,10 +59,15 @@ 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';
|
|
63
69
|
import { registerMcpRoutes } from './mcp.js';
|
|
70
|
+
import { chatMcpToolsFor } from './chat-mcp.js';
|
|
64
71
|
import { McpPolicies } from '../mcp/policies.js';
|
|
65
72
|
import { disabledSkillsHealth } from '../skills/runtime.js';
|
|
66
73
|
import { withPhaseSkillGaps } from '../skills/phase-skill-gaps.js';
|
|
@@ -70,7 +77,7 @@ import { REQUIRED_GARDEN_VERSION } from '../skills/plugin-source.js';
|
|
|
70
77
|
import { engineRosterJson, noEligibleSeatBody, parseNoEligibleSeat, toEngineSeat } from '../core/engine-roster.js';
|
|
71
78
|
import { launchAssuranceNotice } from '../core/assurance.js';
|
|
72
79
|
import { launchRequiresQe, mayRequireQe, qeAcceptanceMode, qeSettingSkipReason } from '../core/qe-acceptance-setting.js';
|
|
73
|
-
import { aggregateReceipt, postHocDeliveryAssurance, qeAcceptanceFromView, qeAcceptanceRefusal, QE_ACCEPTANCE_REQUIRED_CODE, receiptOf, qeAcceptanceNotRequired, qeDecisionBypasses, qeDecisionOf, unreadableQeAcceptance, } from './delivery-assurance.js';
|
|
80
|
+
import { aggregateReceipt, postHocDeliveryAssurance, qeAcceptanceFromView, qeAcceptanceRefusal, QE_ACCEPTANCE_REQUIRED_CODE, receiptOf, qeAcceptanceNotRequired, qeDecisionBypasses, qeDecisionOf, qeAcceptanceSkippedAtGate, unreadableQeAcceptance, } from './delivery-assurance.js';
|
|
74
81
|
import { ProjectSettingsStore } from '../projects/settings.js';
|
|
75
82
|
import { isHumanOperator } from './watch-routes.js';
|
|
76
83
|
import { boundOrigin, InteractiveBridgePool } from '../interactive/bridge-pool.js';
|
|
@@ -93,7 +100,8 @@ import { AcpFoldCache, EngineVersionCache, eventsDirOf, installedPackageVersion,
|
|
|
93
100
|
import { GovernanceDiagnostics } from './governance-health.js';
|
|
94
101
|
import { ATTACHMENT_LIMITS, ATTACHMENT_MAX_MESSAGE_BYTES, AttachmentError, ChatAttachmentStore, attachmentWire, attachmentsBlock, boundaryOf, parseMultipart, stageForSeat } from './chat-attachments.js';
|
|
95
102
|
import { CHAT_HISTORY_MAX_LIMIT, ChatHistoryStore, decodeCursor } from './chat-history.js';
|
|
96
|
-
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';
|
|
97
105
|
import { governanceStoreFix, legacyHomeOutboxPath } from '../core/governance-store.js';
|
|
98
106
|
import { UsageError, replayOutboxInto } from '../cli/governance.js';
|
|
99
107
|
import { foldDeadletters } from './governance-health.js';
|
|
@@ -119,6 +127,7 @@ import { registerDecisionRoutes } from '../decisions/routes.js';
|
|
|
119
127
|
import { ChatDecisionRecorder } from '../decisions/chat-recorder.js';
|
|
120
128
|
import { ConsiderationService } from '../decisions/consider.js';
|
|
121
129
|
import { gardenConformance, gardenPackCheck } from '../editors/pack-install.js';
|
|
130
|
+
import { conformanceProvisioner, conformanceRuntimeRoot } from '../editors/conformance-runtime.js';
|
|
122
131
|
import { EditorRegistry } from '../editors/registry.js';
|
|
123
132
|
import { registerEditorRoutes } from '../editors/routes.js';
|
|
124
133
|
import { registerMcpInspectRoutes } from '../mcp-inspect/routes.js';
|
|
@@ -549,12 +558,21 @@ const isEscalationAction = (a) => a !== undefined && (ESCALATION_ACTIONS.include
|
|
|
549
558
|
* run and the delivery too) and refuses it (409, gate left open) at any other gate and while any
|
|
550
559
|
* other layer of the gate also denied. QE acceptance is never waived by it. */
|
|
551
560
|
export const OPERATOR_JUDGMENT_ACTION = 'operator_judgment';
|
|
561
|
+
/** (j4-gates, api-types 0.139.0) SKIP QE ACCEPTANCE at a gate — the launch's `skipQeAcceptance`,
|
|
562
|
+
* given later: `approve: true`, the written reason in `amend` (required, non-empty), no
|
|
563
|
+
* `amendScope` or `plan`. The engine (wicked-core `HumanDecision::SkipQe`) records the run's QE
|
|
564
|
+
* decision as `skipped` by the operator ("QE acceptance skipped by operator: <reason>"), labelled on
|
|
565
|
+
* the run, every later gate and the delivery, and answers: the gate before the deliver step (this
|
|
566
|
+
* route lets the delivery go without a QE PASS, saying so), the escalation gate of a unit only QE
|
|
567
|
+
* denied (it counts), or a failed floor's gate with QE behind it (it re-opens on the floor alone).
|
|
568
|
+
* Refused (409, gate left open) everywhere else and on a run that does not require QE. */
|
|
569
|
+
export const SKIP_QE_ACTION = 'skip_qe';
|
|
552
570
|
export const GateSchema = z.object({
|
|
553
571
|
approve: z.boolean(),
|
|
554
572
|
amend: z.string().optional(),
|
|
555
573
|
action: z
|
|
556
574
|
.union([
|
|
557
|
-
z.enum(['approve', 'request_changes', 'reject', 'edit_plan', 'amend_intent', OPERATOR_JUDGMENT_ACTION, ...ESCALATION_ACTIONS]),
|
|
575
|
+
z.enum(['approve', 'request_changes', 'reject', 'edit_plan', 'amend_intent', OPERATOR_JUDGMENT_ACTION, SKIP_QE_ACTION, ...ESCALATION_ACTIONS]),
|
|
558
576
|
z.string().regex(CONSENT_CHOICE_ACTION, 'a consent choice is `consent:<choice id>`'),
|
|
559
577
|
])
|
|
560
578
|
.optional(),
|
|
@@ -565,10 +583,12 @@ export const GateSchema = z.object({
|
|
|
565
583
|
* open gate is a 409 `gate_changed`: the decision was made on a gate that is no longer open. */
|
|
566
584
|
ord: z.number().int().nonnegative().optional(),
|
|
567
585
|
}).strict().refine((b) => b.action === undefined
|
|
568
|
-
|| (b.action === 'approve' || b.action === 'edit_plan' || b.action === 'amend_intent' || b.action === OPERATOR_JUDGMENT_ACTION || isEscalationAction(b.action)) === b.approve, { message: '`action` disagrees with `approve`: request_changes and reject require approve: false; approve, edit_plan, amend_intent, operator_judgment, extend, targeted, accept_partial, accept_suggestion, rejudge and consent:<id> require approve: true', path: ['action'] }).refine(
|
|
586
|
+
|| (b.action === 'approve' || b.action === 'edit_plan' || b.action === 'amend_intent' || b.action === OPERATOR_JUDGMENT_ACTION || b.action === SKIP_QE_ACTION || isEscalationAction(b.action)) === b.approve, { message: '`action` disagrees with `approve`: request_changes and reject require approve: false; approve, edit_plan, amend_intent, operator_judgment, skip_qe, extend, targeted, accept_partial, accept_suggestion, rejudge and consent:<id> require approve: true', path: ['action'] }).refine(
|
|
569
587
|
// (operator ruling 2026-10-21) The override's rationale IS its record: a blank one is a 400 here,
|
|
570
588
|
// before the engine is asked.
|
|
571
589
|
(b) => b.action !== OPERATOR_JUDGMENT_ACTION || (b.amend !== undefined && b.amend.trim() !== ''), { message: 'operator_judgment needs `amend`: your written rationale — why the failed floor is not the work\'s (an empty one is refused)', path: ['amend'] }).refine((b) => b.action !== OPERATOR_JUDGMENT_ACTION || (b.amendScope === undefined && b.plan === undefined), { message: 'operator_judgment takes no amendScope or plan — it answers the failed floor of the gated unit', path: ['amendScope'] }).refine(
|
|
590
|
+
// (j4-gates) A QE skip's reason IS its record, as at launch: a blank one is a 400 here.
|
|
591
|
+
(b) => b.action !== SKIP_QE_ACTION || (b.amend !== undefined && b.amend.trim() !== ''), { message: 'skip_qe needs `amend`: your written reason for skipping QE acceptance (an empty one is refused)', path: ['amend'] }).refine((b) => b.action !== SKIP_QE_ACTION || (b.amendScope === undefined && b.plan === undefined), { message: 'skip_qe takes no amendScope or plan — it answers the run\'s QE acceptance', path: ['amendScope'] }).refine(
|
|
572
592
|
// (wicked-core#555) An intent amendment IS its text: an empty one amends nothing, and the arm's
|
|
573
593
|
// scope is every unit at or after the cursor — which is what makes it reach the evaluator — so
|
|
574
594
|
// `amendScope` cannot narrow it. Both are 400s here rather than a 409 from the engine.
|
|
@@ -708,6 +728,9 @@ export const ChatOpenSchema = z.object({
|
|
|
708
728
|
primary: z.string().min(1).optional(),
|
|
709
729
|
/** (api-types 0.137.0, DES-SYSTEM-CHAT) The page the chat was started on, for the history list. */
|
|
710
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(),
|
|
711
734
|
}).strict();
|
|
712
735
|
/**
|
|
713
736
|
* The `singleSeat` disclosure (crew#641, hole closed in crew#650) — built in ONE place so the 201
|
|
@@ -787,6 +810,8 @@ function pushedOnlyRefusal(runId, pushed) {
|
|
|
787
810
|
'pull request can be opened there because that origin is not a GitHub host gh can resolve — ' +
|
|
788
811
|
'open the pull/merge request on your forge; merge stays human');
|
|
789
812
|
}
|
|
813
|
+
/** B8: the editor-conformance browser runtime crew provisions when a host has none (one per process). */
|
|
814
|
+
const editorConformanceRuntime = conformanceProvisioner(() => conformanceRuntimeRoot());
|
|
790
815
|
export function registerRoutes(app, adapter, gateCache, elicitationCache,
|
|
791
816
|
// Defaulted for the same reason: tests that drive this function directly get
|
|
792
817
|
// the projects surface with no bus (events skipped) and a fresh index.
|
|
@@ -919,7 +944,7 @@ runtime = {}) {
|
|
|
919
944
|
registry: editorRegistry,
|
|
920
945
|
adapter,
|
|
921
946
|
packCheck: runtime.packCheck ?? gardenPackCheck(discoverLivePlugin()?.path ?? null),
|
|
922
|
-
conformance: runtime.editorConformance ?? gardenConformance(discoverLivePlugin()?.path ?? null, () => process.env['WICKED_CREW_URL'] ?? null),
|
|
947
|
+
conformance: runtime.editorConformance ?? gardenConformance(discoverLivePlugin()?.path ?? null, () => process.env['WICKED_CREW_URL'] ?? null, { provision: editorConformanceRuntime }),
|
|
923
948
|
log: (m) => app.log.warn(m),
|
|
924
949
|
});
|
|
925
950
|
const chatRecorder = decisions !== null && chatTranscripts !== undefined && runtime.broadcast !== undefined
|
|
@@ -1220,6 +1245,22 @@ runtime = {}) {
|
|
|
1220
1245
|
// crew#1068: the engine has no wicked-vault CLI to record held boundary findings with.
|
|
1221
1246
|
...[adapter.vaultBin != null ? vaultBinWarning(adapter.vaultBin) : null].filter((w) => w !== null),
|
|
1222
1247
|
];
|
|
1248
|
+
// crew#1157: the engine's built-in presets are compiled in, so the chat workflows it carries are
|
|
1249
|
+
// read once (on the first probe that can read them) and held until restart.
|
|
1250
|
+
let chatWorkflowsHeld = null;
|
|
1251
|
+
const chatWorkflowsNow = async () => {
|
|
1252
|
+
if (chatWorkflowsHeld !== null)
|
|
1253
|
+
return chatWorkflowsHeld;
|
|
1254
|
+
if (typeof adapter.listPresets !== 'function')
|
|
1255
|
+
return [];
|
|
1256
|
+
try {
|
|
1257
|
+
chatWorkflowsHeld = servedChatWorkflows(await adapter.listPresets());
|
|
1258
|
+
return chatWorkflowsHeld;
|
|
1259
|
+
}
|
|
1260
|
+
catch {
|
|
1261
|
+
return [];
|
|
1262
|
+
}
|
|
1263
|
+
};
|
|
1223
1264
|
app.get(`${V}/health`, { config: { manifest: { statusCodes: [200] } } }, async () => {
|
|
1224
1265
|
const ping = await enginePing();
|
|
1225
1266
|
// F-E2E-030: what this deployment can keep. A composer reads `capabilities.deliverGate`
|
|
@@ -1253,9 +1294,14 @@ runtime = {}) {
|
|
|
1253
1294
|
launchPrimary: true,
|
|
1254
1295
|
// api-types 0.136.0 (DES-SYSTEM-VIEW): `GET /system`, the Nerve Center snapshot, is served.
|
|
1255
1296
|
system: true,
|
|
1297
|
+
// api-types 0.140.0: attention[] is the one needs-you list; unresolved[] + toReview; POST /runs/:id/resolve.
|
|
1298
|
+
attentionRanked: true,
|
|
1256
1299
|
// api-types 0.137.0 (DES-SYSTEM-CHAT): the chat history (list, reopen, close) and attachments.
|
|
1257
1300
|
chatList: chatHistory !== null,
|
|
1258
1301
|
chatAttachments: true,
|
|
1302
|
+
// api-types 0.141.0 (crew#1157): the read-only workflows `POST /chats {workflow}` runs on THIS
|
|
1303
|
+
// engine (`analyze` on a core-ts that carries it); `[]` = plain chats only.
|
|
1304
|
+
chatWorkflows: await chatWorkflowsNow(),
|
|
1259
1305
|
};
|
|
1260
1306
|
// wicked-core#411 / crew#497: the state-home blocker rides the health probe as a WARNING. The
|
|
1261
1307
|
// daemon still SERVES (status stays ok — studio must load and show the blocker) but refuses to
|
|
@@ -2190,6 +2236,29 @@ runtime = {}) {
|
|
|
2190
2236
|
req.log.warn(`linked-issue resolution failed; launching on the intent alone: ${message(err)}`);
|
|
2191
2237
|
}
|
|
2192
2238
|
}
|
|
2239
|
+
// DES-RUNS-REVIEW-001 §3: a hand launch of the run review (`/workflow-review-recent-work`) is prepared like a
|
|
2240
|
+
// scheduled one: its snapshot written, repo-less, one seat, its review directory as the only write root.
|
|
2241
|
+
// DES-DEFAULT-EVALS-001 §7: a launch of `run-evals` (`/workflow-run-evals`) is prepared by the daemon, which runs
|
|
2242
|
+
// the evals in-process and writes the report copy its Tool step reads; it delivers nothing.
|
|
2243
|
+
if (b.workflow === RUN_EVALS_WORKFLOW) {
|
|
2244
|
+
try {
|
|
2245
|
+
await runEvalsPrepare(input);
|
|
2246
|
+
}
|
|
2247
|
+
catch (err) {
|
|
2248
|
+
return reply.code(409).send({ error: `the default evals could not be prepared: ${message(err)}` });
|
|
2249
|
+
}
|
|
2250
|
+
deliver = 'none';
|
|
2251
|
+
}
|
|
2252
|
+
const isReview = b.workflow === REVIEW_WORKFLOW && runtime.reviewLaunch !== undefined;
|
|
2253
|
+
if (isReview) {
|
|
2254
|
+
try {
|
|
2255
|
+
await runtime.reviewLaunch(input);
|
|
2256
|
+
}
|
|
2257
|
+
catch (err) {
|
|
2258
|
+
return reply.code(409).send({ error: `the review could not be prepared: ${message(err)}` });
|
|
2259
|
+
}
|
|
2260
|
+
deliver = 'none';
|
|
2261
|
+
}
|
|
2193
2262
|
try {
|
|
2194
2263
|
const runId = await adapter.launchRun(input);
|
|
2195
2264
|
// crew#641/#642 (item 5): when a run is promoted from a chat, record how many seats
|
|
@@ -2299,6 +2368,8 @@ runtime = {}) {
|
|
|
2299
2368
|
}
|
|
2300
2369
|
catch (err) {
|
|
2301
2370
|
const msg = message(err);
|
|
2371
|
+
if (isReview)
|
|
2372
|
+
runtime.reviewLaunchRefused?.(input.sessionId, msg);
|
|
2302
2373
|
// DES-TEAMING-002 T3: a plan on an engine without the plan approval gate is an "upgrade the
|
|
2303
2374
|
// engine" 501 — never a free-text run launched in its place.
|
|
2304
2375
|
if (err instanceof PlanLaunchUnsupportedError) {
|
|
@@ -2436,6 +2507,60 @@ runtime = {}) {
|
|
|
2436
2507
|
return reply.code(500).send({ error: msg });
|
|
2437
2508
|
}
|
|
2438
2509
|
});
|
|
2510
|
+
// ── Resolve an unresolved run (api-types 0.140.0) ─────────────────────────────
|
|
2511
|
+
// Unresolved work (`GET /system.unresolved[]`) is resolved, never hidden: archived WITH a reason,
|
|
2512
|
+
// or closed as superseded naming the run that replaced it (retry is `POST /runs {retryOf}`). A run
|
|
2513
|
+
// parked at a gate is stopped first (no worker is running), then archived; a run that is
|
|
2514
|
+
// executing is refused, so a resolve never ends live work. The note keeps the reason.
|
|
2515
|
+
const ResolveSchema = z.discriminatedUnion('action', [
|
|
2516
|
+
z.object({ action: z.literal('archive'), reason: z.string().trim().min(1, 'a reason is required to archive').max(480) }).strict(),
|
|
2517
|
+
z.object({ action: z.literal('superseded'), by: z.string().min(1), reason: z.string().trim().max(400).optional() }).strict(),
|
|
2518
|
+
]);
|
|
2519
|
+
app.post(`${V}/runs/:id/resolve`, { config: { manifest: { requestType: 'ResolveRunRequest', responseType: 'ResolveRunResponse', statusCodes: [200, 400, 404, 409, 500] } } }, async (req, reply) => {
|
|
2520
|
+
const { id } = req.params;
|
|
2521
|
+
const parsed = ResolveSchema.safeParse(req.body ?? {});
|
|
2522
|
+
if (!parsed.success)
|
|
2523
|
+
return reply.code(400).send(invalidBody(parsed.error, 'Invalid request body'));
|
|
2524
|
+
const b = parsed.data;
|
|
2525
|
+
const note = b.action === 'archive' ? `resolved: ${b.reason}` : `superseded by ${b.by}${b.reason !== undefined && b.reason !== '' ? `: ${b.reason}` : ''}`;
|
|
2526
|
+
// Inside the run's decision lock (the gate path's): the status read, the stop and the archive
|
|
2527
|
+
// are one step, so a gate answered at the same moment can never be stopped by a resolve. The
|
|
2528
|
+
// status is read again inside the lock, right before the stop.
|
|
2529
|
+
const outcome = await decideExclusively(id, async () => {
|
|
2530
|
+
const views = await adapter.sessionsDetail();
|
|
2531
|
+
const run = views.find((v) => v.session.id === id);
|
|
2532
|
+
if (run === undefined)
|
|
2533
|
+
return { code: 404, body: { error: 'Run not found' } };
|
|
2534
|
+
if (b.action === 'superseded') {
|
|
2535
|
+
if (b.by === id)
|
|
2536
|
+
return { code: 400, body: { error: '`by` must name another run' } };
|
|
2537
|
+
if (!views.some((v) => v.session.id === b.by))
|
|
2538
|
+
return { code: 400, body: { error: `\`by\` names an unknown run: ${b.by}` } };
|
|
2539
|
+
}
|
|
2540
|
+
const status = run.session.status;
|
|
2541
|
+
if (status !== 'awaiting_human' && status !== 'failed' && status !== 'completed' && status !== 'cancelled') {
|
|
2542
|
+
return { code: 409, body: { error: `run ${id} is ${status}: a running job cannot be resolved; stop it first` } };
|
|
2543
|
+
}
|
|
2544
|
+
let cancelled = false;
|
|
2545
|
+
try {
|
|
2546
|
+
if (status === 'awaiting_human') {
|
|
2547
|
+
await adapter.cancelRun(id);
|
|
2548
|
+
cancelled = true;
|
|
2549
|
+
}
|
|
2550
|
+
const found = await adapter.archiveRun(id, true, note);
|
|
2551
|
+
if (!found)
|
|
2552
|
+
return { code: 404, body: { error: 'Run not found' } };
|
|
2553
|
+
}
|
|
2554
|
+
catch (e) {
|
|
2555
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
2556
|
+
return { code: /only a terminal run/i.test(msg) ? 409 : 500, body: { error: msg } };
|
|
2557
|
+
}
|
|
2558
|
+
audit.record('run.resolved', actorOf(req), { runId: id, detail: { action: b.action, note, cancelled, ...(b.action === 'superseded' ? { by: b.by } : {}) } });
|
|
2559
|
+
const out = { runId: id, action: b.action, archived: true, cancelled, note };
|
|
2560
|
+
return { code: 200, body: out };
|
|
2561
|
+
});
|
|
2562
|
+
return reply.code(outcome.code).send(outcome.body);
|
|
2563
|
+
});
|
|
2439
2564
|
// Bulk write-off for campaign backlogs — explicit ids only, never implicit age selection.
|
|
2440
2565
|
const BulkArchiveSchema = z.object({
|
|
2441
2566
|
ids: z.array(z.string().min(1)).min(1).max(200),
|
|
@@ -2911,6 +3036,23 @@ runtime = {}) {
|
|
|
2911
3036
|
return reply.code(501).send({ error: message(err) });
|
|
2912
3037
|
}
|
|
2913
3038
|
}
|
|
3039
|
+
// crew#1157: a chat that runs a workflow runs a BUILT-IN read-only one, resolved before anything
|
|
3040
|
+
// is reserved or warmed (a 400 for a name chats do not run, a 409 for an engine without it).
|
|
3041
|
+
let workflow;
|
|
3042
|
+
if (b.workflow !== undefined) {
|
|
3043
|
+
let presets = [];
|
|
3044
|
+
try {
|
|
3045
|
+
presets = typeof adapter.listPresets === 'function' ? await adapter.listPresets() : [];
|
|
3046
|
+
}
|
|
3047
|
+
catch {
|
|
3048
|
+
presets = [];
|
|
3049
|
+
}
|
|
3050
|
+
const resolved = resolveChatWorkflow(b.workflow, presets);
|
|
3051
|
+
if (!resolved.ok)
|
|
3052
|
+
return reply.code(resolved.status).send({ error: resolved.error });
|
|
3053
|
+
workflow = resolved.workflow;
|
|
3054
|
+
}
|
|
3055
|
+
const statementOpts = { analysis: workflow !== undefined };
|
|
2914
3056
|
// A chat id this daemon already holds a scope for is LIVE: re-preparing its scratch root and
|
|
2915
3057
|
// re-opening it would overwrite the statement its seats are reading and (on a changed scope)
|
|
2916
3058
|
// evict them mid-conversation (Copilot, #518). Close it first, or let the daemon mint the id.
|
|
@@ -2958,7 +3100,7 @@ runtime = {}) {
|
|
|
2958
3100
|
if (!chatScopes.owns(chatId, token)) {
|
|
2959
3101
|
return reply.code(409).send({ error: `chat ${chatId} was closed while it was being opened; open it again` });
|
|
2960
3102
|
}
|
|
2961
|
-
prepareChatScratch(chatId, scope, inForceAtOpen);
|
|
3103
|
+
prepareChatScratch(chatId, scope, inForceAtOpen, statementOpts);
|
|
2962
3104
|
}
|
|
2963
3105
|
catch (err) {
|
|
2964
3106
|
// The helper removes only what it created itself (a pre-existing root is preserved) —
|
|
@@ -3029,6 +3171,12 @@ runtime = {}) {
|
|
|
3029
3171
|
refused,
|
|
3030
3172
|
});
|
|
3031
3173
|
}
|
|
3174
|
+
// crew#1157: a workflow chat is ONE seat — the operator's pick (`primary`), else the first
|
|
3175
|
+
// admitted seat in roster order. The others are not refused; they simply do not sit this chat.
|
|
3176
|
+
if (workflow !== undefined) {
|
|
3177
|
+
const one = b.primary !== undefined && clis.includes(b.primary) ? b.primary : clis[0];
|
|
3178
|
+
clis.splice(0, clis.length, one);
|
|
3179
|
+
}
|
|
3032
3180
|
try {
|
|
3033
3181
|
// DES-ASK-TEAM-CHAT-001 §5.1 (ASK-C1): an open resolves the scope and ELIGIBILITY and warms
|
|
3034
3182
|
// nothing — the chat is a path its first message launches, and the engine picks the PA
|
|
@@ -3096,7 +3244,7 @@ runtime = {}) {
|
|
|
3096
3244
|
askPaths.open(chatId, clis, b.primary, {
|
|
3097
3245
|
seats: standing.filter((s) => clis.includes(String(s.key))),
|
|
3098
3246
|
scopeKind: !scoped ? 'unscoped' : admissionKind === 'path-bound' ? 'scoped_bound' : 'scoped',
|
|
3099
|
-
});
|
|
3247
|
+
}, workflow);
|
|
3100
3248
|
// Register roots for path rewriting (crew#618): absolute host paths in seat replies are
|
|
3101
3249
|
// rewritten to repo-relative form before being stored in the transcript.
|
|
3102
3250
|
if (chatTranscripts !== undefined && scope.repos.length > 0) {
|
|
@@ -3139,6 +3287,7 @@ runtime = {}) {
|
|
|
3139
3287
|
seats,
|
|
3140
3288
|
scope,
|
|
3141
3289
|
refused,
|
|
3290
|
+
...(workflow !== undefined ? { workflow: workflow.name } : {}),
|
|
3142
3291
|
...(projectAttachError !== undefined ? { projectAttachError } : {}),
|
|
3143
3292
|
...(singleSeat !== undefined ? { singleSeat } : {}),
|
|
3144
3293
|
});
|
|
@@ -3277,9 +3426,16 @@ runtime = {}) {
|
|
|
3277
3426
|
};
|
|
3278
3427
|
// DC-S7: the rules the chat's seats were told about ride the step's instructions, as the
|
|
3279
3428
|
// preface rode a pool send; told only once the send is accepted (`commit`).
|
|
3280
|
-
|
|
3429
|
+
// crew#1155: the installed MCP tools and the one command that reaches them (the broker), read
|
|
3430
|
+
// per turn so a server installed or disabled since the last turn is reflected.
|
|
3431
|
+
let mcpTools = null;
|
|
3432
|
+
const withPreface = (preface) => {
|
|
3433
|
+
const body = mcpTools !== null ? `${mcpTools}\n\n${seatText}` : seatText;
|
|
3434
|
+
return preface !== null ? `${preface}\n\n${body}` : body;
|
|
3435
|
+
};
|
|
3281
3436
|
try {
|
|
3282
3437
|
const rules = (await considerations.inForceFor(scope?.projectId ?? null)).rules;
|
|
3438
|
+
mcpTools = await chatMcpToolsFor(runtime.mcp);
|
|
3283
3439
|
// crew#1020: a project/everything chat must see repos attached since open. Admission was
|
|
3284
3440
|
// under this scope KIND and the kind stays the same, so do not re-run seat admission.
|
|
3285
3441
|
const scopeRequest = chatScopes.requestOf(id);
|
|
@@ -3356,7 +3512,7 @@ runtime = {}) {
|
|
|
3356
3512
|
}
|
|
3357
3513
|
if (storedScope !== undefined && scope !== undefined && engine !== undefined &&
|
|
3358
3514
|
(JSON.stringify(scope) !== JSON.stringify(storedScope) || JSON.stringify(engine) !== JSON.stringify(chatScopes.engineOf(id)))) {
|
|
3359
|
-
prepareChatScratch(id, scope, rules);
|
|
3515
|
+
prepareChatScratch(id, scope, rules, { analysis: path.workflow !== undefined });
|
|
3360
3516
|
if (chatScopes.refresh(id, storedScope, scope, engine)) {
|
|
3361
3517
|
chatTranscripts?.registerRoots(id, scope.repos.map((r) => ({ absRoot: resolve(r.rootPath), name: r.name })));
|
|
3362
3518
|
}
|
|
@@ -3382,8 +3538,11 @@ runtime = {}) {
|
|
|
3382
3538
|
audience = voice.length > 0 ? voice : path.eligible;
|
|
3383
3539
|
turn = chatTurns.begin(id, audience, text, ASK_TURN_BUDGET_SECS);
|
|
3384
3540
|
turnId = turn?.turnId ?? turnId;
|
|
3385
|
-
// A repo-bound path can deliver; project and multi-repo scopes remain read-only asks.
|
|
3541
|
+
// A repo-bound path can deliver; project and multi-repo scopes remain read-only asks. A workflow
|
|
3542
|
+
// chat (crew#1157) never delivers: it is bound to its repository read-only, with no deliver.
|
|
3386
3543
|
const repoRef = scope?.kind === 'repos' && scope.repos.length === 1 ? scope.repos[0].id : undefined;
|
|
3544
|
+
const delivers = repoRef !== undefined && path.workflow === undefined;
|
|
3545
|
+
const statementOpts = { analysis: path.workflow !== undefined };
|
|
3387
3546
|
const projectId = scope?.projectId;
|
|
3388
3547
|
if (path.runId === undefined) {
|
|
3389
3548
|
const stepId = 'answer-1';
|
|
@@ -3392,14 +3551,18 @@ runtime = {}) {
|
|
|
3392
3551
|
// crew#823: the run is titled by the operator's question (every reader titles a run from
|
|
3393
3552
|
// `problem`); the scope statement grounds the seats from the first answer step's
|
|
3394
3553
|
// instructions instead of standing in as the run's title.
|
|
3395
|
-
const instructions = scope !== undefined ? `${chatScopeStatement(id, scope, rules)}\n\n${withPreface(pending.preface)}` : withPreface(pending.preface);
|
|
3554
|
+
const instructions = scope !== undefined ? `${chatScopeStatement(id, scope, rules, statementOpts)}\n\n${withPreface(pending.preface)}` : withPreface(pending.preface);
|
|
3396
3555
|
const input = {
|
|
3397
3556
|
problem: text,
|
|
3398
3557
|
sessionId: randomUUID(),
|
|
3399
3558
|
clisJson: JSON.stringify(roster),
|
|
3400
|
-
|
|
3559
|
+
// A workflow chat is one seat and asks for no reviewer (crew#1157).
|
|
3560
|
+
plan: {
|
|
3561
|
+
steps: [chatTurnStep(stepId, instructions, path.workflow)],
|
|
3562
|
+
...(path.workflow === undefined ? { monitors: { asked: 1 } } : {}),
|
|
3563
|
+
},
|
|
3401
3564
|
...(path.primary !== undefined ? { primary: path.primary } : {}),
|
|
3402
|
-
...(repoRef !== undefined ? { repoRef, deliver: 'pr' } : {}),
|
|
3565
|
+
...(repoRef !== undefined ? (delivers ? { repoRef, deliver: 'pr' } : { repoRef }) : {}),
|
|
3403
3566
|
...(projectId !== undefined ? { projectId } : {}),
|
|
3404
3567
|
...(scope?.kind === 'project' && codeGraphDb !== null ? { projectGraph: { dbPath: codeGraphDb } } : {}),
|
|
3405
3568
|
// crew#1034: the scope's repositories are the run's read roots, graph or no graph — the
|
|
@@ -3434,7 +3597,8 @@ runtime = {}) {
|
|
|
3434
3597
|
askPath: true,
|
|
3435
3598
|
...(repoRef !== undefined ? { repoRef } : {}),
|
|
3436
3599
|
...(projectId !== undefined ? { projectId } : {}),
|
|
3437
|
-
|
|
3600
|
+
...(path.workflow !== undefined ? { chatWorkflow: path.workflow.name } : {}),
|
|
3601
|
+
deliver: delivers ? 'pr' : 'none',
|
|
3438
3602
|
});
|
|
3439
3603
|
});
|
|
3440
3604
|
return reply.code(202).send({
|
|
@@ -3445,7 +3609,7 @@ runtime = {}) {
|
|
|
3445
3609
|
// crew#1033: EVERY answer step carries the scope statement, as the first one does. A seat on a
|
|
3446
3610
|
// single-shot carrier (codex on the wrapped carrier) remembers nothing of the first turn, so a
|
|
3447
3611
|
// follow-up told only the question answered "the supplied repository contains only tmp".
|
|
3448
|
-
const instructions = scope !== undefined ? `${chatScopeStatement(id, scope, rules)}\n\n${withPreface(pending.preface)}` : withPreface(pending.preface);
|
|
3612
|
+
const instructions = scope !== undefined ? `${chatScopeStatement(id, scope, rules, statementOpts)}\n\n${withPreface(pending.preface)}` : withPreface(pending.preface);
|
|
3449
3613
|
// The continuation answers the path run's gate: under the per-run decision lock it re-reads the
|
|
3450
3614
|
// run and is judged by the record before it proposes or confirms anything (crew#1083 round 7).
|
|
3451
3615
|
// The status read above is a pre-check that changes nothing; this read is the one that counts.
|
|
@@ -3463,7 +3627,7 @@ runtime = {}) {
|
|
|
3463
3627
|
const decided = await gateForDecision(view, undefined);
|
|
3464
3628
|
if ('refusal' in decided)
|
|
3465
3629
|
return decided.refusal;
|
|
3466
|
-
await adapter.proposePlan(pathRunId, { steps: [
|
|
3630
|
+
await adapter.proposePlan(pathRunId, { steps: [chatTurnStep(stepId, instructions, path.workflow)] }, turnId);
|
|
3467
3631
|
await adapter.confirmGate(pathRunId, true);
|
|
3468
3632
|
return null;
|
|
3469
3633
|
});
|
|
@@ -3535,6 +3699,15 @@ runtime = {}) {
|
|
|
3535
3699
|
runId: launched.runId,
|
|
3536
3700
|
});
|
|
3537
3701
|
}
|
|
3702
|
+
// crew#1157 (xreview on #1160): an analysis chat seats ONE helper; a re-seat may re-warm that
|
|
3703
|
+
// seat, never add another.
|
|
3704
|
+
if (launched?.workflow !== undefined && !(clis.length === 1 && launched.eligible.includes(clis[0]))) {
|
|
3705
|
+
return reply.code(409).send({
|
|
3706
|
+
code: 'analysis_one_seat',
|
|
3707
|
+
error: `chat '${id}' is an analysis (${launched.workflow}) and seats one helper (${launched.eligible.join(', ')}); open a new chat to ask another`,
|
|
3708
|
+
chatId: id,
|
|
3709
|
+
});
|
|
3710
|
+
}
|
|
3538
3711
|
if (launched?.busy === true) {
|
|
3539
3712
|
// The first message is launching with the roster as it stands (codex on #808 r3, 4).
|
|
3540
3713
|
return reply.code(409).send({
|
|
@@ -4169,7 +4342,19 @@ runtime = {}) {
|
|
|
4169
4342
|
* A decision without one keeps the pre-table resolution (the cache, then the log, then the
|
|
4170
4343
|
* cursor unit) — an engine, or a run, older than the table.
|
|
4171
4344
|
*/
|
|
4172
|
-
async function gateForDecision(run, namedOrd) {
|
|
4345
|
+
async function gateForDecision(run, namedOrd, opts = {}) {
|
|
4346
|
+
const decided = await gateOfRecord(run, namedOrd);
|
|
4347
|
+
// crew#1157: every APPROVING path (the gate route, standing orders, a gated resume or reassign, a
|
|
4348
|
+
// plan edit at the gate, the next message of a chat) is judged here, so no path approves a held
|
|
4349
|
+
// plan change on an analysis run. Declining stays open.
|
|
4350
|
+
if (!('refusal' in decided) && opts.declining !== true) {
|
|
4351
|
+
const analysis = analysisPlanRefusal(run.session.id, { gateKind: decided.gateKind });
|
|
4352
|
+
if (analysis !== null)
|
|
4353
|
+
return { refusal: analysis };
|
|
4354
|
+
}
|
|
4355
|
+
return decided;
|
|
4356
|
+
}
|
|
4357
|
+
async function gateOfRecord(run, namedOrd) {
|
|
4173
4358
|
const id = run.session.id;
|
|
4174
4359
|
const unitAt = (ord) => (run.units ?? []).find((u) => u.ord === ord);
|
|
4175
4360
|
const unknown = (why) => ({ refusal: { code: 409, body: { error: `Gate unknown: ${why} — refresh and decide again.`, code: 'gate_unknown' } } });
|
|
@@ -4204,9 +4389,18 @@ runtime = {}) {
|
|
|
4204
4389
|
* freeze (idea 15), and wicked-core#850 EX-03 — a run whose contract requires QE acceptance
|
|
4205
4390
|
* delivers only on an attributed PASS. A refusal leaves the gate open.
|
|
4206
4391
|
*/
|
|
4207
|
-
async function deliverApprovalRefusal(run, deliverUnit
|
|
4392
|
+
async function deliverApprovalRefusal(run, deliverUnit,
|
|
4393
|
+
/** (j4-gates) The operator's QE skip this approve carries (`action: 'skip_qe'`, its reason). */
|
|
4394
|
+
skipQeReason) {
|
|
4208
4395
|
if (deliveryFreeze.state().frozen)
|
|
4209
4396
|
return { refusal: { code: 409, body: frozenRefusal(deliveryFreeze.state()) } };
|
|
4397
|
+
// (j4-gates) An explicit operator skip at THIS gate, on a run whose QE acceptance is still
|
|
4398
|
+
// required: the delivery goes without a ledger PASS, and its check says so in the skip's own
|
|
4399
|
+
// words — the engine records the same decision with the confirm (or refuses it, 409, gate open).
|
|
4400
|
+
const decision = qeDecisionOf(run);
|
|
4401
|
+
if (skipQeReason !== undefined && decision !== null && !qeDecisionBypasses(decision)) {
|
|
4402
|
+
return { qeAcceptance: qeAcceptanceSkippedAtGate(skipQeReason) };
|
|
4403
|
+
}
|
|
4210
4404
|
const qeAcceptance = await qeAcceptanceCheckFor(run);
|
|
4211
4405
|
if (qeAcceptance !== null && !qeAcceptance.satisfied) {
|
|
4212
4406
|
return { refusal: { code: 409, body: qeAcceptanceRefusal(run.session.id, qeAcceptance, receiptOf(deliverUnit), 'gate') } };
|
|
@@ -4244,6 +4438,30 @@ runtime = {}) {
|
|
|
4244
4438
|
});
|
|
4245
4439
|
return result;
|
|
4246
4440
|
}
|
|
4441
|
+
/**
|
|
4442
|
+
* crew#1157 (xreview on #1160): an ANALYSIS chat's run never takes on work. Its seat is told not to
|
|
4443
|
+
* propose any, but a `PLAN+` line in an answer would still hold a creator addition at a
|
|
4444
|
+
* `plan_approval` gate; approving it (or approving with an edited plan) would let the run write.
|
|
4445
|
+
* Refused here, for every decision path, by the durable launch record: declining stays allowed.
|
|
4446
|
+
*/
|
|
4447
|
+
function analysisPlanRefusal(runId, held) {
|
|
4448
|
+
const workflow = runTimingIndex.chatWorkflowOf(runId);
|
|
4449
|
+
if (workflow === undefined)
|
|
4450
|
+
return null;
|
|
4451
|
+
// A held plan change (the gate the engine opens on a `PLAN+` line), or a plan edit with any step
|
|
4452
|
+
// that could write (the same read-only predicate the chat open applies to the workflow).
|
|
4453
|
+
const writes = held.plan?.steps.some((s) => writeCapableStep(s) !== null) === true;
|
|
4454
|
+
if (held.gateKind !== 'plan_approval' && !writes)
|
|
4455
|
+
return null;
|
|
4456
|
+
return {
|
|
4457
|
+
code: 409,
|
|
4458
|
+
body: {
|
|
4459
|
+
code: 'analysis_read_only',
|
|
4460
|
+
error: `run ${runId} is an analysis (${workflow}): it answers questions and changes nothing, so it cannot take on ` +
|
|
4461
|
+
'the work its answer proposed. Decline it here, and start a job for the change.',
|
|
4462
|
+
},
|
|
4463
|
+
};
|
|
4464
|
+
}
|
|
4247
4465
|
/**
|
|
4248
4466
|
* THE gate decision path. `POST /runs/:id/gate` answers with it, and so does a standing order
|
|
4249
4467
|
* (behaviour 10) — the same awaiting check, the same ord check and 409 `gate_changed` /
|
|
@@ -4267,9 +4485,12 @@ runtime = {}) {
|
|
|
4267
4485
|
// Decision integrity (api-types 0.44.0; crew#1083 rounds 3–5): the gate this decision answers
|
|
4268
4486
|
// is the RECORD's — read first thing inside the lock, so the gated unit and its delivery
|
|
4269
4487
|
// checks below are the ones the record confirms ({@link gateForDecision}).
|
|
4270
|
-
const decided = await gateForDecision(run, parsed.data.ord);
|
|
4488
|
+
const decided = await gateForDecision(run, parsed.data.ord, { declining: !parsed.data.approve });
|
|
4271
4489
|
if ('refusal' in decided)
|
|
4272
4490
|
return decided.refusal;
|
|
4491
|
+
const analysis = parsed.data.approve ? analysisPlanRefusal(id, { plan: parsed.data.plan }) : null;
|
|
4492
|
+
if (analysis !== null)
|
|
4493
|
+
return analysis;
|
|
4273
4494
|
// The gated unit and its receipt as it stands when the person decides (wicked-core#850: every
|
|
4274
4495
|
// gate answer carries it). An APPROVE that would run the deliver unit passes the delivery
|
|
4275
4496
|
// checks first.
|
|
@@ -4277,7 +4498,7 @@ runtime = {}) {
|
|
|
4277
4498
|
const assurance = receiptOf(gated);
|
|
4278
4499
|
let deliveryCheck;
|
|
4279
4500
|
if (parsed.data.approve && gated !== undefined && isDeliverUnit(gated)) {
|
|
4280
|
-
const refused = await deliverApprovalRefusal(run, gated);
|
|
4501
|
+
const refused = await deliverApprovalRefusal(run, gated, parsed.data.action === SKIP_QE_ACTION ? parsed.data.amend : undefined);
|
|
4281
4502
|
if ('refusal' in refused)
|
|
4282
4503
|
return refused.refusal;
|
|
4283
4504
|
deliveryCheck = { qeAcceptance: refused.qeAcceptance };
|
|
@@ -4520,7 +4741,7 @@ runtime = {}) {
|
|
|
4520
4741
|
async function approveAcceptsWork(run, ord, gateKind) {
|
|
4521
4742
|
const unit = run.units.find((u) => u.ord === ord);
|
|
4522
4743
|
const denial = unit?.denial;
|
|
4523
|
-
if (unit?.status === 'rejected' && (denial?.source === 'judge_unavailable' || denial?.source === 'qe_reviewer_unavailable')) {
|
|
4744
|
+
if (unit?.status === 'rejected' && (denial?.source === 'judge_unavailable' || denial?.source === 'qe_reviewer_unavailable' || denial?.source === 'qe_inconclusive')) {
|
|
4524
4745
|
return 'is the operator\'s judgment of the work as it stands (no judge could rule)';
|
|
4525
4746
|
}
|
|
4526
4747
|
if (gateKind === 'review_adjudication') {
|
|
@@ -7030,7 +7251,12 @@ runtime = {}) {
|
|
|
7030
7251
|
registerPresetRoutes(app, adapter, security);
|
|
7031
7252
|
// ── Team (DES-TEAMING-002 T8) — reads and commands over the engine; the live stream is the
|
|
7032
7253
|
// teamEvent relay (team/ws-relay.ts). Crew publishes no team fact (§4.0).
|
|
7033
|
-
registerTeamRoutes(app, adapter, security, {
|
|
7254
|
+
registerTeamRoutes(app, adapter, security, {
|
|
7255
|
+
exclusive: decideExclusively,
|
|
7256
|
+
judge: gateForDecision,
|
|
7257
|
+
// crew#1157: a plan edit on an analysis run may not add a step that could write.
|
|
7258
|
+
readOnlyRefusal: (runId, plan) => analysisPlanRefusal(runId, { plan }),
|
|
7259
|
+
});
|
|
7034
7260
|
// ── Campaigns (crew#342 + TH-9) — the engine's durable Run-DAG scheduler over REST ──────────
|
|
7035
7261
|
// Progress streams as campaign* CoreEvents on the existing allowlist-free /ws relay; these
|
|
7036
7262
|
// routes are launch/resume/cancel + the reads a scoreboard builds from. The GET routes join
|
|
@@ -7093,6 +7319,19 @@ runtime = {}) {
|
|
|
7093
7319
|
// directly-driven route set records nothing unless it injects one (no `~/.wicked-crew` writes).
|
|
7094
7320
|
...(runtime.evalStore !== undefined ? { evalStore: runtime.evalStore } : {}),
|
|
7095
7321
|
});
|
|
7322
|
+
// ── Default evals (DES-DEFAULT-EVALS-001) — the system's own judgments, scored against what happened ─────
|
|
7323
|
+
// Read-only over the run store (sessions, events, the operator's decisions, QE ledgers, the latest steering
|
|
7324
|
+
// eval); the only write is the report, under the eval store's root.
|
|
7325
|
+
const defaultEvalDeps = {
|
|
7326
|
+
...(runtime.defaultEvalStore !== undefined ? { store: runtime.defaultEvalStore } : {}),
|
|
7327
|
+
// Served runs, as GET /runs serves them: the derived title and the project membership join (codex r1).
|
|
7328
|
+
runs: async () => (await adapter.sessionsDetail()).map(decorateRun),
|
|
7329
|
+
events: async (id) => (typeof adapter.runEvents === 'function' ? (await adapter.runEvents(id)) : null),
|
|
7330
|
+
decisions: () => (runtime.decisionLedger?.list({}) ?? []),
|
|
7331
|
+
...(runtime.evalStore !== undefined ? { steeringEvals: runtime.evalStore } : {}),
|
|
7332
|
+
replay: defaultReplaySet({ gardenRoot: gardenRootFor(process.env) }),
|
|
7333
|
+
};
|
|
7334
|
+
registerDefaultEvalRoutes(app, defaultEvalDeps);
|
|
7096
7335
|
// ── Skills (skills keystone) — the file manager over the daemon-owned garden plugin root ─────
|
|
7097
7336
|
// Manifest + typed reads, guarded CAS writes, refresh/publish/analyze. `createServer` injects the
|
|
7098
7337
|
// runtime it booted (store seeded from the installed plugin, snapshot published); a
|
|
@@ -7151,6 +7390,8 @@ runtime = {}) {
|
|
|
7151
7390
|
// store is kept for SYSTEM_STATS_TTL_MS, with its `asOf`. The whole answer is held to
|
|
7152
7391
|
// SYSTEM_BUDGET_MS: a part that does not settle in time reads `unknown` (or `null`) with why.
|
|
7153
7392
|
const weekFoldCache = new WeekFoldCache();
|
|
7393
|
+
const runCosts = new RunCostCache();
|
|
7394
|
+
let toReviewCache = null;
|
|
7154
7395
|
/** One stat of a run's durable event log: read before the 7-day fold, so the fold can never eat
|
|
7155
7396
|
* the time a liveness verdict needs. */
|
|
7156
7397
|
const EVIDENCE_TIMEOUT_MS = 1_000;
|
|
@@ -7186,6 +7427,17 @@ runtime = {}) {
|
|
|
7186
7427
|
const rules = await adapter.listConformanceRules();
|
|
7187
7428
|
return rules.filter((r) => r.retired !== true).length;
|
|
7188
7429
|
}, PROBE_TIMEOUT_MS);
|
|
7430
|
+
// Pending proposals: "N to review", never in the needs-you number (no run waits on them).
|
|
7431
|
+
// Kept for SYSTEM_STATS_TTL_MS: a skin polls /system, and each read would spawn the estate client.
|
|
7432
|
+
const toReviewP = settleWithin(async () => {
|
|
7433
|
+
if (toReviewCache !== null && Date.now() - toReviewCache.at < SYSTEM_STATS_TTL_MS)
|
|
7434
|
+
return toReviewCache.value;
|
|
7435
|
+
const got = (await estateTool('proposal.list', { state: 'pending' }));
|
|
7436
|
+
const value = Array.isArray(got?.proposals) ? got.proposals.length : null;
|
|
7437
|
+
if (value !== null)
|
|
7438
|
+
toReviewCache = { at: Date.now(), value }; // a failed read is never kept
|
|
7439
|
+
return value;
|
|
7440
|
+
}, PROBE_TIMEOUT_MS);
|
|
7189
7441
|
const gatesByYouP = settleWithin(async () => {
|
|
7190
7442
|
const since = Date.now() - SYSTEM_STATS_DAYS * 86_400_000;
|
|
7191
7443
|
const entries = await audit.readAll({ action: 'gate.decided', since });
|
|
@@ -7244,7 +7496,14 @@ runtime = {}) {
|
|
|
7244
7496
|
if (openGates.kind === 'ok' && openGates.value !== null) {
|
|
7245
7497
|
const at = (r) => new Date(r.created_at).getTime() || 0;
|
|
7246
7498
|
for (const r of [...openGates.value].sort((a, b) => at(a) - at(b))) {
|
|
7247
|
-
|
|
7499
|
+
const arrived = at(r);
|
|
7500
|
+
openByRun.set(r.session_id, {
|
|
7501
|
+
interactionId: r.id,
|
|
7502
|
+
ord: r.ord,
|
|
7503
|
+
kind: r.gate_kind ?? (r.kind === 'elicitation' ? 'question' : null),
|
|
7504
|
+
prompt: r.prompt.slice(0, 280),
|
|
7505
|
+
receivedAt: arrived > 0 ? new Date(arrived).toISOString() : (gateCache.get(r.session_id)?.receivedAt ?? null),
|
|
7506
|
+
});
|
|
7248
7507
|
}
|
|
7249
7508
|
}
|
|
7250
7509
|
const now = Date.now();
|
|
@@ -7265,7 +7524,7 @@ runtime = {}) {
|
|
|
7265
7524
|
const cached = gateCache.get(view.session.id);
|
|
7266
7525
|
const gate = openByRun.get(view.session.id) ??
|
|
7267
7526
|
(view.session.status === 'awaiting_human' && cached !== undefined
|
|
7268
|
-
? { interactionId: null, ord: cached.ord, kind: cached.gateKind ?? null, prompt: cached.prompt.slice(0, 280) }
|
|
7527
|
+
? { interactionId: null, ord: cached.ord, kind: cached.gateKind ?? null, prompt: cached.prompt.slice(0, 280), receivedAt: cached.receivedAt }
|
|
7269
7528
|
: null);
|
|
7270
7529
|
const run = runLiveness({ view, now, evidence: await evidenceOf(view.session.id), gate: view.session.status === 'awaiting_human' ? gate : null, quietAfterMs, stallAfterMs });
|
|
7271
7530
|
return { view, run };
|
|
@@ -7362,16 +7621,50 @@ runtime = {}) {
|
|
|
7362
7621
|
steeringRules: rules.kind === 'ok' ? rules.value : null,
|
|
7363
7622
|
...(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}` } : {}),
|
|
7364
7623
|
});
|
|
7365
|
-
|
|
7366
|
-
|
|
7624
|
+
// The one "needs you" rule (needs.ts): root causes ranked by impact, plus the unresolved work
|
|
7625
|
+
// and what it cost. Per-run cost is read from the run's own log, cached, never waited on past
|
|
7626
|
+
// the budget (an unread run's cost reads unknown and the next read has it).
|
|
7627
|
+
const first = buildNeeds({
|
|
7628
|
+
now,
|
|
7629
|
+
views: allViews,
|
|
7630
|
+
runs,
|
|
7367
7631
|
infra,
|
|
7368
7632
|
clis,
|
|
7633
|
+
gaps,
|
|
7634
|
+
infraHoldsLive: (key) => (key === 'gateway' || key === 'broker' ? containerFloor : key === 'skills' || key === 'state_home' || key === 'disk' || key === 'daemon'),
|
|
7635
|
+
costOf: (id) => runCosts.get(id),
|
|
7636
|
+
});
|
|
7637
|
+
if (typeof adapter.runEvents === 'function' && first.unresolved.length > 0) {
|
|
7638
|
+
const byId = new Map(allViews.map((v) => [v.session.id, v]));
|
|
7639
|
+
await settleWithin(() => runCosts.fill(first.unresolved.map((u) => {
|
|
7640
|
+
const s = byId.get(u.runId)?.session;
|
|
7641
|
+
return { id: u.runId, final: ['failed', 'completed', 'cancelled'].includes(s?.status ?? ''), stamp: `${s?.status ?? ''}:${s?.ended_at ?? ''}` };
|
|
7642
|
+
}), (id) => adapter.runEvents(id)), Math.min(left(), 2_000));
|
|
7643
|
+
}
|
|
7644
|
+
const needs = buildNeeds({
|
|
7645
|
+
now,
|
|
7646
|
+
views: allViews,
|
|
7369
7647
|
runs,
|
|
7370
|
-
|
|
7648
|
+
infra,
|
|
7649
|
+
clis,
|
|
7371
7650
|
gaps,
|
|
7372
|
-
|
|
7651
|
+
infraHoldsLive: (key) => (key === 'gateway' || key === 'broker' ? containerFloor : key === 'skills' || key === 'state_home' || key === 'disk' || key === 'daemon'),
|
|
7652
|
+
costOf: (id) => runCosts.get(id),
|
|
7653
|
+
});
|
|
7654
|
+
const toReview = await toReviewP;
|
|
7655
|
+
// A run list that could not be read never reads green: the daemon row (degraded, "did not list
|
|
7656
|
+
// its runs") is in attention as a slowdown, and the level stays at least yellow.
|
|
7657
|
+
return assembleSnapshot({
|
|
7658
|
+
startedAt,
|
|
7659
|
+
clis,
|
|
7660
|
+
runs,
|
|
7661
|
+
infra,
|
|
7662
|
+
governance,
|
|
7663
|
+
attention: needs.attention,
|
|
7664
|
+
unresolved: needs.unresolved,
|
|
7665
|
+
unresolvedSpend: needs.unresolvedSpend,
|
|
7666
|
+
toReview: toReview.kind === 'ok' ? toReview.value : null,
|
|
7373
7667
|
});
|
|
7374
|
-
return assembleSnapshot({ startedAt, clis, runs, infra, governance, attention });
|
|
7375
7668
|
});
|
|
7376
7669
|
registerInteractiveProxy(app, adapter, {
|
|
7377
7670
|
settings: projectSettings,
|
|
@@ -7470,6 +7763,9 @@ runtime = {}) {
|
|
|
7470
7763
|
log: (m) => app.log.warn(m),
|
|
7471
7764
|
});
|
|
7472
7765
|
registerSearchRoutes(app, search);
|
|
7473
|
-
|
|
7766
|
+
const runEvalsPrepare = async (input) => {
|
|
7767
|
+
await prepareRunEvalsLaunch(input, () => runAndRecord(defaultEvalDeps, { project: null, baseline: false, id: randomUUID(), now: new Date() }), runtime.evalRunsDir);
|
|
7768
|
+
};
|
|
7769
|
+
return { decideGate, decisions, chatRecorder, considerations, search, decorateRun, runEvalsPrepare };
|
|
7474
7770
|
}
|
|
7475
7771
|
//# sourceMappingURL=routes.js.map
|