wicked-crew 0.11.0 → 0.12.1
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 +7 -3
- package/dist/api/ask-paths.d.ts.map +1 -1
- package/dist/api/ask-paths.js +2 -1
- package/dist/api/ask-paths.js.map +1 -1
- package/dist/api/capture.d.ts +2 -2
- package/dist/api/chat-scope.d.ts +14 -3
- package/dist/api/chat-scope.d.ts.map +1 -1
- package/dist/api/chat-scope.js +27 -5
- package/dist/api/chat-scope.js.map +1 -1
- package/dist/api/delivery-assurance.d.ts +1 -6
- package/dist/api/delivery-assurance.d.ts.map +1 -1
- package/dist/api/delivery-assurance.js +7 -14
- package/dist/api/delivery-assurance.js.map +1 -1
- package/dist/api/governance-steering.d.ts +2 -2
- package/dist/api/mcp.d.ts +4 -4
- package/dist/api/recording.d.ts +29 -3
- package/dist/api/recording.d.ts.map +1 -1
- package/dist/api/recording.js +188 -0
- package/dist/api/recording.js.map +1 -1
- package/dist/api/roster-standing.d.ts +21 -2
- package/dist/api/roster-standing.d.ts.map +1 -1
- package/dist/api/roster-standing.js +108 -9
- package/dist/api/roster-standing.js.map +1 -1
- package/dist/api/routes.d.ts +112 -112
- package/dist/api/routes.d.ts.map +1 -1
- package/dist/api/routes.js +203 -65
- package/dist/api/routes.js.map +1 -1
- package/dist/api/seat-probe.d.ts +7 -0
- package/dist/api/seat-probe.d.ts.map +1 -1
- package/dist/api/seat-probe.js +27 -5
- package/dist/api/seat-probe.js.map +1 -1
- package/dist/api/seat-version.d.ts +8 -0
- package/dist/api/seat-version.d.ts.map +1 -1
- package/dist/api/seat-version.js +36 -4
- package/dist/api/seat-version.js.map +1 -1
- package/dist/api/skills.d.ts.map +1 -1
- package/dist/api/skills.js +4 -5
- package/dist/api/skills.js.map +1 -1
- package/dist/api/testing.d.ts +4 -4
- package/dist/campaigns/routes.d.ts +2 -2
- package/dist/core/adapter.d.ts +25 -0
- package/dist/core/adapter.d.ts.map +1 -1
- package/dist/core/adapter.js +121 -5
- package/dist/core/adapter.js.map +1 -1
- package/dist/core/builtin-catalog.d.ts.map +1 -1
- package/dist/core/builtin-catalog.js +6 -1
- package/dist/core/builtin-catalog.js.map +1 -1
- package/dist/core/deliver-text.d.ts +5 -1
- package/dist/core/deliver-text.d.ts.map +1 -1
- package/dist/core/deliver-text.js +14 -1
- package/dist/core/deliver-text.js.map +1 -1
- package/dist/core/qe-acceptance-setting.d.ts +65 -0
- package/dist/core/qe-acceptance-setting.d.ts.map +1 -0
- package/dist/core/qe-acceptance-setting.js +93 -0
- package/dist/core/qe-acceptance-setting.js.map +1 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js +3 -0
- package/dist/core/types.js.map +1 -1
- package/dist/editors/pack-install.d.ts +30 -3
- package/dist/editors/pack-install.d.ts.map +1 -1
- package/dist/editors/pack-install.js +59 -4
- package/dist/editors/pack-install.js.map +1 -1
- package/dist/events/terminals.d.ts +42 -2
- package/dist/events/terminals.d.ts.map +1 -1
- package/dist/events/terminals.js +136 -3
- package/dist/events/terminals.js.map +1 -1
- package/dist/interactive/chat-events.d.ts.map +1 -1
- package/dist/interactive/chat-events.js +6 -1
- package/dist/interactive/chat-events.js.map +1 -1
- package/dist/interactive/draft-events.d.ts.map +1 -1
- package/dist/interactive/draft-events.js +6 -1
- package/dist/interactive/draft-events.js.map +1 -1
- package/dist/interactive/edit-events.d.ts.map +1 -1
- package/dist/interactive/edit-events.js +6 -1
- package/dist/interactive/edit-events.js.map +1 -1
- package/dist/projects/state-home-preflight.d.ts +5 -1
- package/dist/projects/state-home-preflight.d.ts.map +1 -1
- package/dist/projects/state-home-preflight.js +43 -13
- package/dist/projects/state-home-preflight.js.map +1 -1
- package/dist/skills/bundle.d.ts +2 -0
- package/dist/skills/bundle.d.ts.map +1 -1
- package/dist/skills/bundle.js +9 -2
- package/dist/skills/bundle.js.map +1 -1
- package/dist/skills/installed.d.ts +8 -1
- package/dist/skills/installed.d.ts.map +1 -1
- package/dist/skills/installed.js +25 -4
- package/dist/skills/installed.js.map +1 -1
- package/dist/skills/plugin-source.d.ts +2 -0
- package/dist/skills/plugin-source.d.ts.map +1 -1
- package/dist/skills/plugin-source.js +17 -1
- package/dist/skills/plugin-source.js.map +1 -1
- package/dist/skills/runtime.d.ts +24 -4
- package/dist/skills/runtime.d.ts.map +1 -1
- package/dist/skills/runtime.js +67 -30
- package/dist/skills/runtime.js.map +1 -1
- package/dist/skills/store.d.ts +2 -0
- package/dist/skills/store.d.ts.map +1 -1
- package/dist/skills/store.js +6 -1
- package/dist/skills/store.js.map +1 -1
- package/dist/skills/tree.d.ts +4 -0
- package/dist/skills/tree.d.ts.map +1 -1
- package/dist/skills/tree.js +39 -1
- package/dist/skills/tree.js.map +1 -1
- package/dist/studio/assets/index-CRbl0LAp.js +770 -0
- package/dist/studio/assets/index-D1WKb58Z.css +32 -0
- package/dist/studio/editors/wicked-deck/editor.json +56 -3
- package/dist/studio/editors/wicked-deck/index.html +53 -7
- package/dist/studio/editors/wicked-doc/editor.json +91 -0
- package/dist/studio/editors/wicked-doc/index.html +161 -0
- package/dist/studio/editors/wicked-page/editor.json +42 -4
- package/dist/studio/editors/wicked-page/index.html +24 -7
- package/dist/studio/editors/wicked-video/editor.json +271 -0
- package/dist/studio/editors/wicked-video/index.html +28 -0
- package/dist/studio/index.html +2 -2
- package/dist/studio/testid-inventory.json +763 -122
- package/dist/team/routes.d.ts +2 -2
- package/endpoint-manifest.json +14 -1
- package/package.json +3 -3
- package/dist/studio/assets/index-BSPM1fFW.css +0 -32
- package/dist/studio/assets/index-R8uK_NlG.js +0 -848
package/dist/api/routes.js
CHANGED
|
@@ -48,7 +48,8 @@ import { isSteeringAuthorRun, landSteeringProposal } from './steering-landing.js
|
|
|
48
48
|
import { inventoryComplete, parseInventoryClaims } from '../core/inventory-claim.js';
|
|
49
49
|
import { ApproveEditSchema, approveEdited, captureLaunchRoots, editInFlight, FileProposalSchema, registerCaptureRoutes } from './capture.js';
|
|
50
50
|
import { registerTestingRoutes } from './testing.js';
|
|
51
|
-
import { DEMO_PRESET, demoLaunchRoots, registerDemoRoutes, registerWalkthroughRoutes, walkthroughAcceptance } from './recording.js';
|
|
51
|
+
import { DEMO_PRESET, demoLaunchRoots, registerDemoDocumentRoute, registerDemoRoutes, registerWalkthroughRoutes, walkthroughAcceptance } from './recording.js';
|
|
52
|
+
import { projectDocsRoot } from '../interactive/project-root.js';
|
|
52
53
|
import { registerSkillsRoutes } from './skills.js';
|
|
53
54
|
import { registerMcpRoutes } from './mcp.js';
|
|
54
55
|
import { McpPolicies } from '../mcp/policies.js';
|
|
@@ -59,7 +60,8 @@ import { BASE_SKILL_POLICIES, BASE_SKILL_REF_SHAPE, baseSkillRemedy } from '../s
|
|
|
59
60
|
import { REQUIRED_GARDEN_VERSION } from '../skills/plugin-source.js';
|
|
60
61
|
import { engineRosterJson, noEligibleSeatBody, parseNoEligibleSeat } from '../core/engine-roster.js';
|
|
61
62
|
import { launchAssuranceNotice } from '../core/assurance.js';
|
|
62
|
-
import {
|
|
63
|
+
import { launchRequiresQe, mayRequireQe, qeAcceptanceMode, qeSettingSkipReason } from '../core/qe-acceptance-setting.js';
|
|
64
|
+
import { aggregateReceipt, postHocDeliveryAssurance, qeAcceptanceFromView, qeAcceptanceRefusal, QE_ACCEPTANCE_REQUIRED_CODE, receiptOf, qeAcceptanceNotRequired, qeDecisionBypasses, qeDecisionOf, unreadableQeAcceptance, } from './delivery-assurance.js';
|
|
63
65
|
import { ProjectSettingsStore } from '../projects/settings.js';
|
|
64
66
|
import { isHumanOperator } from './watch-routes.js';
|
|
65
67
|
import { boundOrigin, InteractiveBridgePool } from '../interactive/bridge-pool.js';
|
|
@@ -1079,6 +1081,9 @@ runtime = {}) {
|
|
|
1079
1081
|
// spellings where the contract has no name, statusCodes list every code the route answers on
|
|
1080
1082
|
// purpose. Declared on the highest-traffic run-lifecycle routes first; the manifest records
|
|
1081
1083
|
// null / [] for the rest ("where declared", never invented). See src/api/endpoint-manifest.ts.
|
|
1084
|
+
// The daemon's QE acceptance setting as read now ('required' when the settings cannot be read:
|
|
1085
|
+
// fail closed, never off by accident).
|
|
1086
|
+
const qeAcceptanceNow = async () => qeAcceptanceMode(await Promise.resolve().then(() => adapter.getSettings()).catch(() => null));
|
|
1082
1087
|
app.get(`${V}/health`, { config: { manifest: { statusCodes: [200] } } }, async () => {
|
|
1083
1088
|
const ping = await enginePing();
|
|
1084
1089
|
// F-E2E-030: what this deployment can keep. A composer reads `capabilities.deliverGate`
|
|
@@ -1105,6 +1110,9 @@ runtime = {}) {
|
|
|
1105
1110
|
// QE-IN-APP-WORKFLOWS: `LaunchRunBody.skipQeAcceptance` / `forceQeAcceptance` reach an addon
|
|
1106
1111
|
// that carries the QE decision; never an invented capability.
|
|
1107
1112
|
qeAcceptanceOverride: typeof adapter.engineCapabilities === 'function' && addonSupportsQeOverride(),
|
|
1113
|
+
// api-types 0.120.0: the daemon's QE acceptance setting, read per probe ('off' = every launch
|
|
1114
|
+
// that requires QE acceptance skips it, labelled with the setting).
|
|
1115
|
+
qeAcceptance: await qeAcceptanceNow(),
|
|
1108
1116
|
};
|
|
1109
1117
|
// wicked-core#411 / crew#497: the state-home blocker rides the health probe as a WARNING. The
|
|
1110
1118
|
// daemon still SERVES (status stays ok — studio must load and show the blocker) but refuses to
|
|
@@ -1657,6 +1665,11 @@ runtime = {}) {
|
|
|
1657
1665
|
}
|
|
1658
1666
|
return null;
|
|
1659
1667
|
};
|
|
1668
|
+
/**
|
|
1669
|
+
* crew#1019: registration is durable even when onboarding cannot launch (core has no unregister).
|
|
1670
|
+
* A landed local repo returns 201 with `onboardRunId: null` and `onboardError` on launch failure, so
|
|
1671
|
+
* callers can still attach it to their project and retry onboarding separately.
|
|
1672
|
+
*/
|
|
1660
1673
|
app.post(`${V}/repos`, async (req, reply) => {
|
|
1661
1674
|
const parsed = RegisterRepoSchema.safeParse(req.body);
|
|
1662
1675
|
if (!parsed.success) {
|
|
@@ -1672,6 +1685,8 @@ runtime = {}) {
|
|
|
1672
1685
|
if (gitUrl) {
|
|
1673
1686
|
// Remote: clone to rootPath (if provided) or default ~/.wicked/repos/<name>,
|
|
1674
1687
|
// register, and launch onboarding run.
|
|
1688
|
+
// cloneAndRegisterRepo registers and launches in one call. Distinguishing a landed
|
|
1689
|
+
// registration from a failed clone requires splitting that adapter call (follow-up).
|
|
1675
1690
|
const cloned = await adapter.cloneAndRegisterRepo(name, gitUrl, rootPath);
|
|
1676
1691
|
runId = cloned.runId;
|
|
1677
1692
|
const repos = await adapter.listRepos();
|
|
@@ -1686,7 +1701,11 @@ runtime = {}) {
|
|
|
1686
1701
|
}
|
|
1687
1702
|
}
|
|
1688
1703
|
catch (err) {
|
|
1689
|
-
|
|
1704
|
+
if (repo === undefined)
|
|
1705
|
+
return reply.code(400).send({ error: message(err) });
|
|
1706
|
+
req.log.warn({ repoId: repo.id }, `repo registered but onboarding launch failed: ${message(err)}`);
|
|
1707
|
+
// No run exists to record or file, even when projectId was supplied (crew#1019).
|
|
1708
|
+
return reply.code(201).send({ repo, onboardRunId: null, onboardError: message(err) });
|
|
1690
1709
|
}
|
|
1691
1710
|
const projectAttachError = await recordOnboardingLaunch(req, runId, repo.id, { projectId, channel, actor, autoCapture });
|
|
1692
1711
|
return reply.code(201).send({ repo, onboardRunId: runId, ...(projectAttachError !== undefined ? { projectAttachError } : {}) });
|
|
@@ -1823,16 +1842,27 @@ runtime = {}) {
|
|
|
1823
1842
|
// F-E2E-030: only the explicit `'auto'` opts out of the engine's deliver gate. `'human'` and
|
|
1824
1843
|
// an omitted field both leave `autoDeliver` off the input — the gate is the engine's default,
|
|
1825
1844
|
// so the wire never has to say "gate me" to be gated.
|
|
1845
|
+
// The daemon's QE acceptance setting, read ONCE for this launch: the deliverGate check below and
|
|
1846
|
+
// the skip handed to the engine come from the same reading (core/qe-acceptance-setting.ts).
|
|
1847
|
+
// An addon without the QE decision enforces none, so the setting has nothing to skip on it.
|
|
1848
|
+
const qeMode = addonSupportsQeOverride() ? await qeAcceptanceNow() : 'required';
|
|
1849
|
+
const qeRequired = b.deliverGate === 'auto' || qeMode === 'off' ? await launchRequiresQe(adapter, b.workflow, b.projectId) : false;
|
|
1850
|
+
const settingSkip = qeSettingSkipReason(qeMode, { skipQeAcceptanceReason: b.skipQeAcceptance?.reason, forceQeAcceptance: b.forceQeAcceptance }, qeRequired);
|
|
1826
1851
|
if (b.deliverGate === 'auto') {
|
|
1827
1852
|
// wicked-core#850 EX-03: a workflow that requires QE acceptance delivers only through the deliver
|
|
1828
1853
|
// gate, where crew checks the verdict. Unattended delivery would push before anyone could.
|
|
1829
1854
|
// QE-IN-APP-WORKFLOWS: an explicit operator skip leaves no verdict to check, so it may. A name
|
|
1830
1855
|
// the engine resolves to a built-in preset that requires QE acceptance is judged by that preset
|
|
1831
1856
|
// (the engine resolves a preset before any def, so a def of the same name never speaks for it).
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1857
|
+
// The daemon's `qeAcceptance: 'off'` setting skips it the same way (resolved once, above, and
|
|
1858
|
+
// handed to the engine with this launch), unless this launch forces QE acceptance.
|
|
1859
|
+
// Refused on the CONSERVATIVE reading (a built-in QE name or a QE def counts even when a project
|
|
1860
|
+
// preset shadows it now): only an explicit or setting skip lets QE work deliver unattended.
|
|
1861
|
+
if (mayRequireQe(adapter, b.workflow, qeRequired) && b.skipQeAcceptance === undefined && settingSkip === undefined) {
|
|
1862
|
+
// `code` is the same stable token a refused delivery carries (QeAcceptanceRefusal.code), so a
|
|
1863
|
+
// client can say "this failed because QE acceptance is required" without parsing the sentence.
|
|
1835
1864
|
return reply.code(400).send({
|
|
1865
|
+
code: QE_ACCEPTANCE_REQUIRED_CODE,
|
|
1836
1866
|
error: `workflow ${b.workflow} requires QE acceptance (required_instruments: qe_acceptance), so it delivers only ` +
|
|
1837
1867
|
'through the deliver gate, where the verdict is checked — deliverGate: "auto" cannot apply to it ' +
|
|
1838
1868
|
'(unless the launch explicitly skips QE acceptance with a reason: skipQeAcceptance)',
|
|
@@ -1913,6 +1943,8 @@ runtime = {}) {
|
|
|
1913
1943
|
// when sent, never inferred.
|
|
1914
1944
|
if (b.skipQeAcceptance !== undefined)
|
|
1915
1945
|
input.skipQeAcceptanceReason = b.skipQeAcceptance.reason;
|
|
1946
|
+
else if (settingSkip !== undefined)
|
|
1947
|
+
input.skipQeAcceptanceReason = settingSkip;
|
|
1916
1948
|
if (b.forceQeAcceptance === true)
|
|
1917
1949
|
input.forceQeAcceptance = true;
|
|
1918
1950
|
// DES-L9 / crew#550 — REVISION: `revisesPr` names an OPEN same-repository pull request whose
|
|
@@ -2096,7 +2128,7 @@ runtime = {}) {
|
|
|
2096
2128
|
const freeText = input.workflow === undefined && input.plan === undefined ? { freeText: { notice: FREE_TEXT_NOTICE } } : {};
|
|
2097
2129
|
// crew#874: the run uses the PUBLISHED generation; when the installed garden is not the one it
|
|
2098
2130
|
// was built from, the launch answer says so (a recorded proof can disclose which garden ran).
|
|
2099
|
-
const skillsAhead = runtime.skills?.installedAheadFinding({ fresh: true }) ?? null; // fresh: an upgrade just before Send counts (codex r1)
|
|
2131
|
+
const skillsAhead = await runtime.skills?.installedAheadFinding({ fresh: true }) ?? null; // fresh: an upgrade just before Send counts (codex r1)
|
|
2100
2132
|
const skillsWarning = skillsAhead === null ? {} : { skillsWarning: { kind: skillsAhead.kind, severity: skillsAhead.severity, message: skillsAhead.message } };
|
|
2101
2133
|
// wicked-core#850: a full-assurance run on a one-CLI roster will wait at a gate for a seat that
|
|
2102
2134
|
// can evaluate or judge it; the answer names the explicit opt-in to relaunch with.
|
|
@@ -2669,6 +2701,14 @@ runtime = {}) {
|
|
|
2669
2701
|
// READ-ONLY estate MCP over it (the grounding governed runs get). The scope is resolved and
|
|
2670
2702
|
// validated BEFORE any seat warms, stated to the seats in the scratch root's AGENTS.md /
|
|
2671
2703
|
// CLAUDE.md, and returned as `scope` (see `chat-scope.ts`).
|
|
2704
|
+
// crew#1020: open and refresh use the same registry, graph, liveness and checkout dependencies.
|
|
2705
|
+
const resolveScope = (request, req) => resolveChatScope(request, {
|
|
2706
|
+
...chatScopeDeps(adapter),
|
|
2707
|
+
scratchBase: chatScopes.base,
|
|
2708
|
+
log: (m) => req.log.warn(m),
|
|
2709
|
+
...(runtime.entityCount !== undefined ? { entityCount: runtime.entityCount } : {}),
|
|
2710
|
+
...(runtime.freshenCheckout !== undefined ? { freshen: runtime.freshenCheckout } : {}),
|
|
2711
|
+
});
|
|
2672
2712
|
app.post(`${V}/chats`, {
|
|
2673
2713
|
config: {
|
|
2674
2714
|
manifest: {
|
|
@@ -2731,18 +2771,13 @@ runtime = {}) {
|
|
|
2731
2771
|
// The scope (crew#502): explicit repos (`repoRefs`, the legacy `repoRef` merged in) or the
|
|
2732
2772
|
// project's members — each ref checked against the registry, EVERY missing one named — else
|
|
2733
2773
|
// none. Resolved before any seat warms: a 404 here warmed nothing.
|
|
2734
|
-
const
|
|
2774
|
+
const scopeRequest = {
|
|
2735
2775
|
chatId,
|
|
2736
2776
|
...(b.projectId !== undefined ? { projectId: b.projectId } : {}),
|
|
2737
2777
|
repoRefs: requestedRefs,
|
|
2738
2778
|
...(b.scopeKind !== undefined ? { kind: b.scopeKind } : {}),
|
|
2739
|
-
}
|
|
2740
|
-
|
|
2741
|
-
scratchBase: chatScopes.base,
|
|
2742
|
-
log: (m) => req.log.warn(m),
|
|
2743
|
-
...(runtime.entityCount !== undefined ? { entityCount: runtime.entityCount } : {}),
|
|
2744
|
-
...(runtime.freshenCheckout !== undefined ? { freshen: runtime.freshenCheckout } : {}),
|
|
2745
|
-
});
|
|
2779
|
+
};
|
|
2780
|
+
const resolution = await resolveScope(scopeRequest, req);
|
|
2746
2781
|
if (!resolution.ok) {
|
|
2747
2782
|
chatScopes.release(chatId, token);
|
|
2748
2783
|
return reply.code(resolution.status).send({
|
|
@@ -2871,7 +2906,7 @@ runtime = {}) {
|
|
|
2871
2906
|
projectAttachError = message(err);
|
|
2872
2907
|
}
|
|
2873
2908
|
}
|
|
2874
|
-
if (!chatScopes.set(chatId, scope, token, refused, engine)) {
|
|
2909
|
+
if (!chatScopes.set(chatId, scope, token, refused, engine, scopeRequest)) {
|
|
2875
2910
|
// The reservation was cancelled while the open was in flight — an engine `chatClosed` or a
|
|
2876
2911
|
// `DELETE` for this id (Copilot, #518). Nothing was recorded; tear the chat down (scratch
|
|
2877
2912
|
// root and the filing just made) instead of returning a stale 201. No path record exists
|
|
@@ -2983,24 +3018,20 @@ runtime = {}) {
|
|
|
2983
3018
|
// later message CONTINUES the same path: `propose_plan(answer-N)` then `confirm_gate(Approve)`
|
|
2984
3019
|
// at the turn gate the previous answer paused on. Nothing is fanned out: the PA is the one
|
|
2985
3020
|
// voice, helpers talk to the PA, the reviewer raises quiet findings — all on the bus.
|
|
2986
|
-
const
|
|
2987
|
-
if (
|
|
3021
|
+
const openedPath = askPaths.get(id);
|
|
3022
|
+
if (openedPath === undefined) {
|
|
2988
3023
|
return reply.code(409).send({ error: `chat '${id}' is not open on this daemon — open it first (POST /chats)` });
|
|
2989
3024
|
}
|
|
3025
|
+
const path = openedPath;
|
|
2990
3026
|
if (path.eligible.length === 0) {
|
|
2991
3027
|
return reply.code(409).send({ error: `chat '${id}' has no eligible seat — sign a seat in and re-seat it (POST /chats/:id/seats)` });
|
|
2992
3028
|
}
|
|
2993
3029
|
const text = parsed.data.text;
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
const voice = path.pa !== null ? [path.pa] : [];
|
|
3000
|
-
// The turn is reserved for the voice — or, for a random pick (no PA yet), for the whole
|
|
3001
|
-
// eligible roster: the relay narrows it to the PA when `path.started` names one (ASK-C2), so
|
|
3002
|
-
// the PA's frames are stamped with this turn and a second message is refused meanwhile.
|
|
3003
|
-
const audience = voice.length > 0 ? voice : path.eligible;
|
|
3030
|
+
let scope = chatScopes.get(id);
|
|
3031
|
+
const storedScope = scope;
|
|
3032
|
+
let engine = chatScopes.engineOf(id);
|
|
3033
|
+
let voice = [];
|
|
3034
|
+
let audience = [];
|
|
3004
3035
|
// crew#641/#650 re-stated for a path: ONE eligible seat means no distinct reviewer and no
|
|
3005
3036
|
// helper — said on every turn (the engine's `member.joined{seat:null}` says it on the bus).
|
|
3006
3037
|
const singleSeat202 = path.eligible.length === 1
|
|
@@ -3026,8 +3057,8 @@ runtime = {}) {
|
|
|
3026
3057
|
});
|
|
3027
3058
|
}
|
|
3028
3059
|
// crew#826: the turn lives as long as its answer step may (`budget_secs`), not 3× the pool knob.
|
|
3029
|
-
|
|
3030
|
-
|
|
3060
|
+
let turn = null;
|
|
3061
|
+
let turnId = randomUUID();
|
|
3031
3062
|
// What an ACCEPTED message records — the engine has it, so a persistence failure here is
|
|
3032
3063
|
// logged, never a retraction of a turn that is already being answered (codex on #808 r2, 9).
|
|
3033
3064
|
const persistAccepted = (preface, after) => {
|
|
@@ -3055,12 +3086,97 @@ runtime = {}) {
|
|
|
3055
3086
|
// preface rode a pool send; told only once the send is accepted (`commit`).
|
|
3056
3087
|
const withPreface = (preface) => (preface !== null ? `${preface}\n\n${text}` : text);
|
|
3057
3088
|
try {
|
|
3089
|
+
const rules = (await considerations.inForceFor(scope?.projectId ?? null)).rules;
|
|
3090
|
+
// crew#1020: a project/everything chat must see repos attached since open. Admission was
|
|
3091
|
+
// under this scope KIND and the kind stays the same, so do not re-run seat admission.
|
|
3092
|
+
const scopeRequest = chatScopes.requestOf(id);
|
|
3093
|
+
if (scope !== undefined && (scope.kind === 'project' || scope.kind === 'everything') && scopeRequest !== undefined) {
|
|
3094
|
+
try {
|
|
3095
|
+
if (scopeRequest.projectId !== undefined && await adapter.projectGet(scopeRequest.projectId) === null) {
|
|
3096
|
+
throw new Error(`Project ${scopeRequest.projectId} not found`);
|
|
3097
|
+
}
|
|
3098
|
+
const resolution = await resolveScope(scopeRequest, req);
|
|
3099
|
+
if (!resolution.ok)
|
|
3100
|
+
throw new Error(resolution.error);
|
|
3101
|
+
// Retain this chat's scratch root even if the resolver's defaults change (crew#1020).
|
|
3102
|
+
// Resolve only: a refused message must leave the stored scope, executing seats' files
|
|
3103
|
+
// and transcript roots untouched. Publish after every refusal check, just before send.
|
|
3104
|
+
engine = { ...resolution.engine, cwd: scope.cwd };
|
|
3105
|
+
scope = { ...resolution.scope, cwd: scope.cwd };
|
|
3106
|
+
}
|
|
3107
|
+
catch (err) {
|
|
3108
|
+
req.log.warn({ chatId: id }, `chat scope refresh failed; keeping stored scope: ${message(err)}`);
|
|
3109
|
+
}
|
|
3110
|
+
}
|
|
3111
|
+
if (askPaths.get(id)?.generation !== lease) {
|
|
3112
|
+
return reply.code(409).send({ error: `chat '${id}' was closed while the message was in flight; nothing was sent` });
|
|
3113
|
+
}
|
|
3114
|
+
if (path.runId !== undefined) {
|
|
3115
|
+
const view = (await adapter.sessionsDetail()).find((v) => v.session.id === path.runId);
|
|
3116
|
+
if (view === undefined) {
|
|
3117
|
+
askPaths.release(id, lease);
|
|
3118
|
+
return reply.code(409).send({ error: `chat '${id}': its path ${path.runId} is gone — open a new chat` });
|
|
3119
|
+
}
|
|
3120
|
+
const status = String(view.session.status);
|
|
3121
|
+
if (status !== 'awaiting_human') {
|
|
3122
|
+
askPaths.release(id, lease);
|
|
3123
|
+
const who = path.pa ?? 'the primary agent';
|
|
3124
|
+
return reply.code(409).send({
|
|
3125
|
+
code: 'turn_in_flight',
|
|
3126
|
+
error: status === 'executing' || status === 'running'
|
|
3127
|
+
? `${who} is still answering the previous message in this chat — wait for the reply. Nothing was sent.`
|
|
3128
|
+
: `chat '${id}': its path ${path.runId} is ${status}, not waiting for your turn — nothing was sent`,
|
|
3129
|
+
chatId: id,
|
|
3130
|
+
runId: path.runId,
|
|
3131
|
+
status,
|
|
3132
|
+
});
|
|
3133
|
+
}
|
|
3134
|
+
// The status read awaited: the chat may have been closed (and its id reopened) meanwhile —
|
|
3135
|
+
// nothing of this message may land on the newcomer (codex on #808 r3, 2).
|
|
3136
|
+
if (askPaths.get(id)?.generation !== lease) {
|
|
3137
|
+
return reply.code(409).send({ error: `chat '${id}' was closed while the message was in flight; nothing was sent` });
|
|
3138
|
+
}
|
|
3139
|
+
}
|
|
3140
|
+
const codeGraphDb = engine?.codeGraphDb ?? null;
|
|
3141
|
+
// Only project scopes refresh a project graph; fixed repo scopes may use the engine's
|
|
3142
|
+
// own repo graph instead of input.projectGraph, and everything scopes have no graph.
|
|
3143
|
+
if (scope?.kind === 'project' && path.runId !== undefined && codeGraphDb !== path.launchGraphBinding) {
|
|
3144
|
+
askPaths.release(id, lease);
|
|
3145
|
+
return reply.code(409).send({
|
|
3146
|
+
code: 'chat_scope_changed',
|
|
3147
|
+
error: `This chat's repositories or their code graph changed since it started (${scope?.repos.length ?? 0} now in scope). Start a new chat to read them — this one keeps its original scope.`,
|
|
3148
|
+
});
|
|
3149
|
+
}
|
|
3150
|
+
// Sort repository identities so membership order alone does not trigger another statement.
|
|
3151
|
+
const scopeKey = JSON.stringify([
|
|
3152
|
+
scope?.repos.map((repo) => [repo.id, repo.name, repo.rootPath]).sort((a, b) => a[0].localeCompare(b[0])) ?? [],
|
|
3153
|
+
codeGraphDb,
|
|
3154
|
+
]);
|
|
3155
|
+
const pending = await considerations.pendingPreface(id, { inForce: rules });
|
|
3156
|
+
// The last await before delivery may also have closed/reopened the chat. From this check
|
|
3157
|
+
// through publication and the send there is no await, so no executing turn's files change.
|
|
3158
|
+
if (askPaths.get(id)?.generation !== lease || chatScopes.get(id) !== storedScope) {
|
|
3159
|
+
return reply.code(409).send({ error: `chat '${id}' was closed while the message was in flight; nothing was sent` });
|
|
3160
|
+
}
|
|
3161
|
+
if (storedScope !== undefined && scope !== undefined && engine !== undefined &&
|
|
3162
|
+
(JSON.stringify(scope) !== JSON.stringify(storedScope) || JSON.stringify(engine) !== JSON.stringify(chatScopes.engineOf(id)))) {
|
|
3163
|
+
prepareChatScratch(id, scope, rules);
|
|
3164
|
+
if (chatScopes.refresh(id, storedScope, scope, engine)) {
|
|
3165
|
+
chatTranscripts?.registerRoots(id, scope.repos.map((r) => ({ absRoot: resolve(r.rootPath), name: r.name })));
|
|
3166
|
+
}
|
|
3167
|
+
}
|
|
3168
|
+
voice = path.pa !== null ? [path.pa] : [];
|
|
3169
|
+
// A random pick reserves every eligible seat until the relay names the PA.
|
|
3170
|
+
audience = voice.length > 0 ? voice : path.eligible;
|
|
3171
|
+
turn = chatTurns.begin(id, audience, text, ASK_TURN_BUDGET_SECS);
|
|
3172
|
+
turnId = turn?.turnId ?? turnId;
|
|
3173
|
+
// A repo-bound path can deliver; project and multi-repo scopes remain read-only asks.
|
|
3174
|
+
const repoRef = scope?.kind === 'repos' && scope.repos.length === 1 ? scope.repos[0].id : undefined;
|
|
3175
|
+
const projectId = scope?.projectId;
|
|
3058
3176
|
if (path.runId === undefined) {
|
|
3059
3177
|
const stepId = 'answer-1';
|
|
3060
3178
|
const generation = path.generation;
|
|
3061
3179
|
const roster = rosterWithStanding().filter((seat) => path.eligible.includes(String(seat.key)));
|
|
3062
|
-
const rules = (await considerations.inForceFor(scope?.projectId ?? null)).rules;
|
|
3063
|
-
const pending = await considerations.pendingPreface(id, { inForce: rules });
|
|
3064
3180
|
// crew#823: the run is titled by the operator's question (every reader titles a run from
|
|
3065
3181
|
// `problem`); the scope statement grounds the seats from the first answer step's
|
|
3066
3182
|
// instructions instead of standing in as the run's title.
|
|
@@ -3073,15 +3189,17 @@ runtime = {}) {
|
|
|
3073
3189
|
...(path.primary !== undefined ? { primary: path.primary } : {}),
|
|
3074
3190
|
...(repoRef !== undefined ? { repoRef, deliver: 'pr' } : {}),
|
|
3075
3191
|
...(projectId !== undefined ? { projectId } : {}),
|
|
3192
|
+
...(scope?.kind === 'project' && codeGraphDb !== null ? { projectGraph: { dbPath: codeGraphDb } } : {}),
|
|
3076
3193
|
};
|
|
3077
3194
|
const runId = await adapter.launchRun(input);
|
|
3078
|
-
if (!askPaths.started(id, generation, runId, stepId)) {
|
|
3195
|
+
if (!askPaths.started(id, generation, runId, stepId, input.projectGraph?.dbPath ?? null)) {
|
|
3079
3196
|
// The chat was closed (End) while the launch was in flight: the run must not outlive it.
|
|
3080
3197
|
await adapter.cancelRun(runId).catch(() => undefined);
|
|
3081
3198
|
if (turn !== null)
|
|
3082
3199
|
chatTurns.abort(id, turn.turnId);
|
|
3083
3200
|
return reply.code(409).send({ error: `chat '${id}' was closed while its path was launching; the run was cancelled` });
|
|
3084
3201
|
}
|
|
3202
|
+
path.lastToldScopeKey = scopeKey;
|
|
3085
3203
|
pending.commit();
|
|
3086
3204
|
persistAccepted(pending.preface, () => {
|
|
3087
3205
|
runtime.linkChatRun?.(id, runId);
|
|
@@ -3100,47 +3218,24 @@ runtime = {}) {
|
|
|
3100
3218
|
deliver: repoRef !== undefined ? 'pr' : 'none',
|
|
3101
3219
|
});
|
|
3102
3220
|
});
|
|
3103
|
-
return reply.code(202).send({
|
|
3104
|
-
|
|
3105
|
-
const view = (await adapter.sessionsDetail()).find((v) => v.session.id === path.runId);
|
|
3106
|
-
if (view === undefined) {
|
|
3107
|
-
askPaths.release(id, lease);
|
|
3108
|
-
if (turn !== null)
|
|
3109
|
-
chatTurns.abort(id, turn.turnId);
|
|
3110
|
-
return reply.code(409).send({ error: `chat '${id}': its path ${path.runId} is gone — open a new chat` });
|
|
3111
|
-
}
|
|
3112
|
-
const status = String(view.session.status);
|
|
3113
|
-
if (status !== 'awaiting_human') {
|
|
3114
|
-
askPaths.release(id, lease);
|
|
3115
|
-
if (turn !== null)
|
|
3116
|
-
chatTurns.abort(id, turn.turnId);
|
|
3117
|
-
const who = path.pa ?? 'the primary agent';
|
|
3118
|
-
return reply.code(409).send({
|
|
3119
|
-
code: 'turn_in_flight',
|
|
3120
|
-
error: status === 'executing' || status === 'running'
|
|
3121
|
-
? `${who} is still answering the previous message in this chat — wait for the reply. Nothing was sent.`
|
|
3122
|
-
: `chat '${id}': its path ${path.runId} is ${status}, not waiting for your turn — nothing was sent`,
|
|
3123
|
-
chatId: id,
|
|
3124
|
-
runId: path.runId,
|
|
3125
|
-
status,
|
|
3221
|
+
return reply.code(202).send({
|
|
3222
|
+
seats: voice, turnId, runId, stepId, ...singleSeat202,
|
|
3126
3223
|
});
|
|
3127
3224
|
}
|
|
3128
|
-
// The status read awaited: the chat may have been closed (and its id reopened) meanwhile —
|
|
3129
|
-
// nothing of this message may land on the newcomer (codex on #808 r3, 2).
|
|
3130
|
-
if (askPaths.get(id)?.generation !== lease) {
|
|
3131
|
-
if (turn !== null)
|
|
3132
|
-
chatTurns.abort(id, turn.turnId);
|
|
3133
|
-
return reply.code(409).send({ error: `chat '${id}' was closed while the message was in flight; nothing was sent` });
|
|
3134
|
-
}
|
|
3135
3225
|
const stepId = askPaths.nextStep(id);
|
|
3136
|
-
|
|
3137
|
-
|
|
3226
|
+
// crew#1020: tell the run about a refreshed scope until a continuation accepts it.
|
|
3227
|
+
let instructions = withPreface(pending.preface);
|
|
3228
|
+
if (scope !== undefined && scopeKey !== path.lastToldScopeKey) {
|
|
3229
|
+
instructions = `${chatScopeStatement(id, scope, rules)}\n\n${instructions}`;
|
|
3230
|
+
}
|
|
3231
|
+
await adapter.proposePlan(path.runId, { steps: [answerStep(stepId, instructions)] }, turnId);
|
|
3138
3232
|
await adapter.confirmGate(path.runId, true);
|
|
3139
3233
|
askPaths.release(id, lease);
|
|
3140
3234
|
if (!askPaths.continued(id, lease, stepId)) {
|
|
3141
3235
|
// Closed between the proposal and here: the DELETE cancelled the run this was accepted on.
|
|
3142
3236
|
return reply.code(409).send({ error: `chat '${id}' was closed while the message was in flight; its path was cancelled` });
|
|
3143
3237
|
}
|
|
3238
|
+
path.lastToldScopeKey = scopeKey;
|
|
3144
3239
|
pending.commit();
|
|
3145
3240
|
persistAccepted(pending.preface, () => undefined);
|
|
3146
3241
|
return reply.code(202).send({ seats: voice, turnId, runId: path.runId, stepId, ...singleSeat202 });
|
|
@@ -5438,6 +5533,19 @@ runtime = {}) {
|
|
|
5438
5533
|
.send({ error: "deliverDefault must be 'pr' or 'none'" });
|
|
5439
5534
|
}
|
|
5440
5535
|
}
|
|
5536
|
+
// qeAcceptance (api-types 0.120.0): 'required' | 'off' only. 'off' skips QE acceptance on every
|
|
5537
|
+
// launch that requires it (labelled with the setting), so a typo must be a 400, never a dropped key.
|
|
5538
|
+
// Turning it off waives an assurance instrument on every launch after it, so only a PERSON may
|
|
5539
|
+
// change it (the predicate the watch switches use); a worker's bearer gets 403.
|
|
5540
|
+
if (Object.hasOwn(patch, 'qeAcceptance')) {
|
|
5541
|
+
const q = patch.qeAcceptance;
|
|
5542
|
+
if (q !== 'required' && q !== 'off') {
|
|
5543
|
+
return reply.code(400).send({ error: "qeAcceptance must be 'required' or 'off'" });
|
|
5544
|
+
}
|
|
5545
|
+
if (!isHumanOperator(actorOf(req))) {
|
|
5546
|
+
return reply.code(403).send({ error: 'Only a person can change QE acceptance (qeAcceptance); a worker or system actor cannot' });
|
|
5547
|
+
}
|
|
5548
|
+
}
|
|
5441
5549
|
// onboardingAutoCapture (crew#552): whether a completed onboarding chains capture-learnings.
|
|
5442
5550
|
if (Object.hasOwn(patch, 'onboardingAutoCapture') && typeof patch.onboardingAutoCapture !== 'boolean') {
|
|
5443
5551
|
return reply.code(400).send({ error: 'onboardingAutoCapture must be true or false' });
|
|
@@ -5522,6 +5630,7 @@ runtime = {}) {
|
|
|
5522
5630
|
'workerStallEscalateAction',
|
|
5523
5631
|
'workerStallMaxEscalations',
|
|
5524
5632
|
'deliverDefault',
|
|
5633
|
+
'qeAcceptance',
|
|
5525
5634
|
'onboardingAutoCapture',
|
|
5526
5635
|
'deliverIdentityLogin',
|
|
5527
5636
|
'deliverIdentityByRepo',
|
|
@@ -6195,6 +6304,35 @@ runtime = {}) {
|
|
|
6195
6304
|
dropDocLedgerRows: runtime.dropDocLedgerRows ?? (() => ({ ok: true, removed_keys: [] })),
|
|
6196
6305
|
log: (m) => app.log.warn(m),
|
|
6197
6306
|
});
|
|
6307
|
+
// ── A demo run's video as an editable document (the video editor re-cuts it through E1's per-doc API) ──
|
|
6308
|
+
registerDemoDocumentRoute(app, adapter, {
|
|
6309
|
+
bridgeFor: async (projectId) => {
|
|
6310
|
+
const root = await projectDocsRoot(adapter, projectSettings, projectId);
|
|
6311
|
+
if (root === null)
|
|
6312
|
+
return null;
|
|
6313
|
+
const bridge = await interactiveBridges.ensure(root);
|
|
6314
|
+
return async (method, path, body) => {
|
|
6315
|
+
const raw = Buffer.isBuffer(body);
|
|
6316
|
+
const res = await fetch(`http://${bridge.host}:${bridge.port}${path}`, {
|
|
6317
|
+
method,
|
|
6318
|
+
headers: { 'content-type': raw ? 'application/octet-stream' : 'application/json' },
|
|
6319
|
+
body: raw ? new Uint8Array(body) : JSON.stringify(body),
|
|
6320
|
+
// A segment upload or the import's render (ffmpeg) can take minutes on a loaded host.
|
|
6321
|
+
signal: AbortSignal.timeout(600_000),
|
|
6322
|
+
});
|
|
6323
|
+
let parsed = {};
|
|
6324
|
+
try {
|
|
6325
|
+
const j = await res.json();
|
|
6326
|
+
if (j !== null && typeof j === 'object' && !Array.isArray(j))
|
|
6327
|
+
parsed = j;
|
|
6328
|
+
}
|
|
6329
|
+
catch {
|
|
6330
|
+
parsed = {};
|
|
6331
|
+
}
|
|
6332
|
+
return { status: res.status, body: parsed };
|
|
6333
|
+
};
|
|
6334
|
+
},
|
|
6335
|
+
});
|
|
6198
6336
|
// ── Project-attributed docs list (crew#472) ──
|
|
6199
6337
|
// `GET /projects/:id/interactive/api/docs` relays the bridge's list and stamps each row with
|
|
6200
6338
|
// the mount's `projectId` — the attribution the bridge cannot know. Same static-over-wildcard
|