wicked-crew 0.8.6 → 0.8.8
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 +18 -1
- package/dist/api/ask-paths.d.ts.map +1 -1
- package/dist/api/ask-paths.js +36 -1
- package/dist/api/ask-paths.js.map +1 -1
- package/dist/api/ask-relay.d.ts.map +1 -1
- package/dist/api/ask-relay.js +20 -0
- package/dist/api/ask-relay.js.map +1 -1
- package/dist/api/chat-transcripts.d.ts +14 -6
- package/dist/api/chat-transcripts.d.ts.map +1 -1
- package/dist/api/chat-transcripts.js +66 -10
- package/dist/api/chat-transcripts.js.map +1 -1
- package/dist/api/diagnostics.d.ts +10 -0
- package/dist/api/diagnostics.d.ts.map +1 -1
- package/dist/api/diagnostics.js +37 -2
- package/dist/api/diagnostics.js.map +1 -1
- package/dist/api/roster-standing.d.ts.map +1 -1
- package/dist/api/roster-standing.js +13 -3
- package/dist/api/roster-standing.js.map +1 -1
- package/dist/api/routes.d.ts +50 -17
- package/dist/api/routes.d.ts.map +1 -1
- package/dist/api/routes.js +170 -18
- package/dist/api/routes.js.map +1 -1
- package/dist/api/seat-governance.d.ts +115 -0
- package/dist/api/seat-governance.d.ts.map +1 -0
- package/dist/api/seat-governance.js +166 -0
- package/dist/api/seat-governance.js.map +1 -0
- package/dist/api/seat-standing.d.ts +12 -1
- package/dist/api/seat-standing.d.ts.map +1 -1
- package/dist/api/seat-standing.js +11 -9
- package/dist/api/seat-standing.js.map +1 -1
- package/dist/api/server.d.ts.map +1 -1
- package/dist/api/server.js +4 -0
- package/dist/api/server.js.map +1 -1
- package/dist/api/testing.d.ts +2 -2
- package/dist/api/unit-output.d.ts +17 -0
- package/dist/api/unit-output.d.ts.map +1 -1
- package/dist/api/unit-output.js +47 -0
- package/dist/api/unit-output.js.map +1 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +7 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/core/bridge-path.d.ts +24 -3
- package/dist/core/bridge-path.d.ts.map +1 -1
- package/dist/core/bridge-path.js +96 -6
- package/dist/core/bridge-path.js.map +1 -1
- package/dist/core/engine-roster.d.ts.map +1 -1
- package/dist/core/engine-roster.js +3 -0
- package/dist/core/engine-roster.js.map +1 -1
- package/dist/core/estate-mcp-client.d.ts +8 -0
- package/dist/core/estate-mcp-client.d.ts.map +1 -1
- package/dist/core/estate-mcp-client.js +7 -0
- package/dist/core/estate-mcp-client.js.map +1 -1
- package/dist/core/linked-issues.d.ts +20 -2
- package/dist/core/linked-issues.d.ts.map +1 -1
- package/dist/core/linked-issues.js +41 -6
- package/dist/core/linked-issues.js.map +1 -1
- package/dist/core/worker-env.d.ts +31 -0
- package/dist/core/worker-env.d.ts.map +1 -0
- package/dist/core/worker-env.js +34 -0
- package/dist/core/worker-env.js.map +1 -0
- package/dist/interactive/bridge-pool.d.ts +20 -0
- package/dist/interactive/bridge-pool.d.ts.map +1 -1
- package/dist/interactive/bridge-pool.js +34 -2
- package/dist/interactive/bridge-pool.js.map +1 -1
- package/dist/qe/acceptance.d.ts +3 -0
- package/dist/qe/acceptance.d.ts.map +1 -1
- package/dist/qe/acceptance.js +1 -0
- package/dist/qe/acceptance.js.map +1 -1
- package/dist/qe/conformance.d.ts +36 -3
- package/dist/qe/conformance.d.ts.map +1 -1
- package/dist/qe/conformance.js +111 -4
- package/dist/qe/conformance.js.map +1 -1
- package/dist/skills/runtime.d.ts +56 -1
- package/dist/skills/runtime.d.ts.map +1 -1
- package/dist/skills/runtime.js +135 -2
- package/dist/skills/runtime.js.map +1 -1
- package/dist/skills/store.d.ts +27 -0
- package/dist/skills/store.d.ts.map +1 -1
- package/dist/skills/store.js +63 -0
- package/dist/skills/store.js.map +1 -1
- package/dist/studio/assets/index-BPYzJzBk.css +32 -0
- package/dist/studio/assets/index-DtVsHuuC.js +584 -0
- package/dist/studio/editors/wicked-page/editor.json +1 -1
- package/dist/studio/editors/wicked-page/index.html +1 -1
- package/dist/studio/index.html +2 -2
- package/dist/studio/testid-inventory.json +269 -1713
- package/dist/team/routes.d.ts +2 -2
- package/endpoint-manifest.json +11 -1
- package/package.json +3 -3
- package/dist/studio/assets/index-CUNMURZ4.js +0 -581
- package/dist/studio/assets/index-DPqNRzrT.css +0 -32
package/dist/api/routes.js
CHANGED
|
@@ -14,12 +14,13 @@ import { detectRefusal } from './gate-cache.js';
|
|
|
14
14
|
import { acceptanceRequirementOf, buildAcceptanceView } from '../qe/acceptance.js';
|
|
15
15
|
import { runIdentityOf, wireIdentity } from '../core/run-identity.js';
|
|
16
16
|
import { buildEvidenceBundle, coreUnitId, evidenceFilename } from './evidence.js';
|
|
17
|
-
import { outputUnavailableReason, resolveUnit, unitKeysFor } from './unit-output.js';
|
|
17
|
+
import { attemptUnavailableReason, capturedAttempts, outputUnavailableReason, resolveUnit, storedAttempt, unitKeysFor } from './unit-output.js';
|
|
18
18
|
import { execCapped, ExecOutputTooLarge } from '../core/exec.js';
|
|
19
|
-
import { callEstateTool, EstateMcpError } from '../core/estate-mcp-client.js';
|
|
19
|
+
import { callEstateTool, EstateMcpError, memoryStoreInfo } from '../core/estate-mcp-client.js';
|
|
20
20
|
import { SeatHealthTracker } from './seat-health.js';
|
|
21
21
|
import { applyWorkerConfigRoot, signedInHeuristic } from './seat-signin.js';
|
|
22
22
|
import { chatSeatAdmission } from './seat-standing.js';
|
|
23
|
+
import { seatGovernanceDiagnostics, seatGovernanceWarnings, } from './seat-governance.js';
|
|
23
24
|
import { rosterWithStandingFactory } from './roster-standing.js';
|
|
24
25
|
import { ChatTurnIndex } from './chat-turns.js';
|
|
25
26
|
import { ChatScopeIndex, chatScopeDeps, chatScopeStatement, prepareChatScratch, removeChatScratch, namedKindRefusal, resolveChatScope, } from './chat-scope.js';
|
|
@@ -64,7 +65,7 @@ import { MembershipIndex } from '../projects/membership-index.js';
|
|
|
64
65
|
import { MEMBERSHIP_ATTACHED, membershipAttachedKey } from '../projects/events.js';
|
|
65
66
|
import { resolveLinkedIssues } from '../core/linked-issues.js';
|
|
66
67
|
import { AuditLog } from './audit.js';
|
|
67
|
-
import { AcpFoldCache, EngineVersionCache, eventsDirOf, installedPackageVersion, listStoreFiles, readStudioBundleVersion, } from './diagnostics.js';
|
|
68
|
+
import { AcpFoldCache, EngineVersionCache, eventsDirOf, installedPackageVersion, hostSandboxLauncher, seatRosterFile, listStoreFiles, readStudioBundleVersion, } from './diagnostics.js';
|
|
68
69
|
import { GovernanceDiagnostics } from './governance-health.js';
|
|
69
70
|
import { governanceStoreFix, legacyHomeOutboxPath } from '../core/governance-store.js';
|
|
70
71
|
import { UsageError, replayOutboxInto } from '../cli/governance.js';
|
|
@@ -147,15 +148,28 @@ function message(err) {
|
|
|
147
148
|
* the caller comparing their JSON against the source, so the unknown keys are named in `error`
|
|
148
149
|
* itself, where a human and a `curl | jq .error` both see it without reading `details`.
|
|
149
150
|
*/
|
|
150
|
-
function invalidBody(err, what) {
|
|
151
|
+
export function invalidBody(err, what) {
|
|
151
152
|
const unknown = err.issues.flatMap((i) => (i.code === 'unrecognized_keys' ? i.keys : []));
|
|
152
153
|
const error = unknown.length > 0
|
|
153
154
|
? `${what}: unknown field${unknown.length > 1 ? 's' : ''} ${unknown
|
|
154
155
|
.map((k) => `\`${k}\``)
|
|
155
156
|
.join(', ')} — this endpoint does not accept ${unknown.length > 1 ? 'them' : 'it'}, and ignoring ${unknown.length > 1 ? 'them' : 'it'} would run a different request than you sent`
|
|
156
|
-
: what;
|
|
157
|
+
: namedIssues(what, err.issues);
|
|
157
158
|
return { error, details: err.issues };
|
|
158
159
|
}
|
|
160
|
+
/**
|
|
161
|
+
* crew#662: a schema rejection that is NOT an unknown key names the field and what was expected —
|
|
162
|
+
* `Invalid request body: \`revisesPr\` — Expected number, received boolean`. The bare
|
|
163
|
+
* "Invalid request body" sent a caller off to misdiagnose the contract. Up to three issues are
|
|
164
|
+
* spelled out; the rest are counted (all of them stay in `details`).
|
|
165
|
+
*/
|
|
166
|
+
function namedIssues(what, issues) {
|
|
167
|
+
if (issues.length === 0)
|
|
168
|
+
return what;
|
|
169
|
+
const shown = issues.slice(0, 3).map((i) => (i.path.length > 0 ? `\`${i.path.join('.')}\` — ${i.message}` : i.message));
|
|
170
|
+
const more = issues.length > 3 ? `; and ${issues.length - 3} more (see details)` : '';
|
|
171
|
+
return `${what}: ${shown.join('; ')}${more}`;
|
|
172
|
+
}
|
|
159
173
|
/** First value of a possibly-repeated query param, WITHOUT trimming — `''` is PRESERVED because a
|
|
160
174
|
* blank `scope`/`scope_prefix` is meaningful to estate (root / whole-subtree), unlike an omitted
|
|
161
175
|
* one. Returns undefined only when the param is absent. */
|
|
@@ -312,6 +326,11 @@ const OnboardBodySchema = z
|
|
|
312
326
|
// compile time, that every body the published contract (`wicked-crew-api-types`) lets a client
|
|
313
327
|
// send is a body these schemas accept — the request-direction half of the drift guard (task #84).
|
|
314
328
|
export { PlanSchema };
|
|
329
|
+
/** crew#825: the issue refs a launch leaves out of the linked-issue expansion. */
|
|
330
|
+
const ExcludeLinkedIssuesSchema = z
|
|
331
|
+
.array(z.string().regex(/^\s*(?:\d+|(?:(?:[\w.-]+\/)?[\w.-]+)?#\d+)\s*$/, 'an issue ref: 541, #541, repo#541 or owner/repo#541'))
|
|
332
|
+
.max(50)
|
|
333
|
+
.optional();
|
|
315
334
|
export const LaunchSchema = z.object({
|
|
316
335
|
problem: z.string().min(1),
|
|
317
336
|
sessionId: z.string().min(1).optional(),
|
|
@@ -374,6 +393,11 @@ export const LaunchSchema = z.object({
|
|
|
374
393
|
* launch (e.g. a CI job name or a studio tab id). Persisted on the `run.launched` audit entry
|
|
375
394
|
* (`detail.actor`) and served on the run DTO; absent = caller omitted it. */
|
|
376
395
|
actor: z.string().min(1).max(256).optional(),
|
|
396
|
+
/** crew#825 (api-types additive) — issue refs the intent names that the daemon must NOT read and
|
|
397
|
+
* append (`541`, `#541`, `repo#541`, `owner/repo#541`): a reference in passing is not a request to inline
|
|
398
|
+
* the issue. They are answered as `linkedIssues[].excluded`. Send only when
|
|
399
|
+
* `GET /health.capabilities.linkedIssuesExclude === true`. */
|
|
400
|
+
excludeLinkedIssues: ExcludeLinkedIssuesSchema,
|
|
377
401
|
}).strict().refine((b) => b.plan === undefined || b.workflow === undefined, {
|
|
378
402
|
message: 'plan and workflow are mutually exclusive — a launch carries a plan or names a preset, not both',
|
|
379
403
|
path: ['plan'],
|
|
@@ -763,7 +787,8 @@ runtime = {}) {
|
|
|
763
787
|
/** DES-L9: PR number → head branch, through `gh pr view` unless the runtime injected an answerer. */
|
|
764
788
|
const resolvePullRequest = runtime.resolvePullRequest ?? resolvePullRequestViaGh;
|
|
765
789
|
/** crew#627: the intent's linked issues, read under the daemon's identity at launch. */
|
|
766
|
-
const readLinkedIssues = runtime.resolveLinkedIssues ??
|
|
790
|
+
const readLinkedIssues = runtime.resolveLinkedIssues ??
|
|
791
|
+
((p, root, ref, exclude) => resolveLinkedIssues(p, root, ref, undefined, exclude));
|
|
767
792
|
/** crew#550 P-7: the follow-ups the run's evaluator units flagged, read from their captured output.
|
|
768
793
|
* A unit whose output cannot be read contributes nothing (the section then says what WAS read);
|
|
769
794
|
* an adapter without transcripts (a directly-driven route set) reads as "not read" — absent. */
|
|
@@ -999,6 +1024,8 @@ runtime = {}) {
|
|
|
999
1024
|
: { deliverGate: false, revisesPr: false, chatIdOnLaunch: false, seatChipOnCreate: false }),
|
|
1000
1025
|
// C1: `AgentSession.chat_id` is served from the daemon's own launch index, whatever the engine.
|
|
1001
1026
|
runChatId: true,
|
|
1027
|
+
// crew#825: `LaunchRunBody.excludeLinkedIssues` + `POST /linked-issues/preview` are served.
|
|
1028
|
+
linkedIssuesExclude: true,
|
|
1002
1029
|
// ASK-C1 (DES-ASK-TEAM-CHAT-001 §5.1): an ask starts a path — the whole contract is the
|
|
1003
1030
|
// pinned core-ts floor the adapter reports; an older studio reads ABSENT as "every helper
|
|
1004
1031
|
// answers", a newer one renders the one PA voice.
|
|
@@ -1035,6 +1062,9 @@ runtime = {}) {
|
|
|
1035
1062
|
...(adapter.busUnavailable != null ? [busUnavailableWarning(adapter.busUnavailable)] : []),
|
|
1036
1063
|
// wicked-core#631: the engine has the bus but not the calls crew reaches it through.
|
|
1037
1064
|
...(adapter.busSeamsOff != null ? [busSeamsOffWarning(adapter.busSeamsOff)] : []),
|
|
1065
|
+
// crew#742: a seat that runs ungoverned, or floor seats on a host that cannot arm their
|
|
1066
|
+
// boundary, are named before any run starts.
|
|
1067
|
+
...seatGovernanceWarnings(seatGovernanceNow(), hostSandboxLauncher()),
|
|
1038
1068
|
];
|
|
1039
1069
|
return {
|
|
1040
1070
|
status: 'ok',
|
|
@@ -1100,6 +1130,16 @@ runtime = {}) {
|
|
|
1100
1130
|
// who classified (the engine, or crew's registry copy on an older addon) and whether that
|
|
1101
1131
|
// refuses launches — re-surveyed per read. `null` on a route set booted without the watch.
|
|
1102
1132
|
stateHome: runtime.stateHome !== undefined ? await runtime.stateHome.refresh() : null,
|
|
1133
|
+
// crew#742: per seat — transport, the record's OS sandbox, the engine's governance class,
|
|
1134
|
+
// input governance enforced / claimed / unenforced, the mode per scope kind; plus the
|
|
1135
|
+
// registry overlay file and this host's OS launcher.
|
|
1136
|
+
seatGovernance: {
|
|
1137
|
+
rosterFile: seatRosterFile(),
|
|
1138
|
+
hostBoundary: hostSandboxLauncher(),
|
|
1139
|
+
seats: seatGovernanceNow(),
|
|
1140
|
+
},
|
|
1141
|
+
// crew#740: which memory store this daemon reads and writes, and the isolation notice.
|
|
1142
|
+
memoryStore: memoryStoreInfo(),
|
|
1103
1143
|
};
|
|
1104
1144
|
});
|
|
1105
1145
|
// Who am I talking to the daemon as? (task #88). In local mode this is
|
|
@@ -1161,6 +1201,16 @@ runtime = {}) {
|
|
|
1161
1201
|
// disagree about a seat.
|
|
1162
1202
|
const rosterWithStanding = runtime.rosterWithStanding ?? rosterWithStandingFactory({ seatHealth, signedIn });
|
|
1163
1203
|
app.get(`${V}/roster`, async () => ({ roster: rosterWithStanding() }));
|
|
1204
|
+
/** crew#742: the seats' governance standing, read from the standing roster (empty when the
|
|
1205
|
+
* roster cannot be read — the roster route reports that failure itself). */
|
|
1206
|
+
const seatGovernanceNow = () => {
|
|
1207
|
+
try {
|
|
1208
|
+
return seatGovernanceDiagnostics(rosterWithStanding());
|
|
1209
|
+
}
|
|
1210
|
+
catch {
|
|
1211
|
+
return [];
|
|
1212
|
+
}
|
|
1213
|
+
};
|
|
1164
1214
|
// Each seat's week (studio's weekly 1:1 per agent; api-types 0.52.0): units, first pass, rework,
|
|
1165
1215
|
// stalls, bench and cost, folded from the runs' durable event logs by `seatRecord` — nothing new
|
|
1166
1216
|
// is stored. The daemon's remembered stall frames join the engine's, as on `/runs/:id/events`.
|
|
@@ -1562,6 +1612,33 @@ runtime = {}) {
|
|
|
1562
1612
|
const projectAttachError = await recordOnboardingLaunch(req, runId, repo.id, body.data);
|
|
1563
1613
|
return reply.code(201).send({ runId, ...(projectAttachError !== undefined ? { projectAttachError } : {}) });
|
|
1564
1614
|
});
|
|
1615
|
+
// crew#825: what a workflow launch WOULD append — the same reading `POST /runs` does, before Send,
|
|
1616
|
+
// so the composer shows "N linked issues will be appended (≈K chars)" with a way to leave one out
|
|
1617
|
+
// (`excludeLinkedIssues`). Read-only: nothing launches, nothing is stored.
|
|
1618
|
+
const LinkedIssuesPreviewSchema = z
|
|
1619
|
+
.object({
|
|
1620
|
+
problem: z.string().min(1),
|
|
1621
|
+
repoRef: z.string().min(1).optional(),
|
|
1622
|
+
excludeLinkedIssues: ExcludeLinkedIssuesSchema,
|
|
1623
|
+
})
|
|
1624
|
+
.strict();
|
|
1625
|
+
app.post(`${V}/linked-issues/preview`, { config: { manifest: { requestType: 'LinkedIssuesPreviewBody', responseType: 'LinkedIssuesPreviewResponse', statusCodes: [200, 400] } } }, async (req, reply) => {
|
|
1626
|
+
const parsed = LinkedIssuesPreviewSchema.safeParse(req.body);
|
|
1627
|
+
if (!parsed.success)
|
|
1628
|
+
return reply.code(400).send(invalidBody(parsed.error, 'Invalid request body'));
|
|
1629
|
+
const b = parsed.data;
|
|
1630
|
+
// The launch's own best-effort contract (codex on this PR): a resolver fault launches on the
|
|
1631
|
+
// intent alone, so the preview answers "nothing appended" and says why — never a 500.
|
|
1632
|
+
try {
|
|
1633
|
+
const root = b.repoRef !== undefined ? await repoRootOf(b.repoRef) : undefined;
|
|
1634
|
+
const linked = await readLinkedIssues(b.problem, root, b.repoRef, b.excludeLinkedIssues ?? []);
|
|
1635
|
+
return { issues: linked.issues, appendedChars: linked.block === null ? 0 : linked.block.length + 2 };
|
|
1636
|
+
}
|
|
1637
|
+
catch (err) {
|
|
1638
|
+
req.log.warn(`linked-issue preview failed; a launch would run on the intent alone: ${message(err)}`);
|
|
1639
|
+
return { issues: [], appendedChars: 0, error: `linked issues could not be read: ${message(err)} — a launch now runs on the intent alone` };
|
|
1640
|
+
}
|
|
1641
|
+
});
|
|
1565
1642
|
// Launch a run (replaces POST /sessions). `clisJson` defaults to the roster;
|
|
1566
1643
|
// `sessionId` is minted if the client omits it.
|
|
1567
1644
|
app.post(`${V}/runs`, {
|
|
@@ -1774,11 +1851,12 @@ runtime = {}) {
|
|
|
1774
1851
|
// fault (the repo list unreadable, say) launches the run on its intent alone and is logged.
|
|
1775
1852
|
try {
|
|
1776
1853
|
const root = b.repoRef !== undefined ? await repoRootOf(b.repoRef) : undefined;
|
|
1777
|
-
const linked = await readLinkedIssues(b.problem, root, b.repoRef);
|
|
1778
|
-
if (linked.block !== null)
|
|
1854
|
+
const linked = await readLinkedIssues(b.problem, root, b.repoRef, b.excludeLinkedIssues ?? []);
|
|
1855
|
+
if (linked.block !== null)
|
|
1779
1856
|
input.problem = `${b.problem.trimEnd()}\n\n${linked.block}`;
|
|
1857
|
+
// crew#825: an all-excluded intent appends nothing but still answers what it left out.
|
|
1858
|
+
if (linked.issues.length > 0)
|
|
1780
1859
|
linkedIssues = linked.issues;
|
|
1781
|
-
}
|
|
1782
1860
|
}
|
|
1783
1861
|
catch (err) {
|
|
1784
1862
|
req.log.warn(`linked-issue resolution failed; launching on the intent alone: ${message(err)}`);
|
|
@@ -1875,7 +1953,11 @@ runtime = {}) {
|
|
|
1875
1953
|
// planner (one unit, the brief verbatim). Say so in the answer, with how to get work done,
|
|
1876
1954
|
// instead of letting it pass for a launch of real work.
|
|
1877
1955
|
const freeText = input.workflow === undefined && input.plan === undefined ? { freeText: { notice: FREE_TEXT_NOTICE } } : {};
|
|
1878
|
-
|
|
1956
|
+
// crew#874: the run uses the PUBLISHED generation; when the installed garden is not the one it
|
|
1957
|
+
// was built from, the launch answer says so (a recorded proof can disclose which garden ran).
|
|
1958
|
+
const skillsAhead = runtime.skills?.installedAheadFinding({ fresh: true }) ?? null; // fresh: an upgrade just before Send counts (codex r1)
|
|
1959
|
+
const skillsWarning = skillsAhead === null ? {} : { skillsWarning: { kind: skillsAhead.kind, severity: skillsAhead.severity, message: skillsAhead.message } };
|
|
1960
|
+
return reply.code(201).send({ runId, ...(linkedIssues !== undefined ? { linkedIssues } : {}), ...freeText, ...skillsWarning });
|
|
1879
1961
|
}
|
|
1880
1962
|
catch (err) {
|
|
1881
1963
|
const msg = message(err);
|
|
@@ -2495,7 +2577,10 @@ runtime = {}) {
|
|
|
2495
2577
|
refused.push({ cliKey: key, reason: `seat '${key}' is not in the roster`, source: 'scope' });
|
|
2496
2578
|
continue;
|
|
2497
2579
|
}
|
|
2498
|
-
const admission = chatSeatAdmission(
|
|
2580
|
+
const admission = chatSeatAdmission(
|
|
2581
|
+
// IG1-crew-2: the standing seat carries the engine's `governance_class` and the pin
|
|
2582
|
+
// reading, so a scoped ask admits by the seat's governance mode (reason names it).
|
|
2583
|
+
seat, seat.auth ?? 'unknown', scoped, admissionKind);
|
|
2499
2584
|
if (admission.ok)
|
|
2500
2585
|
clis.push(key);
|
|
2501
2586
|
else
|
|
@@ -2575,7 +2660,12 @@ runtime = {}) {
|
|
|
2575
2660
|
}
|
|
2576
2661
|
// ASK-C1: the path record — published only now, with the scope committed, so a message
|
|
2577
2662
|
// cannot launch against a scope that is still being filed (codex on #808 r2, 4).
|
|
2578
|
-
|
|
2663
|
+
// IG1-crew-1: with the admitted seats' records and the scope kind, so the path's view
|
|
2664
|
+
// names the PA's governance mode.
|
|
2665
|
+
askPaths.open(chatId, clis, b.primary, {
|
|
2666
|
+
seats: standing.filter((s) => clis.includes(String(s.key))),
|
|
2667
|
+
scopeKind: !scoped ? 'unscoped' : admissionKind === 'path-bound' ? 'scoped_bound' : 'scoped',
|
|
2668
|
+
});
|
|
2579
2669
|
// Register roots for path rewriting (crew#618): absolute host paths in seat replies are
|
|
2580
2670
|
// rewritten to repo-relative form before being stored in the transcript.
|
|
2581
2671
|
if (chatTranscripts !== undefined && scope.repos.length > 0) {
|
|
@@ -2896,7 +2986,10 @@ runtime = {}) {
|
|
|
2896
2986
|
const seat = standingNow.find((s) => String(s.key) === cliKey);
|
|
2897
2987
|
if (seat === undefined)
|
|
2898
2988
|
return { cliKey, ok: false, error: `seat '${cliKey}' is not in the roster` };
|
|
2899
|
-
const admission = chatSeatAdmission(
|
|
2989
|
+
const admission = chatSeatAdmission(
|
|
2990
|
+
// IG1-crew-2: the standing seat carries the engine's `governance_class` and the pin
|
|
2991
|
+
// reading, so a scoped ask admits by the seat's governance mode (reason names it).
|
|
2992
|
+
seat, seat.auth ?? 'unknown', scopedChat, reseatKind);
|
|
2900
2993
|
return admission.ok ? { cliKey, ok: true } : { cliKey, ok: false, error: admission.reason };
|
|
2901
2994
|
});
|
|
2902
2995
|
askPaths.admit(id, seats.filter((s) => s.ok).map((s) => s.cliKey));
|
|
@@ -2974,7 +3067,9 @@ runtime = {}) {
|
|
|
2974
3067
|
refused: chatScopes.refusedOf(id) ?? null,
|
|
2975
3068
|
...(askPaths.view(id) !== undefined ? { path: askPaths.view(id) } : {}),
|
|
2976
3069
|
// DES-L5 (D-13): the transcript so far, append order — `[]` before the first persisted
|
|
2977
|
-
// turn
|
|
3070
|
+
// turn. The file goes with the chat on `chatClosed` UNLESS a run promoted from it is still
|
|
3071
|
+
// live (crew#619 retention — dropped when that run terminals), so a reclaimed id answers
|
|
3072
|
+
// `[]` only once no promoted run holds the old file.
|
|
2978
3073
|
...(chatTranscripts !== undefined ? { messages: chatTranscripts.read(id) } : {}),
|
|
2979
3074
|
};
|
|
2980
3075
|
}
|
|
@@ -3050,12 +3145,48 @@ runtime = {}) {
|
|
|
3050
3145
|
units: unitKeysFor(run),
|
|
3051
3146
|
});
|
|
3052
3147
|
}
|
|
3148
|
+
const rawAttempt = req.query.attempt;
|
|
3149
|
+
const attemptArg = Array.isArray(rawAttempt) ? rawAttempt[0] : rawAttempt;
|
|
3150
|
+
const wanted = attemptArg === undefined || attemptArg === '' ? undefined : /^\d{1,9}$/.test(attemptArg) ? Number(attemptArg) : NaN;
|
|
3151
|
+
if (wanted !== undefined && !Number.isSafeInteger(wanted)) {
|
|
3152
|
+
return reply.code(400).send({ error: '`attempt` must be a non-negative integer' });
|
|
3153
|
+
}
|
|
3053
3154
|
// Keyed off the unit RECORD (the same derivation the evidence bundle uses), not off the
|
|
3054
3155
|
// caller's path segment — the two disagreeing is what made a resolvable unit unreadable.
|
|
3055
3156
|
const output = await adapter.workOutput(coreUnitId(id, unit));
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
|
|
3157
|
+
// crew#848: which attempt those bytes are. The engine keeps one transcript per unit (the last
|
|
3158
|
+
// attempt that folded ok); the durable log has one `unitOutputCaptured` per attempt.
|
|
3159
|
+
const events = typeof adapter.runEvents === 'function' ? await adapter.runEvents(id).catch(() => null) : null;
|
|
3160
|
+
const attempts = capturedAttempts((events ?? []), unit.ord);
|
|
3161
|
+
const served = output !== null ? storedAttempt(attempts, output) : null;
|
|
3162
|
+
const latest = attempts[attempts.length - 1];
|
|
3163
|
+
const meta = {
|
|
3164
|
+
...(served !== null ? { attempt: served } : {}),
|
|
3165
|
+
...(latest !== undefined ? { latestAttempt: latest.attempt } : {}),
|
|
3166
|
+
...(events !== null ? { attempts } : {}),
|
|
3167
|
+
};
|
|
3168
|
+
if (served !== null)
|
|
3169
|
+
void reply.header('x-wicked-unit-attempt', String(served));
|
|
3170
|
+
if (wanted !== undefined) {
|
|
3171
|
+
const rec = attempts.find((a) => a.attempt === wanted);
|
|
3172
|
+
if (rec === undefined) {
|
|
3173
|
+
return reply.code(404).send({
|
|
3174
|
+
error: `Unit ${unit.ord} of run ${id} has no attempt ${wanted} on record`,
|
|
3175
|
+
attempts: attempts.map((a) => a.attempt),
|
|
3176
|
+
});
|
|
3177
|
+
}
|
|
3178
|
+
if (output !== null && served === wanted)
|
|
3179
|
+
return reply.send({ output, ...meta });
|
|
3180
|
+
return reply.send({ output: null, ...meta, attempt: wanted, outputUnavailable: attemptUnavailableReason(unit, rec, served) });
|
|
3181
|
+
}
|
|
3182
|
+
if (output !== null) {
|
|
3183
|
+
// A later attempt that left no transcript: the stored bytes are SUPERSEDED — say so, and why.
|
|
3184
|
+
if (served !== null && latest !== undefined && latest.attempt > served) {
|
|
3185
|
+
return reply.send({ output, ...meta, superseded: true, latestUnavailable: attemptUnavailableReason(unit, latest, served) });
|
|
3186
|
+
}
|
|
3187
|
+
return reply.send({ output, ...meta });
|
|
3188
|
+
}
|
|
3189
|
+
return reply.send({ output: null, ...meta, outputUnavailable: outputUnavailableReason(unit) });
|
|
3059
3190
|
});
|
|
3060
3191
|
// DC-S7 (§4.6): what the seats considered, set aside and cited for one chat turn or one unit
|
|
3061
3192
|
// attempt. Operator+ (the rules are the project's doctrine). A cited rule is "unchecked", never
|
|
@@ -3150,6 +3281,26 @@ runtime = {}) {
|
|
|
3150
3281
|
claims: () => adapter.listConformanceClaims(),
|
|
3151
3282
|
events: (rid) => adapter.runEvents(rid),
|
|
3152
3283
|
...(walkthroughs.gates.length > 0 || walkthroughs.ownedByYou ? { walkthroughs } : {}),
|
|
3284
|
+
// IG1-crew-2: what fenced each unit — the run's binding and the roster's seat records.
|
|
3285
|
+
enforcementContext: {
|
|
3286
|
+
bound: run.session.repo_ref !== null,
|
|
3287
|
+
worktree: run.session.workdir,
|
|
3288
|
+
extraWriteRoots: run.session.extra_write_roots ?? [],
|
|
3289
|
+
seatOf: (() => {
|
|
3290
|
+
let seats = null;
|
|
3291
|
+
return (cli) => {
|
|
3292
|
+
if (seats === null) {
|
|
3293
|
+
try {
|
|
3294
|
+
seats = rosterWithStanding();
|
|
3295
|
+
}
|
|
3296
|
+
catch {
|
|
3297
|
+
seats = [];
|
|
3298
|
+
}
|
|
3299
|
+
}
|
|
3300
|
+
return seats.find((seat) => seat.key === cli);
|
|
3301
|
+
};
|
|
3302
|
+
})(),
|
|
3303
|
+
},
|
|
3153
3304
|
});
|
|
3154
3305
|
});
|
|
3155
3306
|
/**
|
|
@@ -5201,7 +5352,8 @@ runtime = {}) {
|
|
|
5201
5352
|
}
|
|
5202
5353
|
if (limit !== undefined)
|
|
5203
5354
|
memories = memories.slice(0, limit);
|
|
5204
|
-
|
|
5355
|
+
// crew#740: the store the list came from (path, source, the isolation notice when it applies).
|
|
5356
|
+
const body = { memories, store: memoryStoreInfo() };
|
|
5205
5357
|
return body;
|
|
5206
5358
|
}
|
|
5207
5359
|
catch (err) {
|