@claudexor/control-api 3.20.1 → 3.23.0
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/artifact-serve-routes.d.ts +7 -4
- package/dist/artifact-serve-routes.d.ts.map +1 -1
- package/dist/artifact-serve-routes.js +35 -7
- package/dist/artifact-serve-routes.js.map +1 -1
- package/dist/command-reads.d.ts +5 -0
- package/dist/command-reads.d.ts.map +1 -0
- package/dist/command-reads.js +18 -0
- package/dist/command-reads.js.map +1 -0
- package/dist/control-protocol.d.ts.map +1 -1
- package/dist/control-protocol.js +3 -1
- package/dist/control-protocol.js.map +1 -1
- package/dist/daemon-server.d.ts +1 -0
- package/dist/daemon-server.d.ts.map +1 -1
- package/dist/daemon-server.js +75 -109
- package/dist/daemon-server.js.map +1 -1
- package/dist/daemon-status-routes.d.ts +7 -0
- package/dist/daemon-status-routes.d.ts.map +1 -0
- package/dist/daemon-status-routes.js +16 -0
- package/dist/daemon-status-routes.js.map +1 -0
- package/dist/decision-rerun.js +1 -1
- package/dist/decision-rerun.js.map +1 -1
- package/dist/operation-catalog.d.ts.map +1 -1
- package/dist/operation-catalog.js +1 -0
- package/dist/operation-catalog.js.map +1 -1
- package/dist/operation-summaries.d.ts.map +1 -1
- package/dist/operation-summaries.js +1 -0
- package/dist/operation-summaries.js.map +1 -1
- package/dist/project-routes.d.ts +6 -1
- package/dist/project-routes.d.ts.map +1 -1
- package/dist/project-routes.js +18 -3
- package/dist/project-routes.js.map +1 -1
- package/dist/remote-operation-descriptors.d.ts +8 -0
- package/dist/remote-operation-descriptors.d.ts.map +1 -1
- package/dist/remote-operation-descriptors.js +4 -0
- package/dist/remote-operation-descriptors.js.map +1 -1
- package/dist/retention.d.ts.map +1 -1
- package/dist/retention.js +4 -1
- package/dist/retention.js.map +1 -1
- package/dist/run-apply-routes.d.ts.map +1 -1
- package/dist/run-apply-routes.js +6 -10
- package/dist/run-apply-routes.js.map +1 -1
- package/dist/run-audit.d.ts +5 -0
- package/dist/run-audit.d.ts.map +1 -0
- package/dist/run-audit.js +18 -0
- package/dist/run-audit.js.map +1 -0
- package/dist/run-continuation-projection.d.ts +7 -0
- package/dist/run-continuation-projection.d.ts.map +1 -0
- package/dist/run-continuation-projection.js +135 -0
- package/dist/run-continuation-projection.js.map +1 -0
- package/dist/run-continuation-start.d.ts +13 -0
- package/dist/run-continuation-start.d.ts.map +1 -0
- package/dist/run-continuation-start.js +62 -0
- package/dist/run-continuation-start.js.map +1 -0
- package/dist/run-delivery-state.d.ts.map +1 -1
- package/dist/run-delivery-state.js +6 -0
- package/dist/run-delivery-state.js.map +1 -1
- package/dist/run-discard.d.ts +10 -0
- package/dist/run-discard.d.ts.map +1 -0
- package/dist/run-discard.js +47 -0
- package/dist/run-discard.js.map +1 -0
- package/dist/run-exact-patch.d.ts +25 -0
- package/dist/run-exact-patch.d.ts.map +1 -0
- package/dist/run-exact-patch.js +55 -0
- package/dist/run-exact-patch.js.map +1 -0
- package/dist/run-execution-root.d.ts +22 -0
- package/dist/run-execution-root.d.ts.map +1 -0
- package/dist/run-execution-root.js +23 -0
- package/dist/run-execution-root.js.map +1 -0
- package/dist/run-list-fingerprint.d.ts.map +1 -1
- package/dist/run-list-fingerprint.js +7 -0
- package/dist/run-list-fingerprint.js.map +1 -1
- package/dist/run-list.d.ts +3 -57
- package/dist/run-list.d.ts.map +1 -1
- package/dist/run-list.js +2 -67
- package/dist/run-list.js.map +1 -1
- package/dist/run-record.d.ts +4 -3
- package/dist/run-record.d.ts.map +1 -1
- package/dist/run-record.js.map +1 -1
- package/dist/run-retry-routes.js +11 -2
- package/dist/run-retry-routes.js.map +1 -1
- package/dist/run-start.d.ts +12 -3
- package/dist/run-start.d.ts.map +1 -1
- package/dist/run-start.js +25 -25
- package/dist/run-start.js.map +1 -1
- package/dist/thread-create-route.d.ts +16 -0
- package/dist/thread-create-route.d.ts.map +1 -0
- package/dist/thread-create-route.js +83 -0
- package/dist/thread-create-route.js.map +1 -0
- package/dist/thread-delegated-execution.d.ts +19 -0
- package/dist/thread-delegated-execution.d.ts.map +1 -0
- package/dist/thread-delegated-execution.js +27 -0
- package/dist/thread-delegated-execution.js.map +1 -0
- package/dist/thread-lifecycle-routes.d.ts +2 -1
- package/dist/thread-lifecycle-routes.d.ts.map +1 -1
- package/dist/thread-lifecycle-routes.js +12 -3
- package/dist/thread-lifecycle-routes.js.map +1 -1
- package/dist/thread-mutation.d.ts +2 -4
- package/dist/thread-mutation.d.ts.map +1 -1
- package/dist/thread-mutation.js +1 -1
- package/dist/thread-mutation.js.map +1 -1
- package/dist/thread-projection.d.ts.map +1 -1
- package/dist/thread-projection.js +1 -0
- package/dist/thread-projection.js.map +1 -1
- package/dist/thread-recovery.js +1 -1
- package/dist/thread-recovery.js.map +1 -1
- package/dist/thread-turn-problems.d.ts +39 -0
- package/dist/thread-turn-problems.d.ts.map +1 -0
- package/dist/thread-turn-problems.js +86 -0
- package/dist/thread-turn-problems.js.map +1 -0
- package/dist/thread-turn-routes.d.ts +2 -25
- package/dist/thread-turn-routes.d.ts.map +1 -1
- package/dist/thread-turn-routes.js +19 -90
- package/dist/thread-turn-routes.js.map +1 -1
- package/package.json +7 -7
package/dist/daemon-server.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { readThreadCommands } from "./command-reads.js";
|
|
1
2
|
import { recoverInterruptedOutput } from "./retained-output.js";
|
|
2
3
|
import { recordedExecutionRoot } from "./run-delivery-state.js";
|
|
4
|
+
import * as exactPatch from "./run-exact-patch.js";
|
|
3
5
|
import { readTextArtifact, readRawTextArtifact, readStructured, safeReadStructuredArtifact, } from "./run-artifact-read.js";
|
|
4
6
|
import { controlRunResult, readDeliveryState, markRunApplyState } from "./run-delivery-state.js";
|
|
5
7
|
import { timingSafeEqual } from "node:crypto";
|
|
@@ -7,10 +9,13 @@ import { existsSync, lstatSync, mkdirSync, readdirSync, writeFileSync } from "no
|
|
|
7
9
|
import { createServer } from "node:http";
|
|
8
10
|
import { basename, extname, join } from "node:path";
|
|
9
11
|
import { deriveApplyEligibility, revertInPlaceFromAnchor, validateApplyGate, verifyAndDeliver, } from "@claudexor/delivery";
|
|
10
|
-
import {
|
|
12
|
+
import { lastSeqInFile } from "@claudexor/event-log";
|
|
11
13
|
import { isVanishedErrno, safeArtifactPath, safeArtifactRoot } from "./artifact-paths.js";
|
|
12
14
|
import { TERMINAL_STATES } from "./sse-shared.js";
|
|
13
15
|
import { readFilesWorkProduct } from "./files-work-product.js";
|
|
16
|
+
import { appendRunAuditEvent } from "./run-audit.js";
|
|
17
|
+
import { discardRunResult } from "./run-discard.js";
|
|
18
|
+
import { continuationSummary } from "./run-continuation-projection.js";
|
|
14
19
|
import { applyFilesResult } from "./files-apply-route.js";
|
|
15
20
|
import { deliverableWorkspaceChanges } from "@claudexor/workspace";
|
|
16
21
|
import { streamRunEvents } from "./run-events-stream.js";
|
|
@@ -21,6 +26,7 @@ import { projectTurnRunCards } from "./thread-projection.js";
|
|
|
21
26
|
import { projectSession, projectThread, projectTurn } from "./thread-projection.js";
|
|
22
27
|
import { handleThreadTurnCreate, handleThreadTurnRetry, } from "./thread-turn-routes.js";
|
|
23
28
|
import { chainIdleRunMutation, chainThreadMutation } from "./thread-mutation.js";
|
|
29
|
+
import { handleThreadCreate } from "./thread-create-route.js";
|
|
24
30
|
import { handleThreadLifecycleRoutes, } from "./thread-lifecycle-routes.js";
|
|
25
31
|
import * as runStart from "./run-start.js";
|
|
26
32
|
import { cancelDelegationFamily } from "./delegation-control.js";
|
|
@@ -37,10 +43,12 @@ import { writeBinaryResponse } from "./binary-response.js";
|
|
|
37
43
|
export { inlineContentDisposition } from "./binary-response.js";
|
|
38
44
|
import { handleRecoveryRoute } from "./recovery-routes.js";
|
|
39
45
|
import { handleJournalEventRoute } from "./journal-event-routes.js";
|
|
46
|
+
import { handleDaemonStatusRoute } from "./daemon-status-routes.js";
|
|
40
47
|
import { handleMaintenanceRoute } from "./maintenance-routes.js";
|
|
41
48
|
import { handleResourceRoute } from "./resource-routes.js";
|
|
42
49
|
import { handleModelRoute } from "./model-routes.js";
|
|
43
50
|
import { handleArtifactServeRoute, listArtifacts, resolveProjectRoot, } from "./artifact-serve-routes.js";
|
|
51
|
+
import { runExecutionRoot, runExecutionWorkspaceRoot } from "./run-execution-root.js";
|
|
44
52
|
import { requiredGateSpecsFromTaskArtifact } from "./task-contract-gates.js";
|
|
45
53
|
import { bearerCredential } from "./authorization.js";
|
|
46
54
|
import { assertOnlyQueryParams, singleQuery } from "./query.js";
|
|
@@ -51,10 +59,10 @@ export { resetRunListFingerprintProbeCountForTests, runListFingerprintProbeCount
|
|
|
51
59
|
import { controlProblemError, normalizeRequestValidationError, revertRefusedProblem, thrownProblemCode, } from "./problem-response.js";
|
|
52
60
|
import { handleSecurityRoute } from "./security-routes.js";
|
|
53
61
|
import { effectiveTerminalFacts, expectedRunFacts, projectRunFactsForDetail, } from "./run-facts-projection.js";
|
|
54
|
-
import { PlanQuestionsArtifact, CouncilProjection, derivePlanReadiness, directDelegatedChildrenFromRecords, ControlAuthReadinessRefreshRequest, ControlAuthReadinessRefreshResponse, ControlProblem, AccessProfile, ControlApplyRequest, AgentCapabilityCatalog, ControlHarnessListResponse, ControlHarnessModelsQueryResponse, ControlSetupJob, ControlSetupJobCreateRequest, ControlSetupJobInputRequest, ControlSetupJobEvent, ControlSetupJobSnapshot, isTerminalControlSetupJobState, ControlSetupJobListFilter, ControlSetupJobListResponse, ControlRunStartInfo, ControlRunControlRequest, ControlRunControlResponse, ControlReviewerPanelEntry, ControlRunDetail, ControlRunListResponse, ControlRunSummary, ControlBudgetSnapshot, PaidBudget, ControlSettingsSnapshot, ControlSettingsUpdateRequest, ControlQuotaRefreshRequest, ControlQuotaResponse, ControlAccountPoolsResponse, ControlAccountsMigrationRollbackRequest, ControlAccountsMigrationRollbackResponse, ControlCredentialProfileCreateRequest, ControlCredentialProfileCreateResponse, ControlCredentialProfileUpdateRequest, ControlCredentialProfileUpdateResponse, ControlCredentialProfileDeleteResponse, ControlInteractionAnswerRequest, ControlInteractionAnswerResponse, ControlRunDecisionRequest, ControlRunDecisionResponse, ControlRunApplicabilityResponse,
|
|
62
|
+
import { PlanQuestionsArtifact, CouncilProjection, derivePlanReadiness, directDelegatedChildrenFromRecords, ControlAuthReadinessRefreshRequest, ControlAuthReadinessRefreshResponse, ControlProblem, AccessProfile, ControlApplyRequest, AgentCapabilityCatalog, ControlHarnessListResponse, ControlHarnessModelsQueryResponse, ControlSetupJob, ControlSetupJobCreateRequest, ControlSetupJobInputRequest, ControlSetupJobEvent, ControlSetupJobSnapshot, isTerminalControlSetupJobState, ControlSetupJobListFilter, ControlSetupJobListResponse, ControlRunStartInfo, ControlRunControlRequest, ControlRunControlResponse, ControlReviewerPanelEntry, ControlRunDetail, ControlRunListResponse, ControlRunSummary, ControlBudgetSnapshot, PaidBudget, ControlSettingsSnapshot, ControlSettingsUpdateRequest, ControlQuotaRefreshRequest, ControlQuotaResponse, ControlAccountPoolsResponse, ControlAccountsMigrationRollbackRequest, ControlAccountsMigrationRollbackResponse, ControlCredentialProfileCreateRequest, ControlCredentialProfileCreateResponse, ControlCredentialProfileUpdateRequest, ControlCredentialProfileUpdateResponse, ControlCredentialProfileDeleteResponse, ControlInteractionAnswerRequest, ControlInteractionAnswerResponse, ControlRunDecisionRequest, ControlRunDecisionResponse, ControlRunApplicabilityResponse, ControlThreadTurnRequest, ControlThreadUpdateRequest, ControlThreadDetail, ControlThreadListResponse, DecisionRecord, Id, ModeKind, RoutingGoal, ReviewFinding, RunFailure, RunTelemetry, StructuredOutputConformance, TaskContract, ProtectedPathApproval, isTerminalLifecycle, needsDecision, needsOperatorAttention, outcomeBanner, outcomeFactsFromFailure, TestCommandInvocation, WorkProduct, } from "@claudexor/schema";
|
|
55
63
|
import { resolveControlProtocol } from "./control-protocol.js";
|
|
56
64
|
import { readControlRequestBody } from "./request-body.js";
|
|
57
|
-
import { assertNoInlineSecretValues,
|
|
65
|
+
import { assertNoInlineSecretValues, errorCode, noProjectRepoRoot, redactSecrets, safeProblemContext, safeProblemRequiredActions, sha256, } from "@claudexor/util";
|
|
58
66
|
import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
|
|
59
67
|
const LOOPBACK_HOSTS = new Set(["127.0.0.1", "localhost", "::1"]);
|
|
60
68
|
const NO_PROJECT_ROOT = noProjectRepoRoot();
|
|
@@ -358,12 +366,18 @@ export class DaemonControlApiServer {
|
|
|
358
366
|
}
|
|
359
367
|
const path = protocol.path;
|
|
360
368
|
const dataRoutes = {
|
|
369
|
+
daemon: this.opts.daemon,
|
|
361
370
|
services: this.opts.services,
|
|
362
371
|
readBody: (request) => this.readBody(request),
|
|
363
372
|
json: (response, status, body) => this.json(response, status, body),
|
|
364
373
|
requestError: (response, error, fallback) => this.requestError(response, error, fallback),
|
|
365
374
|
};
|
|
366
|
-
for (const route of [
|
|
375
|
+
for (const route of [
|
|
376
|
+
handleResourceRoute,
|
|
377
|
+
handleModelRoute,
|
|
378
|
+
handleMaintenanceRoute,
|
|
379
|
+
handleDaemonStatusRoute,
|
|
380
|
+
]) {
|
|
367
381
|
if (await route(dataRoutes, method, path, req, res))
|
|
368
382
|
return;
|
|
369
383
|
}
|
|
@@ -373,13 +387,14 @@ export class DaemonControlApiServer {
|
|
|
373
387
|
json: (response, status, body) => this.json(response, status, body),
|
|
374
388
|
requestError: (response, error, fallback) => this.requestError(response, error, fallback),
|
|
375
389
|
binary: writeBinaryResponse,
|
|
390
|
+
findRun: (id) => this.findRun(id),
|
|
376
391
|
}, method, path, req, res))
|
|
377
392
|
return;
|
|
378
393
|
if (method === "POST" && path === "/runs") {
|
|
379
394
|
return runStart.handleRunCreate({
|
|
380
395
|
daemon: this.opts.daemon,
|
|
381
396
|
readBody: (request) => this.readBody(request),
|
|
382
|
-
requestError: (response, error) => this.requestError(response, error),
|
|
397
|
+
requestError: (response, error, fallback) => this.requestError(response, error, fallback),
|
|
383
398
|
json: (response, status, body) => this.json(response, status, body),
|
|
384
399
|
respondToAcceptedJob: (response, jobId) => this.respondToAcceptedJob(response, jobId),
|
|
385
400
|
validateResources: this.opts.services?.validateResources,
|
|
@@ -469,51 +484,19 @@ export class DaemonControlApiServer {
|
|
|
469
484
|
}, method, path, req, res))
|
|
470
485
|
return;
|
|
471
486
|
if (method === "POST" && path === "/threads") {
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
const parsed = ControlThreadCreateRequest.parse(body);
|
|
479
|
-
const idempotencyKey = runStart.requiredIdempotencyKey(req);
|
|
480
|
-
let repoRoot = null;
|
|
481
|
-
if (parsed.scope.kind === "project") {
|
|
482
|
-
repoRoot = runStart.normalizeExistingProjectRoot(parsed.scope.root);
|
|
483
|
-
}
|
|
484
|
-
const thread = await svc({
|
|
485
|
-
title: parsed.title,
|
|
486
|
-
folder: parsed.folder,
|
|
487
|
-
repoRoot,
|
|
488
|
-
// Carried explicitly through the SAME predicate the run route and the
|
|
489
|
-
// partition router use: dropping it here would register a root the
|
|
490
|
-
// wire contract promises never to register.
|
|
491
|
-
ephemeral: isEphemeralRunScope(parsed.scope),
|
|
492
|
-
mode: parsed.mode,
|
|
493
|
-
workspace: parsed.workspace,
|
|
494
|
-
authPreference: parsed.authPreference,
|
|
495
|
-
credentialProfileId: parsed.credentialProfileId ?? null,
|
|
496
|
-
access: parsed.access,
|
|
497
|
-
primaryHarness: parsed.primaryHarness ?? null,
|
|
498
|
-
eligibleHarnesses: parsed.eligibleHarnesses,
|
|
499
|
-
idempotency: {
|
|
500
|
-
key: idempotencyKey,
|
|
501
|
-
client: "control-api",
|
|
502
|
-
request: parsed,
|
|
503
|
-
},
|
|
504
|
-
});
|
|
505
|
-
return this.json(res, 200, projectThread(thread, false));
|
|
506
|
-
}
|
|
507
|
-
catch (err) {
|
|
508
|
-
return this.requestError(res, err);
|
|
509
|
-
}
|
|
487
|
+
return handleThreadCreate({
|
|
488
|
+
services: this.opts.services,
|
|
489
|
+
readBody: (request) => this.readBody(request),
|
|
490
|
+
json: (response, status, body) => this.json(response, status, body),
|
|
491
|
+
requestError: (response, error) => this.requestError(response, error),
|
|
492
|
+
}, req, res);
|
|
510
493
|
}
|
|
511
494
|
if (method === "GET" && path === "/threads") {
|
|
512
495
|
const svc = this.opts.services?.listThreads;
|
|
513
496
|
if (!svc)
|
|
514
497
|
return this.json(res, 501, { error: "threads are not supported by this engine build" });
|
|
515
498
|
const { threads, problems } = await svc();
|
|
516
|
-
const runs = await this.opts.daemon
|
|
499
|
+
const runs = await readThreadCommands(this.opts.daemon, threads);
|
|
517
500
|
const blocked = new Set(runs.filter((r) => this.runNeedsAttention(r)).map((r) => r.runId ?? r.id));
|
|
518
501
|
return this.json(res, 200, ControlThreadListResponse.parse({
|
|
519
502
|
threads: threads.map((t) => projectThread(t, blocked.has(t.head_run_id ?? ""))),
|
|
@@ -527,7 +510,9 @@ export class DaemonControlApiServer {
|
|
|
527
510
|
return this.json(res, 501, { error: "threads are not supported by this engine build" });
|
|
528
511
|
try {
|
|
529
512
|
const detail = await svc(decodeURIComponent(threadDetailMatch[1]));
|
|
530
|
-
const runs = await this.opts.daemon.list(
|
|
513
|
+
const runs = await this.opts.daemon.list({
|
|
514
|
+
threadId: decodeURIComponent(threadDetailMatch[1]),
|
|
515
|
+
});
|
|
531
516
|
const byRun = new Map(runs.map((r) => [r.runId ?? r.id, r]));
|
|
532
517
|
const thread = detail.thread;
|
|
533
518
|
const cards = projectTurnRunCards(detail.turns, runs, (record) => this.summarizeRunOrDiagnostic(record));
|
|
@@ -653,7 +638,11 @@ export class DaemonControlApiServer {
|
|
|
653
638
|
if (!result.revertable || !result.revertAnchorId) {
|
|
654
639
|
throw Object.assign(new Error("this run produced no revertable in-place change"), { status: 409 });
|
|
655
640
|
}
|
|
656
|
-
|
|
641
|
+
// A delegated run's live tree is its recorded caller-owned
|
|
642
|
+
// address; it never falls back to the stable project identity.
|
|
643
|
+
const repoRoot = recordedExecutionRoot(rec) ??
|
|
644
|
+
runExecutionWorkspaceRoot(rec) ??
|
|
645
|
+
applyTargetRoot({ kind: "original_project" }, rec);
|
|
657
646
|
if (!repoRoot) {
|
|
658
647
|
throw Object.assign(new Error("cannot resolve the in-place project root to revert"), { status: 400 });
|
|
659
648
|
}
|
|
@@ -707,25 +696,7 @@ export class DaemonControlApiServer {
|
|
|
707
696
|
key: decisionKey,
|
|
708
697
|
operation: "run.decision.discard",
|
|
709
698
|
request: { runId: rec.runId ?? rec.id, body },
|
|
710
|
-
work:
|
|
711
|
-
const files = readFilesWorkProduct(rec);
|
|
712
|
-
const state = controlRunResult(rec).applyState;
|
|
713
|
-
if (!TERMINAL_STATES.has(rec.state) ||
|
|
714
|
-
!files ||
|
|
715
|
-
files.manifest.isolation !== "envelope" ||
|
|
716
|
-
(state !== "not_applied" && state !== "discarded"))
|
|
717
|
-
throw Object.assign(new Error("Only an unapplied copied files result can be discarded; direct effects remain in place"), { status: 409 });
|
|
718
|
-
markRunApplyState(rec, "discarded", undefined, true);
|
|
719
|
-
appendRunAuditEvent(rec, "control.applied", {
|
|
720
|
-
decision: "discard",
|
|
721
|
-
manifest_sha256: files.manifestSha256,
|
|
722
|
-
});
|
|
723
|
-
return ControlRunDecisionResponse.parse({
|
|
724
|
-
accepted: true,
|
|
725
|
-
status: "discarded",
|
|
726
|
-
message: "Pending file application discarded; retained result follows normal retention.",
|
|
727
|
-
});
|
|
728
|
-
},
|
|
699
|
+
work: () => discardRunResult(rec),
|
|
729
700
|
}));
|
|
730
701
|
return this.json(res, 200, response);
|
|
731
702
|
}
|
|
@@ -751,18 +722,16 @@ export class DaemonControlApiServer {
|
|
|
751
722
|
return this.requestError(res, error);
|
|
752
723
|
}
|
|
753
724
|
}
|
|
754
|
-
const patch = readPatch(rec);
|
|
755
|
-
if (patch === null)
|
|
756
|
-
return this.json(res, 404, { error: "no patch artifact for this run" });
|
|
757
|
-
if (containsSecretLikeToken(patch))
|
|
758
|
-
return this.json(res, 409, { error: "patch contains secret-like token; refusing apply" });
|
|
759
|
-
const repoRoot = applyTargetRoot(body.target ?? { kind: "original_project" }, rec);
|
|
760
|
-
if (!repoRoot)
|
|
761
|
-
return this.json(res, 400, { error: "project root is required for apply" });
|
|
762
|
-
const absoluteRepoError = runStart.validateAbsoluteRepoRoot(repoRoot);
|
|
763
|
-
if (absoluteRepoError)
|
|
764
|
-
return this.json(res, 400, { error: absoluteRepoError });
|
|
765
725
|
try {
|
|
726
|
+
const patch = readPatch(rec); // typed 409 when the exact bytes are unavailable
|
|
727
|
+
if (patch === null)
|
|
728
|
+
return this.json(res, 404, { error: "no patch artifact for this run" });
|
|
729
|
+
const repoRoot = applyTargetRoot(body.target ?? { kind: "original_project" }, rec);
|
|
730
|
+
if (!repoRoot)
|
|
731
|
+
return this.json(res, 400, { error: "project root is required for apply" });
|
|
732
|
+
const absoluteRepoError = runStart.validateAbsoluteRepoRoot(repoRoot);
|
|
733
|
+
if (absoluteRepoError)
|
|
734
|
+
return this.json(res, 400, { error: absoluteRepoError });
|
|
766
735
|
const response = await this.chainRunMutation(rec, () => runIdempotentDelivery(this.opts.services, {
|
|
767
736
|
params: rec.params,
|
|
768
737
|
key: decisionKey,
|
|
@@ -774,7 +743,10 @@ export class DaemonControlApiServer {
|
|
|
774
743
|
repoRoot,
|
|
775
744
|
},
|
|
776
745
|
work: async () => {
|
|
777
|
-
const delivered = await verifyAndDeliver(repoRoot, patch, {
|
|
746
|
+
const delivered = await verifyAndDeliver(repoRoot, patch, {
|
|
747
|
+
mode: body.applyMode ?? "apply",
|
|
748
|
+
secretLikeBinary: exactPatch.runPatchHasSecretLikeBinary(rec),
|
|
749
|
+
}, gateSpecsForRun(rec), (freshVerify) => applyGateError(rec, patch, repoRoot, this.operatorDecisionFor(rec), freshVerify));
|
|
778
750
|
if (delivered.refused) {
|
|
779
751
|
throw Object.assign(new Error(delivered.detail ?? "delivery refused"), {
|
|
780
752
|
status: 409,
|
|
@@ -1370,7 +1342,7 @@ export class DaemonControlApiServer {
|
|
|
1370
1342
|
/** Persisted Delegate-only descendant graph. `parentRunId` alone is broader
|
|
1371
1343
|
* thread/retry lineage and deliberately does not participate. */
|
|
1372
1344
|
async delegatedDescendants(parentRunId) {
|
|
1373
|
-
return delegatedDescendantsFromRecords(parentRunId, await this.opts.daemon.list());
|
|
1345
|
+
return delegatedDescendantsFromRecords(parentRunId, await this.opts.daemon.list({ delegatedDescendantsOf: parentRunId }));
|
|
1374
1346
|
}
|
|
1375
1347
|
/**
|
|
1376
1348
|
* GET /v2/runs (QA-052): a bounded, newest-first, keyset-paginated page of run
|
|
@@ -1391,7 +1363,7 @@ export class DaemonControlApiServer {
|
|
|
1391
1363
|
catch (error) {
|
|
1392
1364
|
return this.requestError(res, error);
|
|
1393
1365
|
}
|
|
1394
|
-
const { page, hasMore, nextCursor } = selectRunListPage(await this.opts.daemon.list(), query);
|
|
1366
|
+
const { page, hasMore, nextCursor } = selectRunListPage(await this.opts.daemon.list({ page: query }), query);
|
|
1395
1367
|
return this.json(res, 200, ControlRunListResponse.parse({
|
|
1396
1368
|
runs: page.map((r) => this.summarizeRunOrDiagnostic(r)),
|
|
1397
1369
|
nextCursor,
|
|
@@ -1415,10 +1387,9 @@ export class DaemonControlApiServer {
|
|
|
1415
1387
|
const ws = thread?.workspace;
|
|
1416
1388
|
if (!ws)
|
|
1417
1389
|
return null;
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
};
|
|
1390
|
+
const mode = ws.mode === "isolated" || ws.mode === "delegated" ? ws.mode : "in_place";
|
|
1391
|
+
const tree = mode === "delegated" ? ws.workspace_root : ws.worktree_path;
|
|
1392
|
+
return { mode, worktreePath: typeof tree === "string" && tree.trim() ? tree : null };
|
|
1422
1393
|
}
|
|
1423
1394
|
operatorDecisionFor(rec) {
|
|
1424
1395
|
return this.opts.services?.operatorDecision?.(rec.runId ?? rec.id, rec.params) ?? null;
|
|
@@ -1427,8 +1398,10 @@ export class DaemonControlApiServer {
|
|
|
1427
1398
|
const decision = this.operatorDecisionFor(rec);
|
|
1428
1399
|
if (!decision)
|
|
1429
1400
|
return null;
|
|
1430
|
-
|
|
1431
|
-
return
|
|
1401
|
+
// INV-062: the decision binds to the EXACT patch, never to a redacted copy.
|
|
1402
|
+
return decision.patchSha256 === exactPatch.runPatchDigest(rec, runRepoRoot(rec))
|
|
1403
|
+
? decision
|
|
1404
|
+
: null;
|
|
1432
1405
|
}
|
|
1433
1406
|
/** The RISK-OVERRIDABLE needs-decision signal: a terminal run whose review is
|
|
1434
1407
|
* blocked or checks failed, with no valid operator decision recorded. This is
|
|
@@ -1515,7 +1488,7 @@ export class DaemonControlApiServer {
|
|
|
1515
1488
|
return {
|
|
1516
1489
|
turnCtx: this.threadTurnRouteCtx(),
|
|
1517
1490
|
services: this.opts.services,
|
|
1518
|
-
listRuns: () => this.opts.daemon.list(),
|
|
1491
|
+
listRuns: (query) => this.opts.daemon.list(query),
|
|
1519
1492
|
readBody: (req) => this.readBody(req),
|
|
1520
1493
|
json: (res, status, body) => this.json(res, status, body),
|
|
1521
1494
|
requestError: (res, error) => this.requestError(res, error),
|
|
@@ -1692,20 +1665,6 @@ function readFailure(rec) {
|
|
|
1692
1665
|
runDir: rec.runDir ?? null,
|
|
1693
1666
|
});
|
|
1694
1667
|
}
|
|
1695
|
-
function appendRunAuditEvent(rec, type, payload) {
|
|
1696
|
-
if (!rec.runDir)
|
|
1697
|
-
return;
|
|
1698
|
-
try {
|
|
1699
|
-
// Single-counter invariant: while the run is active its EventLog owns the
|
|
1700
|
-
// seq space, so audit records MUST route through it (appendRunEvent does;
|
|
1701
|
-
// file-tail stamping only applies once the run is terminal). A tail-read
|
|
1702
|
-
// here would duplicate ids and break SSE Last-Event-ID resume.
|
|
1703
|
-
appendRunEvent(join(rec.runDir, "events.jsonl"), rec.runId ?? rec.id, rec.taskId ?? "unknown", type, payload);
|
|
1704
|
-
}
|
|
1705
|
-
catch {
|
|
1706
|
-
/* audit append must not change control behavior */
|
|
1707
|
-
}
|
|
1708
|
-
}
|
|
1709
1668
|
/** v0.9 strategy flags projected back so surfaces can tell a race from a repair loop. */
|
|
1710
1669
|
function strategyFromParams(p) {
|
|
1711
1670
|
if (p["untilClean"] === true)
|
|
@@ -1778,6 +1737,7 @@ function winnerWorkStateFor(rec) {
|
|
|
1778
1737
|
}
|
|
1779
1738
|
function summarizeRun(rec, eventsSnapshot) {
|
|
1780
1739
|
const p = paramsRecord(rec);
|
|
1740
|
+
const project = projectMetadata(rec);
|
|
1781
1741
|
// safeParse everywhere: one malformed job record (e.g. an old/foreign mode id)
|
|
1782
1742
|
// must degrade to an unknown field, never 500 the whole run list forever.
|
|
1783
1743
|
const parsedMode = ModeKind.safeParse(p["mode"]);
|
|
@@ -1828,15 +1788,18 @@ function summarizeRun(rec, eventsSnapshot) {
|
|
|
1828
1788
|
task?.run_lineage.delegated_from_run_id ??
|
|
1829
1789
|
null,
|
|
1830
1790
|
delegation: telemetry?.delegation ?? null,
|
|
1791
|
+
...continuationSummary(rec, eventsSnapshot),
|
|
1831
1792
|
taskId: rec.taskId,
|
|
1832
1793
|
state: rec.state,
|
|
1833
1794
|
runDir: rec.runDir,
|
|
1834
1795
|
error: rec.error,
|
|
1835
1796
|
failure,
|
|
1836
|
-
project
|
|
1797
|
+
project,
|
|
1798
|
+
executionRoot: runExecutionRoot(rec, project.root),
|
|
1837
1799
|
mode: parsedMode.success ? parsedMode.data : undefined,
|
|
1838
1800
|
strategy: strategyFromParams(p),
|
|
1839
|
-
prompt:
|
|
1801
|
+
prompt: rec.promptPreview ??
|
|
1802
|
+
(typeof p["prompt"] === "string" ? redactPrompt(p["prompt"]) : undefined),
|
|
1840
1803
|
harnesses: Array.isArray(p["harnesses"])
|
|
1841
1804
|
? p["harnesses"].filter((x) => typeof x === "string")
|
|
1842
1805
|
: undefined,
|
|
@@ -2125,7 +2088,7 @@ function budgetSnapshot(rec, decision, eventsSnapshot, integrity) {
|
|
|
2125
2088
|
});
|
|
2126
2089
|
}
|
|
2127
2090
|
function readPatch(rec) {
|
|
2128
|
-
return
|
|
2091
|
+
return exactPatch.readExactRunPatch(rec, runRepoRoot(rec));
|
|
2129
2092
|
}
|
|
2130
2093
|
function runRepoRoot(rec) {
|
|
2131
2094
|
const p = paramsRecord(rec);
|
|
@@ -2159,7 +2122,7 @@ function runRepoRoot(rec) {
|
|
|
2159
2122
|
function applyTargetRoot(target, rec) {
|
|
2160
2123
|
if (target.kind === "project")
|
|
2161
2124
|
return target.root;
|
|
2162
|
-
return runRepoRoot(rec);
|
|
2125
|
+
return runExecutionRoot(rec, runRepoRoot(rec));
|
|
2163
2126
|
}
|
|
2164
2127
|
/** Project the run record into the delivery package's single-owner apply gate. */
|
|
2165
2128
|
function applyGateError(rec, patch, targetRepoRoot, operatorDecision, finalVerify) {
|
|
@@ -2178,7 +2141,7 @@ function applyGateInputFor(rec, patch, targetRepoRoot, operatorDecision) {
|
|
|
2178
2141
|
workProduct: safeReadStructuredArtifact(rec, "final/work_product.yaml", WorkProduct),
|
|
2179
2142
|
patch,
|
|
2180
2143
|
...(files ? { filesManifest: files.manifest, manifestSha256: files.manifestSha256 } : {}),
|
|
2181
|
-
originalRepoRoot: runRepoRoot(rec),
|
|
2144
|
+
originalRepoRoot: runExecutionRoot(rec, runRepoRoot(rec)),
|
|
2182
2145
|
targetRepoRoot,
|
|
2183
2146
|
operatorDecision: operatorDecision
|
|
2184
2147
|
? {
|
|
@@ -2202,7 +2165,7 @@ function applyGateInputFor(rec, patch, targetRepoRoot, operatorDecision) {
|
|
|
2202
2165
|
/**
|
|
2203
2166
|
* The GET /runs/:id projection of the apply gate: null when the run has no
|
|
2204
2167
|
* patch artifact (nothing to apply); otherwise the derived verdict against
|
|
2205
|
-
* the run's
|
|
2168
|
+
* the run's recorded delivery target.
|
|
2206
2169
|
*/
|
|
2207
2170
|
/** Readiness AND open questions of a plan run, from ONE read of the same
|
|
2208
2171
|
* final/questions.json artifact (D17) — no plan-text re-parse, no double read. */
|
|
@@ -2222,14 +2185,17 @@ function councilFor(rec, mode) {
|
|
|
2222
2185
|
return safeReadStructuredArtifact(rec, "council/membership.yaml", CouncilProjection);
|
|
2223
2186
|
}
|
|
2224
2187
|
function applyEligibilityFor(rec, operatorDecision) {
|
|
2225
|
-
const
|
|
2188
|
+
const root = runExecutionRoot(rec, runRepoRoot(rec));
|
|
2189
|
+
const { patch, unavailable } = exactPatch.resolveRunPatch(rec, runRepoRoot(rec));
|
|
2226
2190
|
const files = readFilesWorkProduct(rec);
|
|
2227
2191
|
if (!files && (patch === null || patch.trim() === ""))
|
|
2228
2192
|
return null;
|
|
2229
|
-
const root = runRepoRoot(rec);
|
|
2230
2193
|
if (!root)
|
|
2231
2194
|
return null;
|
|
2232
|
-
return deriveApplyEligibility(
|
|
2195
|
+
return deriveApplyEligibility({
|
|
2196
|
+
...applyGateInputFor(rec, patch ?? "", root, operatorDecision),
|
|
2197
|
+
exactPatchUnavailable: unavailable,
|
|
2198
|
+
});
|
|
2233
2199
|
}
|
|
2234
2200
|
/** Compatibility projection for artifact-only CLI reads; the journal record is authority. */
|
|
2235
2201
|
function writeOperatorDecisionProjection(rec, record) {
|