owlrunkit 0.18.3 → 0.18.4
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/CHANGELOG.md +42 -0
- package/README.md +125 -52
- package/docs/architecture/OWLCODA_RUNKIT_0184_EXTERNAL_DELIVERY_INTAKE_PRODUCT_DECISION_20260812.md +209 -0
- package/docs/architecture/OWLCODA_RUNKIT_USAGE_MODE_SELECTION_PRODUCT_DECISION_20260811.md +8 -7
- package/integrations/codex/skills/owlcoda-runkit/SKILL.md +109 -77
- package/integrations/codex/skills/owlcoda-runkit/assets/templates/config.json +3 -3
- package/package.json +1 -1
- package/packages/attest/distribution-manifest-v1.json +5 -0
- package/packages/attest/src/quick-receipt-contract.mjs +3 -3
- package/packages/attest/src/quick.mjs +5 -0
- package/packages/attest/src/workspace-snapshot.mjs +10 -1
- package/registry-release-policy-v1.json +5 -5
- package/sbom.spdx.json +5 -5
- package/scripts/runkit-contract/assurance-router.mjs +25 -6
- package/scripts/runkit-contract/core-contract.mjs +9 -1
- package/scripts/runkit-contract/onboarding-doctor.mjs +1 -1
- package/scripts/runkit-contract/profile-onboarding.mjs +263 -15
- package/scripts/runkit-contract/project-cli-resolver.mjs +6 -0
- package/scripts/runkit-contract/project-external-delivery.mjs +707 -0
- package/scripts/runkit-contract/quick-verify.mjs +1 -1
- package/scripts/runkit-contract/runkit-bootstrap.mjs +6 -1
- package/scripts/runkit-contract/runkit-cli.mjs +278 -140
- package/scripts/runkit-contract/schemas/assurance-route-v2.schema.json +26 -0
- package/scripts/runkit-contract/schemas/profile-detection-v3.schema.json +170 -0
- package/scripts/runkit-contract/schemas/team-project-event-v3.schema.json +146 -0
- package/scripts/runkit-contract/schemas/team-project-status-v2.schema.json +85 -0
- package/scripts/runkit-contract/schemas/usage-mode-recommendation-v2.schema.json +136 -0
- package/scripts/runkit-contract/team-project.mjs +477 -15
- package/scripts/runkit-contract/usage-mode.mjs +28 -7
|
@@ -12,6 +12,11 @@ import path from "node:path";
|
|
|
12
12
|
|
|
13
13
|
import { validateLeaseOwnedPaths } from "./core-contract.mjs";
|
|
14
14
|
import { inspectProjectControlState } from "./project-control-state.mjs";
|
|
15
|
+
import { canonicalSourceFingerprint } from "./source-fingerprint.mjs";
|
|
16
|
+
import {
|
|
17
|
+
quickSnapshotFingerprintValid,
|
|
18
|
+
validWorkspaceSnapshotShape,
|
|
19
|
+
} from "../../packages/attest/src/quick-receipt-contract.mjs";
|
|
15
20
|
import {
|
|
16
21
|
assertAllowedKeys,
|
|
17
22
|
isRecord,
|
|
@@ -24,7 +29,9 @@ const PROJECT_ROOT = ".owlcoda/runkit/project";
|
|
|
24
29
|
const DEFINITION_SCHEMA = "OwlCodaRunKitTeamProjectDefinitionV1";
|
|
25
30
|
const EVENT_SCHEMA = "OwlCodaRunKitTeamProjectEventV1";
|
|
26
31
|
const EVENT_SCHEMA_V2 = "OwlCodaRunKitTeamProjectEventV2";
|
|
27
|
-
const
|
|
32
|
+
const EVENT_SCHEMA_V3 = "OwlCodaRunKitTeamProjectEventV3";
|
|
33
|
+
const STATUS_SCHEMA_V1 = "OwlCodaRunKitTeamProjectStatusV1";
|
|
34
|
+
const STATUS_SCHEMA_V2 = "OwlCodaRunKitTeamProjectStatusV2";
|
|
28
35
|
|
|
29
36
|
const DEFINITION_KEYS = [
|
|
30
37
|
"schemaVersion",
|
|
@@ -92,6 +99,17 @@ const EVENT_KEYS = Object.freeze({
|
|
|
92
99
|
"sourceRef", "rights", "inputRef", "outputRef", "decisionRef",
|
|
93
100
|
"verificationRefs", "outcomeRef", "version",
|
|
94
101
|
],
|
|
102
|
+
source_delivery_imported: [
|
|
103
|
+
"schemaVersion", "eventId", "type", "occurredAt", "deliveryId",
|
|
104
|
+
"projectId", "projectDefinitionSha256", "assignmentId", "workItemId",
|
|
105
|
+
"producerId", "targetWorkspaceRef",
|
|
106
|
+
"targetHead", "entrySnapshotRef", "entrySnapshotSha256",
|
|
107
|
+
"deliveryPacketSha256", "deliveryCandidateFingerprint",
|
|
108
|
+
"workspaceSnapshotFingerprint", "overlayStatus", "candidateFiles",
|
|
109
|
+
"changedPaths", "toolCachePaths", "dependencyEnvironmentPaths",
|
|
110
|
+
"unchangedPacketPaths", "outOfScopeSourcePaths", "targetSnapshot",
|
|
111
|
+
"proven", "notProven", "targetWritePerformed", "authorizationGranted",
|
|
112
|
+
],
|
|
95
113
|
});
|
|
96
114
|
|
|
97
115
|
function canonical(value) {
|
|
@@ -122,6 +140,26 @@ function nullableString(value, label) {
|
|
|
122
140
|
return nonEmptyString(value, label);
|
|
123
141
|
}
|
|
124
142
|
|
|
143
|
+
function sha256Ref(value, label) {
|
|
144
|
+
if (typeof value !== "string" || !/^sha256:[a-f0-9]{64}$/u.test(value)) {
|
|
145
|
+
throw new Error(`${label} must be a canonical SHA-256 reference.`);
|
|
146
|
+
}
|
|
147
|
+
return value;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function safeSourcePath(value, label) {
|
|
151
|
+
if (typeof value !== "string"
|
|
152
|
+
|| value.length === 0
|
|
153
|
+
|| path.isAbsolute(value)
|
|
154
|
+
|| path.win32.parse(value).root
|
|
155
|
+
|| value.includes("\\")
|
|
156
|
+
|| value.includes("\0")
|
|
157
|
+
|| value.split("/").some(segment => !segment || segment === "." || segment === "..")) {
|
|
158
|
+
throw new Error(`${label} must be a safe repository-relative path.`);
|
|
159
|
+
}
|
|
160
|
+
return value;
|
|
161
|
+
}
|
|
162
|
+
|
|
125
163
|
function stringArray(value, label, { allowEmpty = true } = {}) {
|
|
126
164
|
if (!Array.isArray(value)
|
|
127
165
|
|| (!allowEmpty && value.length === 0)
|
|
@@ -167,6 +205,30 @@ function withProjectLock(root, operation) {
|
|
|
167
205
|
}
|
|
168
206
|
}
|
|
169
207
|
|
|
208
|
+
export function teamProjectDefinitionBindingV1(definition) {
|
|
209
|
+
const normalized = validateDefinition(definition);
|
|
210
|
+
return {
|
|
211
|
+
projectId: normalized.projectId,
|
|
212
|
+
projectDefinitionSha256: `sha256:${sha256(canonical(normalized))}`,
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export function withTeamProjectControlLockV1({ workspaceRoot, operation }) {
|
|
217
|
+
if (typeof operation !== "function") {
|
|
218
|
+
throw new Error("Team project control operation must be a function.");
|
|
219
|
+
}
|
|
220
|
+
const loaded = loadProject(workspaceRoot);
|
|
221
|
+
return withProjectLock(loaded.projectRoot, () => {
|
|
222
|
+
const refreshed = loadProject(workspaceRoot);
|
|
223
|
+
return operation({
|
|
224
|
+
workspaceRoot: refreshed.root,
|
|
225
|
+
projectRoot: refreshed.projectRoot,
|
|
226
|
+
definition: structuredClone(refreshed.definition),
|
|
227
|
+
definitionBinding: teamProjectDefinitionBindingV1(refreshed.definition),
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
|
|
170
232
|
function uniqueIds(rows, label) {
|
|
171
233
|
const ids = rows.map(row => safeIdentifier(row.id, `${label} id`));
|
|
172
234
|
if (new Set(ids).size !== ids.length) throw new Error(`${label} ids must be unique.`);
|
|
@@ -420,6 +482,135 @@ function reworkProjection(rework, attempt) {
|
|
|
420
482
|
: null;
|
|
421
483
|
}
|
|
422
484
|
|
|
485
|
+
function sourceDeliveryProjection(delivery) {
|
|
486
|
+
return delivery
|
|
487
|
+
? {
|
|
488
|
+
projectId: delivery.projectId,
|
|
489
|
+
projectDefinitionSha256: delivery.projectDefinitionSha256,
|
|
490
|
+
deliveryId: delivery.deliveryId,
|
|
491
|
+
workItemId: delivery.workItemId,
|
|
492
|
+
assignmentId: delivery.assignmentId,
|
|
493
|
+
producerId: delivery.producerId,
|
|
494
|
+
targetWorkspaceRef: delivery.targetWorkspaceRef,
|
|
495
|
+
targetHead: delivery.targetHead,
|
|
496
|
+
entrySnapshotRef: delivery.entrySnapshotRef,
|
|
497
|
+
entrySnapshotSha256: delivery.entrySnapshotSha256,
|
|
498
|
+
deliveryPacketSha256: delivery.deliveryPacketSha256,
|
|
499
|
+
deliveryCandidateFingerprint: delivery.deliveryCandidateFingerprint,
|
|
500
|
+
workspaceSnapshotFingerprint: delivery.workspaceSnapshotFingerprint,
|
|
501
|
+
overlayStatus: delivery.overlayStatus,
|
|
502
|
+
candidateFiles: delivery.candidateFiles.map(row => ({ ...row })),
|
|
503
|
+
candidatePaths: delivery.candidateFiles.map(row => row.path),
|
|
504
|
+
changedPaths: [...delivery.changedPaths],
|
|
505
|
+
toolCachePaths: [...delivery.toolCachePaths],
|
|
506
|
+
dependencyEnvironmentPaths: [...delivery.dependencyEnvironmentPaths],
|
|
507
|
+
unchangedPacketPaths: [...delivery.unchangedPacketPaths],
|
|
508
|
+
proven: [...delivery.proven],
|
|
509
|
+
notProven: [...delivery.notProven],
|
|
510
|
+
occurredAt: delivery.occurredAt,
|
|
511
|
+
truthRef: delivery.ref,
|
|
512
|
+
targetWritePerformed: false,
|
|
513
|
+
authorizationGranted: false,
|
|
514
|
+
}
|
|
515
|
+
: null;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
function sourceDeliveryDelta(deliveries) {
|
|
519
|
+
const current = deliveries.at(-1) ?? null;
|
|
520
|
+
if (!current) return null;
|
|
521
|
+
const previous = deliveries.at(-2) ?? null;
|
|
522
|
+
if (!previous) {
|
|
523
|
+
return {
|
|
524
|
+
status: "first_delivery",
|
|
525
|
+
currentDeliveryId: current.deliveryId,
|
|
526
|
+
previousDeliveryId: null,
|
|
527
|
+
addedPaths: current.candidateFiles.map(row => row.path),
|
|
528
|
+
changedPaths: [],
|
|
529
|
+
removedPaths: [],
|
|
530
|
+
unchangedPaths: [],
|
|
531
|
+
};
|
|
532
|
+
}
|
|
533
|
+
const currentFiles = new Map(current.candidateFiles.map(row => [row.path, row.sha256]));
|
|
534
|
+
const previousFiles = new Map(previous.candidateFiles.map(row => [row.path, row.sha256]));
|
|
535
|
+
const allPaths = uniqueSorted([...currentFiles.keys(), ...previousFiles.keys()]);
|
|
536
|
+
const addedPaths = allPaths.filter(filePath => (
|
|
537
|
+
currentFiles.has(filePath) && !previousFiles.has(filePath)
|
|
538
|
+
));
|
|
539
|
+
const removedPaths = allPaths.filter(filePath => (
|
|
540
|
+
!currentFiles.has(filePath) && previousFiles.has(filePath)
|
|
541
|
+
));
|
|
542
|
+
const changedPaths = allPaths.filter(filePath => (
|
|
543
|
+
currentFiles.has(filePath)
|
|
544
|
+
&& previousFiles.has(filePath)
|
|
545
|
+
&& currentFiles.get(filePath) !== previousFiles.get(filePath)
|
|
546
|
+
));
|
|
547
|
+
const unchangedPaths = allPaths.filter(filePath => (
|
|
548
|
+
currentFiles.has(filePath)
|
|
549
|
+
&& previousFiles.has(filePath)
|
|
550
|
+
&& currentFiles.get(filePath) === previousFiles.get(filePath)
|
|
551
|
+
));
|
|
552
|
+
return {
|
|
553
|
+
status: addedPaths.length || removedPaths.length || changedPaths.length
|
|
554
|
+
? "changed"
|
|
555
|
+
: "unchanged",
|
|
556
|
+
currentDeliveryId: current.deliveryId,
|
|
557
|
+
previousDeliveryId: previous.deliveryId,
|
|
558
|
+
addedPaths,
|
|
559
|
+
changedPaths,
|
|
560
|
+
removedPaths,
|
|
561
|
+
unchangedPaths,
|
|
562
|
+
};
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
function staleHandoffsForTakeover({ loaded, status, agentId, responsibilities }) {
|
|
566
|
+
const responsibilityIds = new Set(responsibilities.map(row => row.workItemId));
|
|
567
|
+
const pendingEventIds = new Set(status.workItems
|
|
568
|
+
.map(row => row.pendingHandoff?.eventId)
|
|
569
|
+
.filter(Boolean));
|
|
570
|
+
return loaded.events
|
|
571
|
+
.filter(({ event }) => (
|
|
572
|
+
event.type === "handoff_recorded"
|
|
573
|
+
&& !pendingEventIds.has(event.eventId)
|
|
574
|
+
&& (responsibilityIds.has(event.workItemId)
|
|
575
|
+
|| event.fromAgentId === agentId
|
|
576
|
+
|| event.toAgentId === agentId)
|
|
577
|
+
))
|
|
578
|
+
.map(({ event, ref }) => {
|
|
579
|
+
const laterFacts = loaded.events.filter(row => (
|
|
580
|
+
row.event.workItemId === event.workItemId
|
|
581
|
+
&& compareEventFacts(row.event, event) > 0
|
|
582
|
+
&& [
|
|
583
|
+
"agent_assigned",
|
|
584
|
+
"checkpoint_recorded",
|
|
585
|
+
"handoff_recorded",
|
|
586
|
+
"rework_returned",
|
|
587
|
+
"source_delivery_imported",
|
|
588
|
+
].includes(row.event.type)
|
|
589
|
+
));
|
|
590
|
+
let reason = "superseded_by_current_truth";
|
|
591
|
+
if (laterFacts.some(row => row.event.type === "handoff_recorded")) {
|
|
592
|
+
reason = "redirected_by_later_handoff";
|
|
593
|
+
} else if (laterFacts.some(row => (
|
|
594
|
+
row.event.type === "agent_assigned" || row.event.type === "rework_returned"
|
|
595
|
+
))) {
|
|
596
|
+
reason = "superseded_by_assignment";
|
|
597
|
+
} else if (laterFacts.length > 0) {
|
|
598
|
+
reason = "superseded_by_later_work_fact";
|
|
599
|
+
}
|
|
600
|
+
return {
|
|
601
|
+
eventId: event.eventId,
|
|
602
|
+
truthRef: ref,
|
|
603
|
+
assignmentId: event.assignmentId,
|
|
604
|
+
workItemId: event.workItemId,
|
|
605
|
+
fromAgentId: event.fromAgentId,
|
|
606
|
+
toAgentId: event.toAgentId,
|
|
607
|
+
occurredAt: event.occurredAt,
|
|
608
|
+
reason,
|
|
609
|
+
};
|
|
610
|
+
})
|
|
611
|
+
.sort((left, right) => compareIdentifiers(left.eventId, right.eventId));
|
|
612
|
+
}
|
|
613
|
+
|
|
423
614
|
function validateTimestamp(value) {
|
|
424
615
|
if (typeof value !== "string"
|
|
425
616
|
|| !/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/u.test(value)
|
|
@@ -434,14 +625,20 @@ function validateEventShape(value, definition) {
|
|
|
434
625
|
throw new Error("Team project event type is invalid.");
|
|
435
626
|
}
|
|
436
627
|
assertAllowedKeys(value, `Team project event ${value.eventId ?? "unknown"}`, EVENT_KEYS[value.type]);
|
|
437
|
-
if (![EVENT_SCHEMA, EVENT_SCHEMA_V2].includes(value.schemaVersion)) {
|
|
628
|
+
if (![EVENT_SCHEMA, EVENT_SCHEMA_V2, EVENT_SCHEMA_V3].includes(value.schemaVersion)) {
|
|
438
629
|
throw new Error(
|
|
439
|
-
`Team project event schemaVersion must be ${EVENT_SCHEMA} or ${
|
|
630
|
+
`Team project event schemaVersion must be ${EVENT_SCHEMA}, ${EVENT_SCHEMA_V2}, or ${EVENT_SCHEMA_V3}.`,
|
|
440
631
|
);
|
|
441
632
|
}
|
|
442
633
|
if (value.type === "rework_returned" && value.schemaVersion !== EVENT_SCHEMA_V2) {
|
|
443
634
|
throw new Error(`rework_returned requires schemaVersion ${EVENT_SCHEMA_V2}.`);
|
|
444
635
|
}
|
|
636
|
+
if (value.type === "source_delivery_imported" && value.schemaVersion !== EVENT_SCHEMA_V3) {
|
|
637
|
+
throw new Error(`source_delivery_imported requires schemaVersion ${EVENT_SCHEMA_V3}.`);
|
|
638
|
+
}
|
|
639
|
+
if (value.schemaVersion === EVENT_SCHEMA_V3 && value.type !== "source_delivery_imported") {
|
|
640
|
+
throw new Error(`${EVENT_SCHEMA_V3} is reserved for source_delivery_imported events.`);
|
|
641
|
+
}
|
|
445
642
|
safeIdentifier(value.eventId, "eventId");
|
|
446
643
|
validateTimestamp(value.occurredAt);
|
|
447
644
|
const workItemIds = new Set(definition.workItems.map(row => row.id));
|
|
@@ -455,6 +652,7 @@ function validateEventShape(value, definition) {
|
|
|
455
652
|
"checkpoint_recorded",
|
|
456
653
|
"handoff_recorded",
|
|
457
654
|
"rework_returned",
|
|
655
|
+
"source_delivery_imported",
|
|
458
656
|
"verification_deferred",
|
|
459
657
|
].includes(value.type)) {
|
|
460
658
|
assertWorkItem(value.workItemId);
|
|
@@ -464,6 +662,7 @@ function validateEventShape(value, definition) {
|
|
|
464
662
|
"checkpoint_recorded",
|
|
465
663
|
"handoff_recorded",
|
|
466
664
|
"rework_returned",
|
|
665
|
+
"source_delivery_imported",
|
|
467
666
|
].includes(value.type)) {
|
|
468
667
|
safeIdentifier(value.assignmentId, "assignmentId");
|
|
469
668
|
}
|
|
@@ -582,6 +781,89 @@ function validateEventShape(value, definition) {
|
|
|
582
781
|
stringArray(value.verificationRefs, "Data candidate verificationRefs");
|
|
583
782
|
nullableString(value.outcomeRef, "Data candidate outcomeRef");
|
|
584
783
|
nonEmptyString(value.version, "Data candidate version");
|
|
784
|
+
} else if (value.type === "source_delivery_imported") {
|
|
785
|
+
safeIdentifier(value.projectId, "External delivery projectId");
|
|
786
|
+
sha256Ref(value.projectDefinitionSha256, "External delivery projectDefinitionSha256");
|
|
787
|
+
const definitionBinding = teamProjectDefinitionBindingV1(definition);
|
|
788
|
+
if (value.projectId !== definitionBinding.projectId
|
|
789
|
+
|| value.projectDefinitionSha256 !== definitionBinding.projectDefinitionSha256) {
|
|
790
|
+
throw new Error("External delivery project identity does not match current project truth.");
|
|
791
|
+
}
|
|
792
|
+
safeIdentifier(value.deliveryId, "deliveryId");
|
|
793
|
+
safeIdentifier(value.producerId, "producerId");
|
|
794
|
+
nonEmptyString(value.targetWorkspaceRef, "External delivery targetWorkspaceRef");
|
|
795
|
+
if (!path.isAbsolute(value.targetWorkspaceRef)) {
|
|
796
|
+
throw new Error("External delivery targetWorkspaceRef must be absolute.");
|
|
797
|
+
}
|
|
798
|
+
if (typeof value.targetHead !== "string"
|
|
799
|
+
|| !/^(?:[a-f0-9]{40}|[a-f0-9]{64})$/u.test(value.targetHead)) {
|
|
800
|
+
throw new Error("External delivery targetHead is invalid.");
|
|
801
|
+
}
|
|
802
|
+
if ((value.entrySnapshotRef === null) !== (value.entrySnapshotSha256 === null)) {
|
|
803
|
+
throw new Error("External delivery entry snapshot ref and hash must be supplied together.");
|
|
804
|
+
}
|
|
805
|
+
if (value.entrySnapshotRef !== null) {
|
|
806
|
+
nonEmptyString(value.entrySnapshotRef, "External delivery entrySnapshotRef");
|
|
807
|
+
sha256Ref(value.entrySnapshotSha256, "External delivery entrySnapshotSha256");
|
|
808
|
+
}
|
|
809
|
+
sha256Ref(value.deliveryPacketSha256, "External delivery packet hash");
|
|
810
|
+
if (typeof value.deliveryCandidateFingerprint !== "string"
|
|
811
|
+
|| !/^[a-f0-9]{64}$/u.test(value.deliveryCandidateFingerprint)) {
|
|
812
|
+
throw new Error("External delivery candidate fingerprint is invalid.");
|
|
813
|
+
}
|
|
814
|
+
sha256Ref(value.workspaceSnapshotFingerprint, "External delivery workspace snapshot fingerprint");
|
|
815
|
+
if (!["candidate_only", "attested", "attested_with_non_source_changes"].includes(value.overlayStatus)) {
|
|
816
|
+
throw new Error("External delivery overlayStatus is invalid.");
|
|
817
|
+
}
|
|
818
|
+
if (!Array.isArray(value.candidateFiles) || value.candidateFiles.length === 0) {
|
|
819
|
+
throw new Error("External delivery candidateFiles must contain at least one file.");
|
|
820
|
+
}
|
|
821
|
+
const candidateFiles = value.candidateFiles.map((row) => {
|
|
822
|
+
assertAllowedKeys(row, "External delivery candidate file", ["path", "sha256"]);
|
|
823
|
+
const filePath = safeSourcePath(row.path, "External delivery candidate file path");
|
|
824
|
+
if (typeof row.sha256 !== "string" || !/^[a-f0-9]{64}$/u.test(row.sha256)) {
|
|
825
|
+
throw new Error(`External delivery candidate file hash is invalid: ${filePath}.`);
|
|
826
|
+
}
|
|
827
|
+
return { path: filePath, sha256: row.sha256 };
|
|
828
|
+
});
|
|
829
|
+
if (new Set(candidateFiles.map(row => row.path)).size !== candidateFiles.length
|
|
830
|
+
|| canonical([...candidateFiles].sort((left, right) => (
|
|
831
|
+
compareIdentifiers(left.path, right.path)
|
|
832
|
+
)))
|
|
833
|
+
!== canonical(candidateFiles)) {
|
|
834
|
+
throw new Error("External delivery candidateFiles must be uniquely sorted by path.");
|
|
835
|
+
}
|
|
836
|
+
if (canonicalSourceFingerprint(Object.fromEntries(
|
|
837
|
+
candidateFiles.map(row => [row.path, row.sha256]),
|
|
838
|
+
)) !== value.deliveryCandidateFingerprint) {
|
|
839
|
+
throw new Error("External delivery candidate fingerprint does not match its file map.");
|
|
840
|
+
}
|
|
841
|
+
for (const [field, label] of [
|
|
842
|
+
["changedPaths", "External delivery changedPaths"],
|
|
843
|
+
["toolCachePaths", "External delivery toolCachePaths"],
|
|
844
|
+
["dependencyEnvironmentPaths", "External delivery dependencyEnvironmentPaths"],
|
|
845
|
+
["unchangedPacketPaths", "External delivery unchangedPacketPaths"],
|
|
846
|
+
["outOfScopeSourcePaths", "External delivery outOfScopeSourcePaths"],
|
|
847
|
+
]) {
|
|
848
|
+
const paths = stringArray(value[field], label);
|
|
849
|
+
for (const filePath of paths) safeSourcePath(filePath, label);
|
|
850
|
+
if (canonical([...paths].sort()) !== canonical(paths)) {
|
|
851
|
+
throw new Error(`${label} must be sorted.`);
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
if (value.outOfScopeSourcePaths.length !== 0) {
|
|
855
|
+
throw new Error("An imported external delivery cannot contain out-of-scope source paths.");
|
|
856
|
+
}
|
|
857
|
+
stringArray(value.proven, "External delivery proven statements", { allowEmpty: false });
|
|
858
|
+
stringArray(value.notProven, "External delivery notProven statements", { allowEmpty: false });
|
|
859
|
+
if (!validWorkspaceSnapshotShape(value.targetSnapshot)
|
|
860
|
+
|| !quickSnapshotFingerprintValid(value.targetSnapshot)
|
|
861
|
+
|| value.targetSnapshot.sourceFingerprint !== value.workspaceSnapshotFingerprint) {
|
|
862
|
+
throw new Error("External delivery targetSnapshot identity is invalid.");
|
|
863
|
+
}
|
|
864
|
+
if (value.targetWritePerformed !== false || value.authorizationGranted !== false) {
|
|
865
|
+
throw new Error("External delivery cannot grant authority or write the target workspace.");
|
|
866
|
+
}
|
|
585
867
|
}
|
|
586
868
|
return structuredClone(value);
|
|
587
869
|
}
|
|
@@ -596,9 +878,11 @@ function projectEvents(definition, records) {
|
|
|
596
878
|
const gatePasses = new Map();
|
|
597
879
|
const verifications = new Map();
|
|
598
880
|
const dataCandidates = [];
|
|
881
|
+
const sourceDeliveries = [];
|
|
599
882
|
const seenAssignments = new Set();
|
|
600
883
|
const seenReworks = new Set();
|
|
601
884
|
const seenDecisions = new Set();
|
|
885
|
+
const seenDeliveries = new Set();
|
|
602
886
|
|
|
603
887
|
for (const { event, ref } of records) {
|
|
604
888
|
validateEventShape(event, definition);
|
|
@@ -692,6 +976,25 @@ function projectEvents(definition, records) {
|
|
|
692
976
|
throw new Error(`Duplicate data candidate ${event.candidateId}.`);
|
|
693
977
|
}
|
|
694
978
|
dataCandidates.push({ ...event, ref });
|
|
979
|
+
} else if (event.type === "source_delivery_imported") {
|
|
980
|
+
if (seenDeliveries.has(event.deliveryId)) {
|
|
981
|
+
throw new Error(`Duplicate external source delivery ${event.deliveryId}.`);
|
|
982
|
+
}
|
|
983
|
+
const current = assignments.get(event.workItemId) ?? null;
|
|
984
|
+
if (current?.assignmentId !== event.assignmentId) {
|
|
985
|
+
throw new Error(
|
|
986
|
+
`External source delivery ${event.deliveryId} does not bind the current assignment.`,
|
|
987
|
+
);
|
|
988
|
+
}
|
|
989
|
+
const currentCheckpoint = checkpoints.get(event.workItemId) ?? null;
|
|
990
|
+
if (currentCheckpoint?.assignmentId === current.assignmentId
|
|
991
|
+
&& ["completed", "failed"].includes(currentCheckpoint.state)) {
|
|
992
|
+
throw new Error(
|
|
993
|
+
`External source delivery ${event.deliveryId} cannot attach to terminal WorkItem truth.`,
|
|
994
|
+
);
|
|
995
|
+
}
|
|
996
|
+
seenDeliveries.add(event.deliveryId);
|
|
997
|
+
sourceDeliveries.push({ ...event, ref });
|
|
695
998
|
}
|
|
696
999
|
}
|
|
697
1000
|
return {
|
|
@@ -704,6 +1007,7 @@ function projectEvents(definition, records) {
|
|
|
704
1007
|
gatePasses,
|
|
705
1008
|
verifications,
|
|
706
1009
|
dataCandidates,
|
|
1010
|
+
sourceDeliveries,
|
|
707
1011
|
};
|
|
708
1012
|
}
|
|
709
1013
|
|
|
@@ -1031,6 +1335,9 @@ function projectStatus(definition, records, workspaceRoot = null) {
|
|
|
1031
1335
|
const checkpoint = state.checkpoints.get(item.id) ?? null;
|
|
1032
1336
|
const handoff = state.handoffs.get(item.id) ?? null;
|
|
1033
1337
|
const rework = state.reworks.get(item.id) ?? null;
|
|
1338
|
+
const sourceDeliveries = state.sourceDeliveries.filter(row => row.workItemId === item.id);
|
|
1339
|
+
const latestSourceDeliveryFact = sourceDeliveries.at(-1) ?? null;
|
|
1340
|
+
const latestSourceDelivery = sourceDeliveryProjection(sourceDeliveries.at(-1) ?? null);
|
|
1034
1341
|
const currentRework = rework?.assignmentId === assignment?.assignmentId ? rework : null;
|
|
1035
1342
|
const latestFact = latestEventFact(latestEventFact(checkpoint, handoff), currentRework);
|
|
1036
1343
|
const activeHandoff = handoff
|
|
@@ -1038,7 +1345,16 @@ function projectStatus(definition, records, workspaceRoot = null) {
|
|
|
1038
1345
|
&& handoff.fromAgentId === assignment?.agentId
|
|
1039
1346
|
? handoff
|
|
1040
1347
|
: null;
|
|
1041
|
-
const
|
|
1348
|
+
const latestHandoffFact = latestEventFact(activeHandoff, latestSourceDeliveryFact);
|
|
1349
|
+
const handoffSupersededByDelivery = activeHandoff
|
|
1350
|
+
&& latestFact?.eventId === activeHandoff.eventId
|
|
1351
|
+
&& latestHandoffFact?.eventId !== activeHandoff.eventId;
|
|
1352
|
+
const projectedFact = handoffSupersededByDelivery
|
|
1353
|
+
? latestEventFact(checkpoint, currentRework)
|
|
1354
|
+
: latestFact;
|
|
1355
|
+
const pendingHandoff = activeHandoff
|
|
1356
|
+
&& latestFact?.eventId === activeHandoff.eventId
|
|
1357
|
+
&& latestHandoffFact?.eventId === activeHandoff.eventId
|
|
1042
1358
|
? pendingHandoffProjection(activeHandoff)
|
|
1043
1359
|
: null;
|
|
1044
1360
|
const executionTruth = executionTruthFor(workspaceRoot, assignment);
|
|
@@ -1046,7 +1362,7 @@ function projectStatus(definition, records, workspaceRoot = null) {
|
|
|
1046
1362
|
let status = "planned";
|
|
1047
1363
|
if (assignment) status = unresolvedDependencies.length > 0 ? "waiting_dependency" : "active";
|
|
1048
1364
|
if (pendingHandoff) status = "planned";
|
|
1049
|
-
if (
|
|
1365
|
+
if (projectedFact?.type === "checkpoint_recorded") status = checkpoint.state;
|
|
1050
1366
|
if (executionTruth && (executionTruth.status !== "bound"
|
|
1051
1367
|
|| (executionTruth.lifecycle === "closed"
|
|
1052
1368
|
&& executionTruth.closeoutDecision !== "accepted"))) {
|
|
@@ -1075,6 +1391,7 @@ function projectStatus(definition, records, workspaceRoot = null) {
|
|
|
1075
1391
|
...(checkpoint ? [checkpoint.ref] : []),
|
|
1076
1392
|
...(handoff ? [handoff.ref] : []),
|
|
1077
1393
|
...(rework ? [rework.ref] : []),
|
|
1394
|
+
...sourceDeliveries.map(row => row.ref),
|
|
1078
1395
|
];
|
|
1079
1396
|
const projectedRework = reworkProjection(
|
|
1080
1397
|
currentRework,
|
|
@@ -1082,9 +1399,9 @@ function projectStatus(definition, records, workspaceRoot = null) {
|
|
|
1082
1399
|
? assignmentEvents.findIndex(row => row.assignmentId === currentRework.assignmentId) + 1
|
|
1083
1400
|
: null,
|
|
1084
1401
|
);
|
|
1085
|
-
const latestSummary =
|
|
1086
|
-
?
|
|
1087
|
-
:
|
|
1402
|
+
const latestSummary = projectedFact?.type === "rework_returned"
|
|
1403
|
+
? projectedFact.reason
|
|
1404
|
+
: projectedFact?.summary ?? null;
|
|
1088
1405
|
return {
|
|
1089
1406
|
workItemId: item.id,
|
|
1090
1407
|
title: item.title,
|
|
@@ -1098,17 +1415,18 @@ function projectStatus(definition, records, workspaceRoot = null) {
|
|
|
1098
1415
|
executionTruth,
|
|
1099
1416
|
dependencies: [...item.dependencies],
|
|
1100
1417
|
unresolvedDependencies,
|
|
1101
|
-
blockers:
|
|
1418
|
+
blockers: projectedFact?.type === "checkpoint_recorded"
|
|
1102
1419
|
? checkpoint.blockerRefs
|
|
1103
1420
|
: unresolvedDependencies.map(id => `work-item:${id}`),
|
|
1104
|
-
decisionRefs:
|
|
1421
|
+
decisionRefs: projectedFact?.type === "checkpoint_recorded" ? checkpoint.decisionRefs : [],
|
|
1105
1422
|
progress: measured,
|
|
1106
1423
|
summary: latestSummary,
|
|
1107
|
-
nextAction:
|
|
1424
|
+
nextAction: projectedFact?.nextAction ?? null,
|
|
1108
1425
|
pendingHandoff,
|
|
1109
1426
|
rework: projectedRework,
|
|
1427
|
+
latestSourceDelivery,
|
|
1110
1428
|
lastCheckpointAt: checkpoint?.occurredAt ?? null,
|
|
1111
|
-
evidenceRefs:
|
|
1429
|
+
evidenceRefs: projectedFact?.evidenceRefs ?? [],
|
|
1112
1430
|
assignmentHistory: history,
|
|
1113
1431
|
truthRefs,
|
|
1114
1432
|
};
|
|
@@ -1311,6 +1629,7 @@ function projectStatus(definition, records, workspaceRoot = null) {
|
|
|
1311
1629
|
truthRefs: [row.ref],
|
|
1312
1630
|
};
|
|
1313
1631
|
});
|
|
1632
|
+
const sourceDeliveries = state.sourceDeliveries.map(sourceDeliveryProjection);
|
|
1314
1633
|
let overall = "planned";
|
|
1315
1634
|
if (workItems.every(row => row.status === "completed")
|
|
1316
1635
|
&& integrationGates.every(row => row.status === "passed")
|
|
@@ -1318,7 +1637,7 @@ function projectStatus(definition, records, workspaceRoot = null) {
|
|
|
1318
1637
|
else if (openDecisions.length > 0 || workItems.some(row => row.status === "failed")) overall = "active_with_blockers";
|
|
1319
1638
|
else if (workItems.some(row => row.status !== "planned" || row.pendingHandoff !== null)) overall = "active";
|
|
1320
1639
|
const status = {
|
|
1321
|
-
schemaVersion:
|
|
1640
|
+
schemaVersion: STATUS_SCHEMA_V2,
|
|
1322
1641
|
status: "team_project_status",
|
|
1323
1642
|
projectId: definition.projectId,
|
|
1324
1643
|
objective: definition.objective,
|
|
@@ -1336,6 +1655,7 @@ function projectStatus(definition, records, workspaceRoot = null) {
|
|
|
1336
1655
|
openDeferredVerificationIds,
|
|
1337
1656
|
integrationGates,
|
|
1338
1657
|
dataCandidates,
|
|
1658
|
+
sourceDeliveries,
|
|
1339
1659
|
projectTruthHash: sha256(canonical({ definition, events: records.map(row => row.event) })),
|
|
1340
1660
|
authorizationGranted: false,
|
|
1341
1661
|
};
|
|
@@ -1513,6 +1833,18 @@ function validateEventAgainstState(event, definition, records, workspaceRoot) {
|
|
|
1513
1833
|
if (state.dataCandidates.some(row => row.candidateId === normalized.candidateId)) {
|
|
1514
1834
|
throw new Error(`Data candidate ${normalized.candidateId} already exists.`);
|
|
1515
1835
|
}
|
|
1836
|
+
} else if (normalized.type === "source_delivery_imported") {
|
|
1837
|
+
if (state.sourceDeliveries.some(row => row.deliveryId === normalized.deliveryId)) {
|
|
1838
|
+
throw new Error(`External source delivery ${normalized.deliveryId} already exists.`);
|
|
1839
|
+
}
|
|
1840
|
+
const current = state.assignments.get(normalized.workItemId) ?? null;
|
|
1841
|
+
if (current?.assignmentId !== normalized.assignmentId) {
|
|
1842
|
+
throw new Error("External source delivery must bind the current assignment.");
|
|
1843
|
+
}
|
|
1844
|
+
const projected = status.workItems.find(row => row.workItemId === normalized.workItemId);
|
|
1845
|
+
if (["completed", "failed"].includes(projected?.status)) {
|
|
1846
|
+
throw new Error("External source delivery requires a non-terminal WorkItem.");
|
|
1847
|
+
}
|
|
1516
1848
|
}
|
|
1517
1849
|
return normalized;
|
|
1518
1850
|
}
|
|
@@ -2155,7 +2487,28 @@ export function checkpointTeamProjectV1({
|
|
|
2155
2487
|
};
|
|
2156
2488
|
}
|
|
2157
2489
|
|
|
2490
|
+
function legacyWorkItemProjection(workItem) {
|
|
2491
|
+
if (workItem === null) return null;
|
|
2492
|
+
const { latestSourceDelivery: _latestSourceDelivery, ...legacy } = workItem;
|
|
2493
|
+
return legacy;
|
|
2494
|
+
}
|
|
2495
|
+
|
|
2158
2496
|
export function readTeamProjectStatusV1({ workspaceRoot }) {
|
|
2497
|
+
const loaded = loadProject(workspaceRoot);
|
|
2498
|
+
const { sourceDeliveries: _sourceDeliveries, ...legacy } = projectStatus(
|
|
2499
|
+
loaded.definition,
|
|
2500
|
+
loaded.events,
|
|
2501
|
+
loaded.root,
|
|
2502
|
+
);
|
|
2503
|
+
return {
|
|
2504
|
+
...legacy,
|
|
2505
|
+
schemaVersion: STATUS_SCHEMA_V1,
|
|
2506
|
+
workItems: legacy.workItems.map(legacyWorkItemProjection),
|
|
2507
|
+
exitCode: 0,
|
|
2508
|
+
};
|
|
2509
|
+
}
|
|
2510
|
+
|
|
2511
|
+
export function readTeamProjectStatusV2({ workspaceRoot }) {
|
|
2159
2512
|
const loaded = loadProject(workspaceRoot);
|
|
2160
2513
|
return {
|
|
2161
2514
|
...projectStatus(loaded.definition, loaded.events, loaded.root),
|
|
@@ -2163,7 +2516,7 @@ export function readTeamProjectStatusV1({ workspaceRoot }) {
|
|
|
2163
2516
|
};
|
|
2164
2517
|
}
|
|
2165
2518
|
|
|
2166
|
-
export function
|
|
2519
|
+
export function buildTeamProjectTakeoverV2({ workspaceRoot, agentId }) {
|
|
2167
2520
|
safeIdentifier(agentId, "agentId");
|
|
2168
2521
|
const loaded = loadProject(workspaceRoot);
|
|
2169
2522
|
const status = projectStatus(loaded.definition, loaded.events, loaded.root);
|
|
@@ -2180,6 +2533,29 @@ export function buildTeamProjectTakeoverV1({ workspaceRoot, agentId }) {
|
|
|
2180
2533
|
row.pendingHandoff?.toAgentId === agentId
|
|
2181
2534
|
))?.pendingHandoff ?? null;
|
|
2182
2535
|
const pendingHandoff = pendingForCurrent ?? pendingForAgent;
|
|
2536
|
+
const staleHandoffs = staleHandoffsForTakeover({
|
|
2537
|
+
loaded,
|
|
2538
|
+
status,
|
|
2539
|
+
agentId,
|
|
2540
|
+
responsibilities,
|
|
2541
|
+
});
|
|
2542
|
+
const latestStaleHandoff = [...staleHandoffs]
|
|
2543
|
+
.sort(compareEventFacts)
|
|
2544
|
+
.at(-1) ?? null;
|
|
2545
|
+
const focusResponsibility = current
|
|
2546
|
+
?? (pendingHandoff
|
|
2547
|
+
? status.workItems.find(row => row.workItemId === pendingHandoff.workItemId) ?? null
|
|
2548
|
+
: null)
|
|
2549
|
+
?? (latestStaleHandoff
|
|
2550
|
+
? status.workItems.find(row => row.workItemId === latestStaleHandoff.workItemId) ?? null
|
|
2551
|
+
: null);
|
|
2552
|
+
const focusedDeliveries = focusResponsibility
|
|
2553
|
+
? status.sourceDeliveries.filter(row => (
|
|
2554
|
+
row.workItemId === focusResponsibility.workItemId
|
|
2555
|
+
))
|
|
2556
|
+
: [];
|
|
2557
|
+
const currentSourceDelivery = focusedDeliveries.at(-1) ?? null;
|
|
2558
|
+
const deliveryDelta = sourceDeliveryDelta(focusedDeliveries);
|
|
2183
2559
|
const checkpoints = loaded.events
|
|
2184
2560
|
.filter(row => row.event.type === "checkpoint_recorded"
|
|
2185
2561
|
&& responsibilities.some(item => item.workItemId === row.event.workItemId))
|
|
@@ -2189,6 +2565,9 @@ export function buildTeamProjectTakeoverV1({ workspaceRoot, agentId }) {
|
|
|
2189
2565
|
...(lastCheckpoint?.evidenceRefs ?? []),
|
|
2190
2566
|
...(current?.evidenceRefs ?? []),
|
|
2191
2567
|
...(pendingHandoff?.evidenceRefs ?? []),
|
|
2568
|
+
...(currentSourceDelivery ? [currentSourceDelivery.truthRef] : []),
|
|
2569
|
+
...(deliveryDelta?.previousDeliveryId ? [focusedDeliveries.at(-2).truthRef] : []),
|
|
2570
|
+
...staleHandoffs.map(row => row.truthRef),
|
|
2192
2571
|
])].sort();
|
|
2193
2572
|
const unresolvedDecisionsById = new Map();
|
|
2194
2573
|
for (const decision of status.openDecisions) {
|
|
@@ -2230,7 +2609,7 @@ export function buildTeamProjectTakeoverV1({ workspaceRoot, agentId }) {
|
|
|
2230
2609
|
: current ? null : status.readyQueue[0] ? `Take ${status.readyQueue[0]}.` : null;
|
|
2231
2610
|
}
|
|
2232
2611
|
return {
|
|
2233
|
-
schemaVersion: "
|
|
2612
|
+
schemaVersion: "OwlCodaRunKitTeamProjectTakeoverV2",
|
|
2234
2613
|
status: "team_project_takeover",
|
|
2235
2614
|
projectId: status.projectId,
|
|
2236
2615
|
objective: status.objective,
|
|
@@ -2243,6 +2622,9 @@ export function buildTeamProjectTakeoverV1({ workspaceRoot, agentId }) {
|
|
|
2243
2622
|
currentResponsibility: current,
|
|
2244
2623
|
allResponsibilities: responsibilities,
|
|
2245
2624
|
pendingHandoff,
|
|
2625
|
+
staleHandoffs,
|
|
2626
|
+
currentSourceDelivery,
|
|
2627
|
+
sourceDeliveryDelta: deliveryDelta,
|
|
2246
2628
|
lastAcceptedCheckpoint: lastCheckpoint
|
|
2247
2629
|
? {
|
|
2248
2630
|
workItemId: lastCheckpoint.workItemId,
|
|
@@ -2265,6 +2647,27 @@ export function buildTeamProjectTakeoverV1({ workspaceRoot, agentId }) {
|
|
|
2265
2647
|
};
|
|
2266
2648
|
}
|
|
2267
2649
|
|
|
2650
|
+
export function buildTeamProjectTakeoverV1(input) {
|
|
2651
|
+
const {
|
|
2652
|
+
staleHandoffs: _staleHandoffs,
|
|
2653
|
+
currentSourceDelivery: _currentSourceDelivery,
|
|
2654
|
+
sourceDeliveryDelta: _sourceDeliveryDelta,
|
|
2655
|
+
...legacy
|
|
2656
|
+
} = buildTeamProjectTakeoverV2(input);
|
|
2657
|
+
const v2OnlyEvidenceRefs = new Set([
|
|
2658
|
+
..._staleHandoffs.map(row => row.truthRef),
|
|
2659
|
+
...readTeamProjectStatusV2({ workspaceRoot: input.workspaceRoot })
|
|
2660
|
+
.sourceDeliveries.map(row => row.truthRef),
|
|
2661
|
+
]);
|
|
2662
|
+
return {
|
|
2663
|
+
...legacy,
|
|
2664
|
+
schemaVersion: "OwlCodaRunKitTeamProjectTakeoverV1",
|
|
2665
|
+
currentResponsibility: legacyWorkItemProjection(legacy.currentResponsibility),
|
|
2666
|
+
allResponsibilities: legacy.allResponsibilities.map(legacyWorkItemProjection),
|
|
2667
|
+
evidenceRefs: legacy.evidenceRefs.filter(ref => !v2OnlyEvidenceRefs.has(ref)),
|
|
2668
|
+
};
|
|
2669
|
+
}
|
|
2670
|
+
|
|
2268
2671
|
export function formatTeamProjectStatusHumanV1(status) {
|
|
2269
2672
|
const nextAction = status.nextAction === null ? "none" : singleLine(status.nextAction);
|
|
2270
2673
|
return [
|
|
@@ -2285,6 +2688,65 @@ export function formatTeamProjectStatusHumanV1(status) {
|
|
|
2285
2688
|
].join("\n");
|
|
2286
2689
|
}
|
|
2287
2690
|
|
|
2691
|
+
export function formatTeamProjectStatusHumanV2(status) {
|
|
2692
|
+
const nextAction = status.nextAction === null ? "none" : singleLine(status.nextAction);
|
|
2693
|
+
const latestSourceDelivery = status.sourceDeliveries.at(-1) ?? null;
|
|
2694
|
+
return [
|
|
2695
|
+
status.headline,
|
|
2696
|
+
`Project: ${status.projectId} — ${singleLine(status.objective)}`,
|
|
2697
|
+
`State: ${status.overall}`,
|
|
2698
|
+
`Work: ${status.counts.completed}/${status.counts.total} completed; ${status.counts.active} active; ${status.counts.waiting_dependency + status.counts.waiting_decision} waiting`,
|
|
2699
|
+
`Agents: ${status.agents.map(agent => agent.agentId).join(", ") || "none assigned"}`,
|
|
2700
|
+
`Open decisions: ${status.openDecisions.map(row => row.decisionId).join(", ") || "none"}`,
|
|
2701
|
+
`Deferred verification: ${status.openDeferredVerificationIds.length} open${
|
|
2702
|
+
status.openDeferredVerificationIds.length > 0
|
|
2703
|
+
? ` (${status.openDeferredVerificationIds.join(", ")})`
|
|
2704
|
+
: ""
|
|
2705
|
+
}`,
|
|
2706
|
+
`Ready queue: ${status.readyQueue.join(", ") || "none"}`,
|
|
2707
|
+
`Source deliveries: ${status.sourceDeliveries.length}${latestSourceDelivery
|
|
2708
|
+
? `; latest ${latestSourceDelivery.deliveryId} (${latestSourceDelivery.overlayStatus}, ${latestSourceDelivery.deliveryCandidateFingerprint})`
|
|
2709
|
+
: ""}`,
|
|
2710
|
+
`Next: ${nextAction}${status.nextActorId ? ` (owner ${status.nextActorId})` : ""}`,
|
|
2711
|
+
"",
|
|
2712
|
+
].join("\n");
|
|
2713
|
+
}
|
|
2714
|
+
|
|
2715
|
+
export function formatTeamProjectTakeoverHumanV2(takeover) {
|
|
2716
|
+
const responsibility = takeover.currentResponsibility;
|
|
2717
|
+
const agentNextAction = takeover.nextAction === null
|
|
2718
|
+
? "none"
|
|
2719
|
+
: singleLine(takeover.nextAction);
|
|
2720
|
+
const projectNextAction = takeover.projectNextAction === null
|
|
2721
|
+
? "none"
|
|
2722
|
+
: singleLine(takeover.projectNextAction);
|
|
2723
|
+
return [
|
|
2724
|
+
takeover.projectHeadline,
|
|
2725
|
+
`Project: ${takeover.projectId} — ${singleLine(takeover.objective)}`,
|
|
2726
|
+
`Agent: ${takeover.agentId}`,
|
|
2727
|
+
`Responsibility: ${responsibility?.workItemId ?? "none"}`,
|
|
2728
|
+
`Open decisions: ${takeover.unresolvedDecisions.map(row => row.decisionId).join(", ") || "none"}`,
|
|
2729
|
+
`Deferred verification: ${takeover.deferredVerifications
|
|
2730
|
+
.map(row => row.verificationId).join(", ") || "none"}`,
|
|
2731
|
+
`Handoffs: pending ${takeover.pendingHandoff
|
|
2732
|
+
? `${takeover.pendingHandoff.fromAgentId} -> ${takeover.pendingHandoff.toAgentId} for ${takeover.pendingHandoff.workItemId}`
|
|
2733
|
+
: "none"}; stale ${takeover.staleHandoffs.length > 0
|
|
2734
|
+
? takeover.staleHandoffs.map(row => `${row.eventId} (${row.reason})`).join(", ")
|
|
2735
|
+
: "none"}`,
|
|
2736
|
+
`Candidate: ${takeover.currentSourceDelivery
|
|
2737
|
+
? `${takeover.currentSourceDelivery.deliveryId} (${takeover.currentSourceDelivery.overlayStatus}, ${takeover.currentSourceDelivery.deliveryCandidateFingerprint})`
|
|
2738
|
+
: "none"}; delta ${takeover.sourceDeliveryDelta === null
|
|
2739
|
+
? "none"
|
|
2740
|
+
: takeover.sourceDeliveryDelta.previousDeliveryId === null
|
|
2741
|
+
? `first delivery; added ${takeover.sourceDeliveryDelta.addedPaths.length}`
|
|
2742
|
+
: `vs ${takeover.sourceDeliveryDelta.previousDeliveryId}; added ${takeover.sourceDeliveryDelta.addedPaths.length}, changed ${takeover.sourceDeliveryDelta.changedPaths.length}, removed ${takeover.sourceDeliveryDelta.removedPaths.length}, unchanged ${takeover.sourceDeliveryDelta.unchangedPaths.length}`}`,
|
|
2743
|
+
`Agent next: ${agentNextAction}`,
|
|
2744
|
+
`Project next: ${projectNextAction}${takeover.projectNextActorId ? ` (owner ${takeover.projectNextActorId})` : ""}`,
|
|
2745
|
+
`State: ${takeover.overall}`,
|
|
2746
|
+
"",
|
|
2747
|
+
].join("\n");
|
|
2748
|
+
}
|
|
2749
|
+
|
|
2288
2750
|
export function formatTeamProjectTakeoverHumanV1(takeover) {
|
|
2289
2751
|
const responsibility = takeover.currentResponsibility;
|
|
2290
2752
|
const agentNextAction = takeover.nextAction === null
|