@scotthuang/agent-knock-knock 0.12.37 → 0.13.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +46 -0
- package/README.md +3 -0
- package/dist/src/agent-session-provider.d.ts +5 -0
- package/dist/src/agent-session-provider.js +10 -1
- package/dist/src/agent-session-provider.js.map +1 -1
- package/dist/src/callback-outbox-service.d.ts +2 -0
- package/dist/src/callback-outbox-service.js +1 -1
- package/dist/src/callback-outbox-service.js.map +1 -1
- package/dist/src/callback-transport.d.ts +7 -0
- package/dist/src/callback-transport.js +1 -1
- package/dist/src/callback-transport.js.map +1 -1
- package/dist/src/claude-lifecycle-compatibility.js +49 -2
- package/dist/src/claude-lifecycle-compatibility.js.map +1 -1
- package/dist/src/claude-terminal-agent-adapter.d.ts +1 -1
- package/dist/src/claude-terminal-agent-adapter.js +2 -2
- package/dist/src/claude-terminal-agent-adapter.js.map +1 -1
- package/dist/src/cli-core.js +35 -32
- package/dist/src/cli-core.js.map +1 -1
- package/dist/src/codex-store-adapter.d.ts +1 -1
- package/dist/src/codex-store-adapter.js +35 -8
- package/dist/src/codex-store-adapter.js.map +1 -1
- package/dist/src/deferred-foreground-authority-cli-adapter.d.ts +20 -0
- package/dist/src/deferred-foreground-authority-cli-adapter.js +216 -8
- package/dist/src/deferred-foreground-authority-cli-adapter.js.map +1 -1
- package/dist/src/host-adapter.d.ts +1 -1
- package/dist/src/host-adapter.js +1 -1
- package/dist/src/host-bridge-tools.js +2 -2
- package/dist/src/managed-turn-recovery-service.d.ts +8 -1
- package/dist/src/managed-turn-recovery-service.js +55 -13
- package/dist/src/managed-turn-recovery-service.js.map +1 -1
- package/dist/src/mutation-transaction.d.ts +1 -0
- package/dist/src/mutation-transaction.js +19 -15
- package/dist/src/mutation-transaction.js.map +1 -1
- package/dist/src/native-thread-lifecycle-cli-adapter.d.ts +39 -3
- package/dist/src/native-thread-lifecycle-cli-adapter.js +307 -7
- package/dist/src/native-thread-lifecycle-cli-adapter.js.map +1 -1
- package/dist/src/openclaw-plugin-callback-adapter.js +17 -7
- package/dist/src/openclaw-plugin-callback-adapter.js.map +1 -1
- package/dist/src/openclaw-plugin-command-adapter.js +297 -83
- package/dist/src/openclaw-plugin-command-adapter.js.map +1 -1
- package/dist/src/openclaw-plugin-schemas.d.ts +57 -0
- package/dist/src/openclaw-plugin-schemas.js +60 -2
- package/dist/src/openclaw-plugin-schemas.js.map +1 -1
- package/dist/src/openclaw-private-authority-offers.d.ts +17 -0
- package/dist/src/openclaw-private-authority-offers.js +75 -3
- package/dist/src/openclaw-private-authority-offers.js.map +1 -1
- package/dist/src/store.d.ts +1 -1
- package/dist/src/store.js +6 -4
- package/dist/src/store.js.map +1 -1
- package/dist/src/terminal-acceptance-application-service.js +2 -2
- package/dist/src/terminal-acceptance-application-service.js.map +1 -1
- package/dist/src/terminal-acceptance-cli-adapter.d.ts +11 -1
- package/dist/src/terminal-acceptance-cli-adapter.js +290 -16
- package/dist/src/terminal-acceptance-cli-adapter.js.map +1 -1
- package/dist/src/terminal-action-projection.d.ts +1 -1
- package/dist/src/terminal-action-projection.js +6 -0
- package/dist/src/terminal-action-projection.js.map +1 -1
- package/dist/src/terminal-agent-adapter.d.ts +9 -0
- package/dist/src/terminal-agent-adapter.js.map +1 -1
- package/dist/src/terminal-agent-bridge.d.ts +52 -8
- package/dist/src/terminal-agent-bridge.js +131 -94
- package/dist/src/terminal-agent-bridge.js.map +1 -1
- package/dist/src/terminal-authority-policy.js +3 -0
- package/dist/src/terminal-authority-policy.js.map +1 -1
- package/dist/src/terminal-binding-authority.d.ts +2 -0
- package/dist/src/terminal-binding-authority.js +8 -0
- package/dist/src/terminal-binding-authority.js.map +1 -1
- package/dist/src/terminal-command-cli-adapter.d.ts +17 -1
- package/dist/src/terminal-command-cli-adapter.js +784 -286
- package/dist/src/terminal-command-cli-adapter.js.map +1 -1
- package/dist/src/terminal-delegate-cli-adapter.js +21 -7
- package/dist/src/terminal-delegate-cli-adapter.js.map +1 -1
- package/dist/src/terminal-dispatch-application.d.ts +3 -1
- package/dist/src/terminal-dispatch-application.js +13 -5
- package/dist/src/terminal-dispatch-application.js.map +1 -1
- package/dist/src/terminal-dispatch-composition.d.ts +43 -0
- package/dist/src/terminal-dispatch-execution.d.ts +2 -2
- package/dist/src/terminal-dispatch-execution.js +53 -3
- package/dist/src/terminal-dispatch-execution.js.map +1 -1
- package/dist/src/terminal-dispatch-policy.d.ts +20 -0
- package/dist/src/terminal-dispatch-policy.js +33 -1
- package/dist/src/terminal-dispatch-policy.js.map +1 -1
- package/dist/src/terminal-dispatch-presenter.d.ts +45 -0
- package/dist/src/terminal-dispatch-presenter.js +220 -18
- package/dist/src/terminal-dispatch-presenter.js.map +1 -1
- package/dist/src/terminal-dispatch-receipt.d.ts +5 -1
- package/dist/src/terminal-dispatch-receipt.js +26 -3
- package/dist/src/terminal-dispatch-receipt.js.map +1 -1
- package/dist/src/terminal-dispatch-recovery-cli-adapter.js +63 -1
- package/dist/src/terminal-dispatch-recovery-cli-adapter.js.map +1 -1
- package/dist/src/terminal-dispatch-recovery-service.d.ts +1 -0
- package/dist/src/terminal-dispatch-recovery-service.js +7 -0
- package/dist/src/terminal-dispatch-recovery-service.js.map +1 -1
- package/dist/src/terminal-identity-authority-cli-adapter.js +103 -4
- package/dist/src/terminal-identity-authority-cli-adapter.js.map +1 -1
- package/dist/src/terminal-interaction-authority.d.ts +48 -1
- package/dist/src/terminal-interaction-authority.js +30 -0
- package/dist/src/terminal-interaction-authority.js.map +1 -1
- package/dist/src/terminal-interaction-cli-adapter.js +9 -0
- package/dist/src/terminal-interaction-cli-adapter.js.map +1 -1
- package/dist/src/terminal-interaction-core.d.ts +104 -0
- package/dist/src/terminal-interaction-core.js +407 -0
- package/dist/src/terminal-interaction-core.js.map +1 -0
- package/dist/src/terminal-interaction-protocol.d.ts +89 -0
- package/dist/src/terminal-interaction-protocol.js +199 -0
- package/dist/src/terminal-interaction-protocol.js.map +1 -1
- package/dist/src/terminal-list-cli-adapter.d.ts +5 -0
- package/dist/src/terminal-list-cli-adapter.js +190 -40
- package/dist/src/terminal-list-cli-adapter.js.map +1 -1
- package/dist/src/terminal-list-renderer.js +60 -7
- package/dist/src/terminal-list-renderer.js.map +1 -1
- package/dist/src/terminal-maintenance-cli-adapter.js +14 -3
- package/dist/src/terminal-maintenance-cli-adapter.js.map +1 -1
- package/dist/src/terminal-monitor-application-service.d.ts +1 -0
- package/dist/src/terminal-monitor-application-service.js +72 -10
- package/dist/src/terminal-monitor-application-service.js.map +1 -1
- package/dist/src/terminal-monitor-cli-adapter.d.ts +3 -0
- package/dist/src/terminal-monitor-cli-adapter.js +24 -8
- package/dist/src/terminal-monitor-cli-adapter.js.map +1 -1
- package/dist/src/terminal-monitor-state-cli-adapter.js +15 -2
- package/dist/src/terminal-monitor-state-cli-adapter.js.map +1 -1
- package/dist/src/terminal-monitor-state-reconciliation-service.js +15 -1
- package/dist/src/terminal-monitor-state-reconciliation-service.js.map +1 -1
- package/dist/src/terminal-mutation-cli-runtime.d.ts +11 -1
- package/dist/src/terminal-mutation-cli-runtime.js +9 -4
- package/dist/src/terminal-mutation-cli-runtime.js.map +1 -1
- package/dist/src/terminal-questionnaire-adapter.d.ts +3 -1
- package/dist/src/terminal-questionnaire-adapter.js +34 -21
- package/dist/src/terminal-questionnaire-adapter.js.map +1 -1
- package/dist/src/terminal-status-cli-adapter.d.ts +1 -7
- package/dist/src/terminal-status-cli-adapter.js +29 -3
- package/dist/src/terminal-status-cli-adapter.js.map +1 -1
- package/dist/src/terminal-status-facts.d.ts +17 -1
- package/dist/src/terminal-status-facts.js +59 -0
- package/dist/src/terminal-status-facts.js.map +1 -1
- package/dist/src/terminal-submission-acceptance.d.ts +37 -2
- package/dist/src/terminal-submission-acceptance.js +633 -30
- package/dist/src/terminal-submission-acceptance.js.map +1 -1
- package/dist/src/terminal-submission-facts.d.ts +5 -0
- package/dist/src/terminal-submission-facts.js.map +1 -1
- package/dist/src/terminal-watch-callback-cli-adapter.d.ts +2 -1
- package/dist/src/terminal-watch-callback-cli-adapter.js +2 -1
- package/dist/src/terminal-watch-callback-cli-adapter.js.map +1 -1
- package/dist/src/terminal-watch-cli-adapter.d.ts +6 -2
- package/dist/src/terminal-watch-cli-adapter.js +1051 -15
- package/dist/src/terminal-watch-cli-adapter.js.map +1 -1
- package/dist/src/terminal-watch-service.d.ts +15 -1
- package/dist/src/terminal-watch-service.js +169 -13
- package/dist/src/terminal-watch-service.js.map +1 -1
- package/dist/src/terminal-watch-store.d.ts +72 -3
- package/dist/src/terminal-watch-store.js +541 -35
- package/dist/src/terminal-watch-store.js.map +1 -1
- package/docs/host-bridge-profiles.md +2 -2
- package/docs/quickstart-herdr.md +5 -3
- package/docs/quickstart-tmux.md +8 -6
- package/openclaw.plugin.json +8 -0
- package/package.json +1 -1
- package/templates/openclaw-skills/agent-knock-knock/SKILL.md +16 -12
|
@@ -8,6 +8,14 @@ const CODEX_ACCEPTANCE_MAX_BYTES = 16 * 1024 * 1024;
|
|
|
8
8
|
const CODEX_COMPLETION_MAX_BYTES = 256 * 1024 * 1024;
|
|
9
9
|
const CODEX_COMPLETION_MAX_TEXT_LENGTH = 4000;
|
|
10
10
|
const CODEX_ACTIVE_TASK_SCAN_CHUNK_BYTES = 64 * 1024;
|
|
11
|
+
const CODEX_QUESTIONNAIRE_TAIL_MAX_BYTES = 1024 * 1024;
|
|
12
|
+
const CODEX_QUESTIONNAIRE_MAX_TEXT_LENGTH = 16 * 1024;
|
|
13
|
+
const CODEX_QUESTIONNAIRE_MAX_OPTIONS = 8;
|
|
14
|
+
const CODEX_QUESTIONNAIRE_MAX_QUESTIONS = 3;
|
|
15
|
+
const CODEX_QUESTIONNAIRE_OTHER_LABEL = "None of the above";
|
|
16
|
+
const CODEX_QUESTIONNAIRE_OTHER_DESCRIPTION = "Optionally, add details in notes (tab).";
|
|
17
|
+
const CODEX_QUESTIONNAIRE_CUSTOM_LABEL = "Type something.";
|
|
18
|
+
const CODEX_QUESTIONNAIRE_CUSTOM_DESCRIPTION = "Enter a free-form answer through Codex Notes.";
|
|
11
19
|
const CODEX_ACTIVE_TASK_MAX_RECORD_BYTES = 16 * 1024 * 1024;
|
|
12
20
|
const CODEX_ROLLOUT_HEADER_MAX_BYTES = 1024 * 1024;
|
|
13
21
|
const CODEX_VERSION_PATTERN = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/u;
|
|
@@ -151,6 +159,9 @@ export function observeCodexHumanStartedActiveTask(options) {
|
|
|
151
159
|
safeResumeOffsetBytes
|
|
152
160
|
};
|
|
153
161
|
}
|
|
162
|
+
if (completion.status === "aborted") {
|
|
163
|
+
throw new Error("Codex active-task abort evidence escaped its exact lifecycle fence");
|
|
164
|
+
}
|
|
154
165
|
return {
|
|
155
166
|
status: "completed",
|
|
156
167
|
completion: {
|
|
@@ -763,7 +774,7 @@ export function captureCodexCandidateSetRolloutAcceptanceAnchor({ inventory: inv
|
|
|
763
774
|
anchor_fingerprint: fingerprint(base)
|
|
764
775
|
};
|
|
765
776
|
}
|
|
766
|
-
export function detectCodexCandidateSetRolloutAcceptance({ anchor: anchorValue, currentInventory: inventoryValue, requestHash: requestHashValue }) {
|
|
777
|
+
export function detectCodexCandidateSetRolloutAcceptance({ anchor: anchorValue, currentInventory: inventoryValue, requestHash: requestHashValue, recoveryCandidate: recoveryCandidateValue }) {
|
|
767
778
|
const anchor = validateCodexRolloutAcceptanceAnchor(anchorValue);
|
|
768
779
|
if (anchor.version !== 3) {
|
|
769
780
|
throw new Error("Codex candidate-set acceptance requires a version 3 anchor");
|
|
@@ -782,15 +793,41 @@ export function detectCodexCandidateSetRolloutAcceptance({ anchor: anchorValue,
|
|
|
782
793
|
exact_matches: 0
|
|
783
794
|
};
|
|
784
795
|
}
|
|
796
|
+
let recoveryCandidate;
|
|
797
|
+
if (recoveryCandidateValue) {
|
|
798
|
+
try {
|
|
799
|
+
recoveryCandidate = validateCodexRecoveryCandidateForAcceptance(recoveryCandidateValue, anchor);
|
|
800
|
+
}
|
|
801
|
+
catch (error) {
|
|
802
|
+
return {
|
|
803
|
+
status: "uncertain",
|
|
804
|
+
code: "candidate_inventory_changed",
|
|
805
|
+
reason: error instanceof Error ? error.message : String(error),
|
|
806
|
+
inspected_candidates: 0,
|
|
807
|
+
exact_matches: 0
|
|
808
|
+
};
|
|
809
|
+
}
|
|
810
|
+
}
|
|
785
811
|
const anchoredByThread = new Map(anchor.candidate_rollouts.map((candidate) => [
|
|
786
812
|
candidate.native_thread_id,
|
|
787
813
|
candidate
|
|
788
814
|
]));
|
|
815
|
+
const anchoredByFile = new Map(anchor.candidate_rollouts.map((candidate) => [
|
|
816
|
+
codexRolloutInodeKey(candidate.rollout),
|
|
817
|
+
candidate
|
|
818
|
+
]));
|
|
789
819
|
const currentByThread = new Map(inventory.roots.map((identity) => [identity.sessionId, identity]));
|
|
790
|
-
|
|
820
|
+
const currentByFile = new Map(inventory.roots.map((identity) => [
|
|
821
|
+
codexRolloutInodeKey(identity.rollout),
|
|
822
|
+
identity
|
|
823
|
+
]));
|
|
824
|
+
for (const identity of [
|
|
825
|
+
...inventory.roots,
|
|
826
|
+
...(recoveryCandidate ? [recoveryCandidate] : [])
|
|
827
|
+
]) {
|
|
791
828
|
const anchored = anchoredByThread.get(identity.sessionId);
|
|
792
829
|
if (anchored &&
|
|
793
|
-
!
|
|
830
|
+
!sameRolloutFileIdentity(anchored.rollout, identity.rollout)) {
|
|
794
831
|
return {
|
|
795
832
|
status: "uncertain",
|
|
796
833
|
code: "candidate_inventory_changed",
|
|
@@ -799,26 +836,72 @@ export function detectCodexCandidateSetRolloutAcceptance({ anchor: anchorValue,
|
|
|
799
836
|
exact_matches: 0
|
|
800
837
|
};
|
|
801
838
|
}
|
|
839
|
+
const anchoredFile = anchoredByFile.get(codexRolloutInodeKey(identity.rollout));
|
|
840
|
+
if (anchoredFile &&
|
|
841
|
+
anchoredFile.native_thread_id !== identity.sessionId) {
|
|
842
|
+
return {
|
|
843
|
+
status: "uncertain",
|
|
844
|
+
code: "candidate_inventory_changed",
|
|
845
|
+
reason: `Codex candidate rollout changed native thread identity from ` +
|
|
846
|
+
`${anchoredFile.native_thread_id} to ${identity.sessionId} after capture`,
|
|
847
|
+
inspected_candidates: 0,
|
|
848
|
+
exact_matches: 0
|
|
849
|
+
};
|
|
850
|
+
}
|
|
802
851
|
}
|
|
803
|
-
|
|
804
|
-
|
|
852
|
+
if (recoveryCandidate) {
|
|
853
|
+
const currentThread = currentByThread.get(recoveryCandidate.sessionId);
|
|
854
|
+
const currentFile = currentByFile.get(codexRolloutInodeKey(recoveryCandidate.rollout));
|
|
855
|
+
if ((currentThread && !sameRolloutFileIdentity(currentThread.rollout, recoveryCandidate.rollout)) ||
|
|
856
|
+
(currentFile && currentFile.sessionId !== recoveryCandidate.sessionId)) {
|
|
805
857
|
return {
|
|
806
858
|
status: "uncertain",
|
|
807
859
|
code: "candidate_inventory_changed",
|
|
808
|
-
reason:
|
|
860
|
+
reason: "persisted Codex recovery candidate conflicts with the current root inventory",
|
|
809
861
|
inspected_candidates: 0,
|
|
810
862
|
exact_matches: 0
|
|
811
863
|
};
|
|
812
864
|
}
|
|
813
865
|
}
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
866
|
+
// A Codex thread may close (or reopen) its process FD while the acceptance
|
|
867
|
+
// poll is running. The captured path/device/inode/offset remains the durable
|
|
868
|
+
// read authority in that case, so inspect the union rather than treating a
|
|
869
|
+
// missing lsof row as evidence that the candidate changed identity.
|
|
870
|
+
const candidates = [
|
|
871
|
+
...anchor.candidate_rollouts.map((anchored) => ({
|
|
872
|
+
identity: currentByThread.get(anchored.native_thread_id) ??
|
|
873
|
+
(recoveryCandidate?.sessionId === anchored.native_thread_id
|
|
874
|
+
? recoveryCandidate
|
|
875
|
+
: {
|
|
876
|
+
sessionId: anchored.native_thread_id,
|
|
877
|
+
processUuid: anchor.process_uuid,
|
|
878
|
+
processBirth: anchor.process_birth,
|
|
879
|
+
rollout: anchored.rollout,
|
|
880
|
+
evidence: "codex_open_root_rollout"
|
|
881
|
+
}),
|
|
882
|
+
offsetBytes: anchored.offset_bytes,
|
|
883
|
+
requireFreshHeader: false,
|
|
884
|
+
currentlyOpen: currentByThread.has(anchored.native_thread_id)
|
|
885
|
+
})),
|
|
886
|
+
...inventory.roots
|
|
887
|
+
.filter((identity) => !anchoredByThread.has(identity.sessionId))
|
|
888
|
+
.map((identity) => ({
|
|
817
889
|
identity,
|
|
818
|
-
offsetBytes:
|
|
819
|
-
requireFreshHeader:
|
|
820
|
-
|
|
821
|
-
|
|
890
|
+
offsetBytes: 0,
|
|
891
|
+
requireFreshHeader: true,
|
|
892
|
+
currentlyOpen: true
|
|
893
|
+
})),
|
|
894
|
+
...(recoveryCandidate &&
|
|
895
|
+
!anchoredByThread.has(recoveryCandidate.sessionId) &&
|
|
896
|
+
!currentByThread.has(recoveryCandidate.sessionId)
|
|
897
|
+
? [{
|
|
898
|
+
identity: recoveryCandidate,
|
|
899
|
+
offsetBytes: 0,
|
|
900
|
+
requireFreshHeader: true,
|
|
901
|
+
currentlyOpen: false
|
|
902
|
+
}]
|
|
903
|
+
: [])
|
|
904
|
+
];
|
|
822
905
|
const matches = [];
|
|
823
906
|
let incompleteCandidates = 0;
|
|
824
907
|
for (const candidate of candidates) {
|
|
@@ -846,6 +929,16 @@ export function detectCodexCandidateSetRolloutAcceptance({ anchor: anchorValue,
|
|
|
846
929
|
};
|
|
847
930
|
}
|
|
848
931
|
if (scan.status === "incomplete") {
|
|
932
|
+
if (!candidate.currentlyOpen) {
|
|
933
|
+
return {
|
|
934
|
+
status: "uncertain",
|
|
935
|
+
code: "candidate_scan_invalid",
|
|
936
|
+
reason: `Codex candidate ${candidate.identity.sessionId} closed with an ` +
|
|
937
|
+
"incomplete rollout record after capture",
|
|
938
|
+
inspected_candidates: candidates.indexOf(candidate) + 1,
|
|
939
|
+
exact_matches: matches.length
|
|
940
|
+
};
|
|
941
|
+
}
|
|
849
942
|
incompleteCandidates += 1;
|
|
850
943
|
}
|
|
851
944
|
else if (scan.status === "accepted") {
|
|
@@ -880,6 +973,94 @@ export function detectCodexCandidateSetRolloutAcceptance({ anchor: anchorValue,
|
|
|
880
973
|
evidence: matches[0].evidence
|
|
881
974
|
};
|
|
882
975
|
}
|
|
976
|
+
/**
|
|
977
|
+
* Attributes a rendered questionnaire to one exact accepted Codex turn while
|
|
978
|
+
* the process has multiple open root rollouts. No prompt or tool arguments are
|
|
979
|
+
* returned: callers receive only a bounded fingerprint or a typed failure.
|
|
980
|
+
*/
|
|
981
|
+
export function detectCodexBoundQuestionnaireAttribution(options) {
|
|
982
|
+
let inventory;
|
|
983
|
+
let acceptedThreadId;
|
|
984
|
+
let acceptedRollout;
|
|
985
|
+
let acceptanceId;
|
|
986
|
+
try {
|
|
987
|
+
inventory = validateCodexOpenRootInventoryForAcceptance(options.currentInventory);
|
|
988
|
+
acceptedThreadId = exactNativeThreadId(options.acceptedIdentity.sessionId);
|
|
989
|
+
acceptanceId = exactNativeThreadId(options.acceptanceId);
|
|
990
|
+
if (!options.acceptedIdentity.rollout) {
|
|
991
|
+
throw new Error("accepted Codex rollout identity is unavailable");
|
|
992
|
+
}
|
|
993
|
+
acceptedRollout = normalizedRolloutIdentity(options.acceptedIdentity.rollout);
|
|
994
|
+
if (requiredString(options.acceptedIdentity.processUuid, "accepted Codex process UUID") !== inventory.processUuid ||
|
|
995
|
+
requiredString(options.acceptedIdentity.processBirth, "accepted Codex process birth") !== inventory.processBirth) {
|
|
996
|
+
return { status: "not_matched", code: "accepted_root_missing" };
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
catch {
|
|
1000
|
+
return { status: "unavailable", code: "invalid_inventory" };
|
|
1001
|
+
}
|
|
1002
|
+
let normalizedScreen;
|
|
1003
|
+
try {
|
|
1004
|
+
normalizedScreen = normalizedCodexQuestionnaireScreen(options.screen);
|
|
1005
|
+
}
|
|
1006
|
+
catch {
|
|
1007
|
+
return { status: "unavailable", code: "invalid_screen" };
|
|
1008
|
+
}
|
|
1009
|
+
const acceptedRoots = inventory.roots.filter((root) => root.sessionId.toLowerCase() === acceptedThreadId &&
|
|
1010
|
+
sameRolloutFileIdentity(root.rollout, acceptedRollout));
|
|
1011
|
+
if (acceptedRoots.length !== 1) {
|
|
1012
|
+
return { status: "not_matched", code: "accepted_root_missing" };
|
|
1013
|
+
}
|
|
1014
|
+
let maxBytes;
|
|
1015
|
+
try {
|
|
1016
|
+
maxBytes = positiveByteLimit(options.maxBytesPerRollout, CODEX_QUESTIONNAIRE_TAIL_MAX_BYTES, "Codex questionnaire rollout scan limit");
|
|
1017
|
+
}
|
|
1018
|
+
catch {
|
|
1019
|
+
return { status: "unavailable", code: "rollout_scan_unavailable" };
|
|
1020
|
+
}
|
|
1021
|
+
const pending = [];
|
|
1022
|
+
try {
|
|
1023
|
+
for (const root of inventory.roots) {
|
|
1024
|
+
pending.push(...readPendingCodexRequestUserInput({
|
|
1025
|
+
rollout: root.rollout,
|
|
1026
|
+
nativeThreadId: root.sessionId,
|
|
1027
|
+
maxBytes
|
|
1028
|
+
}));
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
catch {
|
|
1032
|
+
return { status: "unavailable", code: "rollout_scan_unavailable" };
|
|
1033
|
+
}
|
|
1034
|
+
const acceptedCalls = pending.filter((call) => call.nativeThreadId === acceptedThreadId &&
|
|
1035
|
+
call.turnId === acceptanceId &&
|
|
1036
|
+
sameRolloutFileIdentity(call.rollout, acceptedRollout));
|
|
1037
|
+
if (acceptedCalls.length !== 1) {
|
|
1038
|
+
return { status: "not_matched", code: "accepted_call_not_unique" };
|
|
1039
|
+
}
|
|
1040
|
+
const matching = options.screen.exactShape &&
|
|
1041
|
+
normalizedScreen.responseKind !== "confirm" &&
|
|
1042
|
+
normalizedScreen.responseKind !== "multi_select"
|
|
1043
|
+
? pending.filter((call) => codexPendingQuestionnaireMatchesScreen(call, normalizedScreen))
|
|
1044
|
+
: pending;
|
|
1045
|
+
if (matching.length !== 1 || matching[0] !== acceptedCalls[0]) {
|
|
1046
|
+
return { status: "not_matched", code: "screen_signature_not_unique" };
|
|
1047
|
+
}
|
|
1048
|
+
return {
|
|
1049
|
+
status: "matched",
|
|
1050
|
+
evidenceFingerprint: fingerprint({
|
|
1051
|
+
schema: "agent-knock-knock/codex-bound-questionnaire-attribution",
|
|
1052
|
+
version: 1,
|
|
1053
|
+
native_thread_id: acceptedThreadId,
|
|
1054
|
+
turn_id: acceptanceId,
|
|
1055
|
+
rollout: {
|
|
1056
|
+
device: acceptedRollout.device,
|
|
1057
|
+
inode: acceptedRollout.inode,
|
|
1058
|
+
path: acceptedRollout.path
|
|
1059
|
+
},
|
|
1060
|
+
screen: normalizedScreen
|
|
1061
|
+
})
|
|
1062
|
+
};
|
|
1063
|
+
}
|
|
883
1064
|
export function detectCodexRolloutAcceptance(options) {
|
|
884
1065
|
const anchor = validateCodexRolloutAcceptanceAnchor(options.anchor);
|
|
885
1066
|
if (anchor.version === 3) {
|
|
@@ -1033,7 +1214,10 @@ export function detectCodexBoundRolloutCompletion(options) {
|
|
|
1033
1214
|
const anchoredRollout = anchor.version === 3
|
|
1034
1215
|
? acceptedCandidate?.rollout
|
|
1035
1216
|
: anchor.rollout;
|
|
1036
|
-
|
|
1217
|
+
const anchoredRolloutMatches = !anchoredRollout || (anchor.version === 3
|
|
1218
|
+
? sameRolloutFileIdentity(anchoredRollout, rollout)
|
|
1219
|
+
: sameRolloutIdentity(anchoredRollout, rollout));
|
|
1220
|
+
if (!anchoredRolloutMatches) {
|
|
1037
1221
|
return codexCompletionFailure("rollout_identity_mismatch", new Error("Codex rollout identity changed after terminal acceptance"), rolloutDiagnostics);
|
|
1038
1222
|
}
|
|
1039
1223
|
let opened;
|
|
@@ -1092,7 +1276,8 @@ export function detectCodexBoundRolloutCompletion(options) {
|
|
|
1092
1276
|
const scanDiagnostics = {
|
|
1093
1277
|
...observedDiagnostics,
|
|
1094
1278
|
scanned_records: scan.scannedRecords,
|
|
1095
|
-
observed_task_complete_records: scan.observedTaskCompleteRecords
|
|
1279
|
+
observed_task_complete_records: scan.observedTaskCompleteRecords,
|
|
1280
|
+
observed_turn_aborted_records: scan.observedTurnAbortedRecords
|
|
1096
1281
|
};
|
|
1097
1282
|
if (scan.status === "failure") {
|
|
1098
1283
|
return codexCompletionFailure(scan.code, scan.detail, scanDiagnostics);
|
|
@@ -1100,6 +1285,38 @@ export function detectCodexBoundRolloutCompletion(options) {
|
|
|
1100
1285
|
if (scan.status === "pending") {
|
|
1101
1286
|
return codexCompletionPending("exact_turn_not_complete", scanDiagnostics, "the accepted Codex native turn has no durable task_complete record yet");
|
|
1102
1287
|
}
|
|
1288
|
+
if (scan.status === "aborted") {
|
|
1289
|
+
const reason = truncateCompletionText(redactString(scan.reason ?? "interrupted"));
|
|
1290
|
+
const completion = {
|
|
1291
|
+
source: "durable",
|
|
1292
|
+
outcome: "failure",
|
|
1293
|
+
text: truncateCompletionText(redactString(`Codex task stopped: ${reason}`)),
|
|
1294
|
+
...(scan.timestamp ? { timestamp: scan.timestamp } : {}),
|
|
1295
|
+
id: acceptanceId,
|
|
1296
|
+
confidence: "high",
|
|
1297
|
+
metadata: {
|
|
1298
|
+
match: "bound_rollout_turn_aborted",
|
|
1299
|
+
turn_id: acceptanceId,
|
|
1300
|
+
native_thread_id: expectedNativeThreadId,
|
|
1301
|
+
anchor_fingerprint: anchor.anchor_fingerprint,
|
|
1302
|
+
rollout_identity_fingerprint: rolloutDiagnostics.rollout_identity_fingerprint,
|
|
1303
|
+
abort_reason: reason,
|
|
1304
|
+
scan_start_offset_bytes: scanStartOffset,
|
|
1305
|
+
observed_end_offset_bytes: before.size,
|
|
1306
|
+
scanned_records: scan.scannedRecords,
|
|
1307
|
+
observed_task_complete_records: scan.observedTaskCompleteRecords,
|
|
1308
|
+
observed_turn_aborted_records: scan.observedTurnAbortedRecords
|
|
1309
|
+
}
|
|
1310
|
+
};
|
|
1311
|
+
return {
|
|
1312
|
+
status: "completed",
|
|
1313
|
+
completion,
|
|
1314
|
+
diagnostics: {
|
|
1315
|
+
...scanDiagnostics,
|
|
1316
|
+
code: "abort_found"
|
|
1317
|
+
}
|
|
1318
|
+
};
|
|
1319
|
+
}
|
|
1103
1320
|
const completion = {
|
|
1104
1321
|
source: "durable",
|
|
1105
1322
|
outcome: "success",
|
|
@@ -1116,7 +1333,8 @@ export function detectCodexBoundRolloutCompletion(options) {
|
|
|
1116
1333
|
scan_start_offset_bytes: scanStartOffset,
|
|
1117
1334
|
observed_end_offset_bytes: before.size,
|
|
1118
1335
|
scanned_records: scan.scannedRecords,
|
|
1119
|
-
observed_task_complete_records: scan.observedTaskCompleteRecords
|
|
1336
|
+
observed_task_complete_records: scan.observedTaskCompleteRecords,
|
|
1337
|
+
observed_turn_aborted_records: scan.observedTurnAbortedRecords
|
|
1120
1338
|
}
|
|
1121
1339
|
};
|
|
1122
1340
|
return {
|
|
@@ -1145,7 +1363,11 @@ export function detectCodexBoundRolloutCompletion(options) {
|
|
|
1145
1363
|
function scanExactCodexTaskComplete(text, acceptanceId) {
|
|
1146
1364
|
let scannedRecords = 0;
|
|
1147
1365
|
let observedTaskCompleteRecords = 0;
|
|
1366
|
+
let observedTurnAbortedRecords = 0;
|
|
1367
|
+
let exactTaskStartedIndex;
|
|
1368
|
+
let laterTaskStartedIndex;
|
|
1148
1369
|
const exactMatches = [];
|
|
1370
|
+
const exactAborts = [];
|
|
1149
1371
|
for (const rawLine of text.split("\n")) {
|
|
1150
1372
|
const line = rawLine.endsWith("\r") ? rawLine.slice(0, -1) : rawLine;
|
|
1151
1373
|
if (!line) {
|
|
@@ -1161,10 +1383,37 @@ function scanExactCodexTaskComplete(text, acceptanceId) {
|
|
|
1161
1383
|
code: "invalid_rollout_jsonl",
|
|
1162
1384
|
detail: `Codex bound completion suffix contains invalid JSONL at record ${scannedRecords + 1}`,
|
|
1163
1385
|
scannedRecords,
|
|
1164
|
-
observedTaskCompleteRecords
|
|
1386
|
+
observedTaskCompleteRecords,
|
|
1387
|
+
observedTurnAbortedRecords
|
|
1165
1388
|
};
|
|
1166
1389
|
}
|
|
1390
|
+
const recordIndex = scannedRecords;
|
|
1167
1391
|
scannedRecords += 1;
|
|
1392
|
+
if (isRecord(value) &&
|
|
1393
|
+
value.type === "event_msg" &&
|
|
1394
|
+
isRecord(value.payload) &&
|
|
1395
|
+
value.payload.type === "task_started") {
|
|
1396
|
+
const turnId = optionalString(value.payload.turn_id)?.toLowerCase();
|
|
1397
|
+
if (turnId === acceptanceId) {
|
|
1398
|
+
exactTaskStartedIndex ??= recordIndex;
|
|
1399
|
+
}
|
|
1400
|
+
else if (exactTaskStartedIndex !== undefined &&
|
|
1401
|
+
laterTaskStartedIndex === undefined) {
|
|
1402
|
+
laterTaskStartedIndex = recordIndex;
|
|
1403
|
+
}
|
|
1404
|
+
continue;
|
|
1405
|
+
}
|
|
1406
|
+
if (isRecord(value) &&
|
|
1407
|
+
value.type === "event_msg" &&
|
|
1408
|
+
isRecord(value.payload) &&
|
|
1409
|
+
value.payload.type === "turn_aborted") {
|
|
1410
|
+
observedTurnAbortedRecords += 1;
|
|
1411
|
+
const turnId = optionalString(value.payload.turn_id)?.toLowerCase();
|
|
1412
|
+
if (turnId === acceptanceId) {
|
|
1413
|
+
exactAborts.push({ value, index: recordIndex });
|
|
1414
|
+
}
|
|
1415
|
+
continue;
|
|
1416
|
+
}
|
|
1168
1417
|
if (!isRecord(value) ||
|
|
1169
1418
|
value.type !== "event_msg" ||
|
|
1170
1419
|
!isRecord(value.payload) ||
|
|
@@ -1174,26 +1423,85 @@ function scanExactCodexTaskComplete(text, acceptanceId) {
|
|
|
1174
1423
|
observedTaskCompleteRecords += 1;
|
|
1175
1424
|
const turnId = optionalString(value.payload.turn_id)?.toLowerCase();
|
|
1176
1425
|
if (turnId === acceptanceId) {
|
|
1177
|
-
exactMatches.push(value);
|
|
1426
|
+
exactMatches.push({ value, index: recordIndex });
|
|
1178
1427
|
}
|
|
1179
1428
|
}
|
|
1180
|
-
if (exactMatches.length
|
|
1429
|
+
if (exactMatches.length > 1) {
|
|
1181
1430
|
return {
|
|
1182
|
-
status: "
|
|
1431
|
+
status: "failure",
|
|
1432
|
+
code: "duplicate_exact_completion",
|
|
1433
|
+
detail: "the exact accepted Codex turn has duplicate task_complete records",
|
|
1183
1434
|
scannedRecords,
|
|
1184
|
-
observedTaskCompleteRecords
|
|
1435
|
+
observedTaskCompleteRecords,
|
|
1436
|
+
observedTurnAbortedRecords
|
|
1185
1437
|
};
|
|
1186
1438
|
}
|
|
1187
|
-
if (
|
|
1439
|
+
if (exactAborts.length > 1) {
|
|
1188
1440
|
return {
|
|
1189
1441
|
status: "failure",
|
|
1190
|
-
code: "
|
|
1191
|
-
detail: "the exact accepted Codex turn has duplicate
|
|
1442
|
+
code: "duplicate_exact_abort",
|
|
1443
|
+
detail: "the exact accepted Codex turn has duplicate turn_aborted records",
|
|
1444
|
+
scannedRecords,
|
|
1445
|
+
observedTaskCompleteRecords,
|
|
1446
|
+
observedTurnAbortedRecords
|
|
1447
|
+
};
|
|
1448
|
+
}
|
|
1449
|
+
if (exactMatches.length > 0 && exactAborts.length > 0) {
|
|
1450
|
+
return {
|
|
1451
|
+
status: "failure",
|
|
1452
|
+
code: "conflicting_exact_settlement",
|
|
1453
|
+
detail: "the exact accepted Codex turn has conflicting completion and abort records",
|
|
1454
|
+
scannedRecords,
|
|
1455
|
+
observedTaskCompleteRecords,
|
|
1456
|
+
observedTurnAbortedRecords
|
|
1457
|
+
};
|
|
1458
|
+
}
|
|
1459
|
+
const exactSettlementIndex = exactMatches[0]?.index ?? exactAborts[0]?.index;
|
|
1460
|
+
if (laterTaskStartedIndex !== undefined &&
|
|
1461
|
+
(exactSettlementIndex === undefined ||
|
|
1462
|
+
laterTaskStartedIndex < exactSettlementIndex)) {
|
|
1463
|
+
return {
|
|
1464
|
+
status: "failure",
|
|
1465
|
+
code: "later_turn_started",
|
|
1466
|
+
detail: "a later Codex native turn started before the exact accepted turn settled",
|
|
1467
|
+
scannedRecords,
|
|
1468
|
+
observedTaskCompleteRecords,
|
|
1469
|
+
observedTurnAbortedRecords
|
|
1470
|
+
};
|
|
1471
|
+
}
|
|
1472
|
+
if (exactAborts.length === 1) {
|
|
1473
|
+
const match = exactAborts[0].value;
|
|
1474
|
+
if (match.timestamp !== undefined && !validTimestamp(match.timestamp)) {
|
|
1475
|
+
return {
|
|
1476
|
+
status: "failure",
|
|
1477
|
+
code: "invalid_exact_abort",
|
|
1478
|
+
detail: "the exact Codex turn_aborted record has an invalid timestamp",
|
|
1479
|
+
scannedRecords,
|
|
1480
|
+
observedTaskCompleteRecords,
|
|
1481
|
+
observedTurnAbortedRecords
|
|
1482
|
+
};
|
|
1483
|
+
}
|
|
1484
|
+
const payload = match.payload;
|
|
1485
|
+
return {
|
|
1486
|
+
status: "aborted",
|
|
1487
|
+
reason: optionalString(payload.reason),
|
|
1488
|
+
...(match.timestamp !== undefined
|
|
1489
|
+
? { timestamp: String(match.timestamp) }
|
|
1490
|
+
: {}),
|
|
1192
1491
|
scannedRecords,
|
|
1193
|
-
observedTaskCompleteRecords
|
|
1492
|
+
observedTaskCompleteRecords,
|
|
1493
|
+
observedTurnAbortedRecords
|
|
1194
1494
|
};
|
|
1195
1495
|
}
|
|
1196
|
-
|
|
1496
|
+
if (exactMatches.length === 0) {
|
|
1497
|
+
return {
|
|
1498
|
+
status: "pending",
|
|
1499
|
+
scannedRecords,
|
|
1500
|
+
observedTaskCompleteRecords,
|
|
1501
|
+
observedTurnAbortedRecords
|
|
1502
|
+
};
|
|
1503
|
+
}
|
|
1504
|
+
const match = exactMatches[0].value;
|
|
1197
1505
|
const payload = match.payload;
|
|
1198
1506
|
const textValue = optionalString(payload.last_agent_message);
|
|
1199
1507
|
if (!textValue) {
|
|
@@ -1202,7 +1510,8 @@ function scanExactCodexTaskComplete(text, acceptanceId) {
|
|
|
1202
1510
|
code: "invalid_exact_completion",
|
|
1203
1511
|
detail: "the exact Codex task_complete record has no final agent message",
|
|
1204
1512
|
scannedRecords,
|
|
1205
|
-
observedTaskCompleteRecords
|
|
1513
|
+
observedTaskCompleteRecords,
|
|
1514
|
+
observedTurnAbortedRecords
|
|
1206
1515
|
};
|
|
1207
1516
|
}
|
|
1208
1517
|
if (match.timestamp !== undefined && !validTimestamp(match.timestamp)) {
|
|
@@ -1211,7 +1520,8 @@ function scanExactCodexTaskComplete(text, acceptanceId) {
|
|
|
1211
1520
|
code: "invalid_exact_completion",
|
|
1212
1521
|
detail: "the exact Codex task_complete record has an invalid timestamp",
|
|
1213
1522
|
scannedRecords,
|
|
1214
|
-
observedTaskCompleteRecords
|
|
1523
|
+
observedTaskCompleteRecords,
|
|
1524
|
+
observedTurnAbortedRecords
|
|
1215
1525
|
};
|
|
1216
1526
|
}
|
|
1217
1527
|
return {
|
|
@@ -1221,7 +1531,8 @@ function scanExactCodexTaskComplete(text, acceptanceId) {
|
|
|
1221
1531
|
? { timestamp: String(match.timestamp) }
|
|
1222
1532
|
: {}),
|
|
1223
1533
|
scannedRecords,
|
|
1224
|
-
observedTaskCompleteRecords
|
|
1534
|
+
observedTaskCompleteRecords,
|
|
1535
|
+
observedTurnAbortedRecords
|
|
1225
1536
|
};
|
|
1226
1537
|
}
|
|
1227
1538
|
function codexCompletionPending(code, diagnostics, detail) {
|
|
@@ -1490,6 +1801,292 @@ function validateCodexOpenRootInventoryForAcceptance(value) {
|
|
|
1490
1801
|
}
|
|
1491
1802
|
return value;
|
|
1492
1803
|
}
|
|
1804
|
+
function readPendingCodexRequestUserInput(input) {
|
|
1805
|
+
const rollout = normalizedRolloutIdentity(input.rollout);
|
|
1806
|
+
const nativeThreadId = exactNativeThreadId(input.nativeThreadId);
|
|
1807
|
+
const opened = openExactRollout(rollout);
|
|
1808
|
+
try {
|
|
1809
|
+
const before = opened.stat;
|
|
1810
|
+
assertPrivateRegularFile(before);
|
|
1811
|
+
const header = readCodexRolloutHeader(opened.fd, before.size);
|
|
1812
|
+
assertExistingCodexRolloutHeader(header, nativeThreadId);
|
|
1813
|
+
if (!fileEndsWithNewline(opened.fd, before.size)) {
|
|
1814
|
+
throw new Error("Codex questionnaire rollout has a partial JSONL record");
|
|
1815
|
+
}
|
|
1816
|
+
const readStart = Math.max(0, before.size - input.maxBytes);
|
|
1817
|
+
const length = before.size - readStart;
|
|
1818
|
+
const buffer = Buffer.allocUnsafe(length);
|
|
1819
|
+
if (fs.readSync(opened.fd, buffer, 0, length, readStart) !== length) {
|
|
1820
|
+
throw new Error("Codex questionnaire rollout changed while it was read");
|
|
1821
|
+
}
|
|
1822
|
+
const after = fs.fstatSync(opened.fd);
|
|
1823
|
+
if (!sameStableFile(before, after)) {
|
|
1824
|
+
throw new Error("Codex questionnaire rollout changed while it was scanned");
|
|
1825
|
+
}
|
|
1826
|
+
const atBoundary = readStart === 0 ||
|
|
1827
|
+
byteAtOffset(opened.fd, readStart - 1) === 0x0a;
|
|
1828
|
+
const firstComplete = atBoundary ? 0 : buffer.indexOf(0x0a) + 1;
|
|
1829
|
+
if (firstComplete <= 0 && !atBoundary) {
|
|
1830
|
+
throw new Error("Codex questionnaire rollout record exceeded its bounded tail scan");
|
|
1831
|
+
}
|
|
1832
|
+
const records = parseCodexJsonlRecords(buffer.subarray(firstComplete), "questionnaire tail");
|
|
1833
|
+
return pendingCodexRequestUserInputFromRecords({
|
|
1834
|
+
records,
|
|
1835
|
+
nativeThreadId,
|
|
1836
|
+
rollout,
|
|
1837
|
+
prefixTruncated: readStart > 0
|
|
1838
|
+
});
|
|
1839
|
+
}
|
|
1840
|
+
finally {
|
|
1841
|
+
fs.closeSync(opened.fd);
|
|
1842
|
+
}
|
|
1843
|
+
}
|
|
1844
|
+
function pendingCodexRequestUserInputFromRecords(input) {
|
|
1845
|
+
const calls = [];
|
|
1846
|
+
const outputs = new Map();
|
|
1847
|
+
const terminalTurns = new Map();
|
|
1848
|
+
const unscopedTerminalIndexes = [];
|
|
1849
|
+
const startedTurns = [];
|
|
1850
|
+
let sawLifecycle = false;
|
|
1851
|
+
input.records.forEach(({ value }, index) => {
|
|
1852
|
+
const payload = isRecord(value.payload) ? value.payload : undefined;
|
|
1853
|
+
if (!payload)
|
|
1854
|
+
return;
|
|
1855
|
+
if (value.type === "event_msg" &&
|
|
1856
|
+
payload.type === "task_started") {
|
|
1857
|
+
startedTurns.push({
|
|
1858
|
+
index,
|
|
1859
|
+
turnId: exactNativeThreadId(payload.turn_id)
|
|
1860
|
+
});
|
|
1861
|
+
sawLifecycle = true;
|
|
1862
|
+
return;
|
|
1863
|
+
}
|
|
1864
|
+
if (value.type === "event_msg" &&
|
|
1865
|
+
(payload.type === "task_complete" || payload.type === "turn_aborted")) {
|
|
1866
|
+
const rawTurnId = optionalString(payload.turn_id);
|
|
1867
|
+
if (!rawTurnId) {
|
|
1868
|
+
unscopedTerminalIndexes.push(index);
|
|
1869
|
+
sawLifecycle = true;
|
|
1870
|
+
return;
|
|
1871
|
+
}
|
|
1872
|
+
const turnId = exactNativeThreadId(rawTurnId);
|
|
1873
|
+
const indexes = terminalTurns.get(turnId) ?? [];
|
|
1874
|
+
indexes.push(index);
|
|
1875
|
+
terminalTurns.set(turnId, indexes);
|
|
1876
|
+
sawLifecycle = true;
|
|
1877
|
+
return;
|
|
1878
|
+
}
|
|
1879
|
+
if (value.type !== "response_item" ||
|
|
1880
|
+
payload.type !== "function_call" ||
|
|
1881
|
+
payload.name !== "request_user_input") {
|
|
1882
|
+
if (value.type === "response_item" &&
|
|
1883
|
+
payload.type === "function_call_output") {
|
|
1884
|
+
const callId = requiredString(payload.call_id, "Codex questionnaire output call id");
|
|
1885
|
+
const indexes = outputs.get(callId) ?? [];
|
|
1886
|
+
indexes.push(index);
|
|
1887
|
+
outputs.set(callId, indexes);
|
|
1888
|
+
}
|
|
1889
|
+
return;
|
|
1890
|
+
}
|
|
1891
|
+
const metadata = isRecord(payload.internal_chat_message_metadata_passthrough)
|
|
1892
|
+
? payload.internal_chat_message_metadata_passthrough
|
|
1893
|
+
: undefined;
|
|
1894
|
+
const callId = requiredString(payload.call_id, "Codex questionnaire call id");
|
|
1895
|
+
if (calls.some((call) => call.callId === callId)) {
|
|
1896
|
+
throw new Error("Codex questionnaire call id is duplicated");
|
|
1897
|
+
}
|
|
1898
|
+
calls.push({
|
|
1899
|
+
index,
|
|
1900
|
+
callId,
|
|
1901
|
+
turnId: exactNativeThreadId(metadata?.turn_id),
|
|
1902
|
+
questions: parseCodexRequestUserInputArguments(payload.arguments)
|
|
1903
|
+
});
|
|
1904
|
+
});
|
|
1905
|
+
const pending = calls.filter((call) => {
|
|
1906
|
+
if (outputs.get(call.callId)?.some((index) => index > call.index)) {
|
|
1907
|
+
return false;
|
|
1908
|
+
}
|
|
1909
|
+
if (terminalTurns.get(call.turnId)?.some((index) => index > call.index)) {
|
|
1910
|
+
return false;
|
|
1911
|
+
}
|
|
1912
|
+
if (unscopedTerminalIndexes.some((index) => index > call.index)) {
|
|
1913
|
+
return false;
|
|
1914
|
+
}
|
|
1915
|
+
const precedingStart = latestCodexTaskStartBefore(startedTurns, call.index);
|
|
1916
|
+
if (precedingStart && precedingStart.turnId !== call.turnId) {
|
|
1917
|
+
return false;
|
|
1918
|
+
}
|
|
1919
|
+
return !startedTurns.some((started) => started.index > call.index);
|
|
1920
|
+
});
|
|
1921
|
+
if (input.prefixTruncated &&
|
|
1922
|
+
pending.length === 0 &&
|
|
1923
|
+
!sawLifecycle) {
|
|
1924
|
+
throw new Error("Codex questionnaire tail cannot exclude an omitted pending request");
|
|
1925
|
+
}
|
|
1926
|
+
return pending.map((call) => ({
|
|
1927
|
+
nativeThreadId: input.nativeThreadId,
|
|
1928
|
+
rollout: input.rollout,
|
|
1929
|
+
turnId: call.turnId,
|
|
1930
|
+
questions: call.questions
|
|
1931
|
+
}));
|
|
1932
|
+
}
|
|
1933
|
+
function latestCodexTaskStartBefore(starts, recordIndex) {
|
|
1934
|
+
let latest;
|
|
1935
|
+
for (const start of starts) {
|
|
1936
|
+
if (start.index >= recordIndex)
|
|
1937
|
+
break;
|
|
1938
|
+
latest = start;
|
|
1939
|
+
}
|
|
1940
|
+
return latest;
|
|
1941
|
+
}
|
|
1942
|
+
function parseCodexRequestUserInputArguments(value) {
|
|
1943
|
+
if (typeof value !== "string" ||
|
|
1944
|
+
value.length === 0 ||
|
|
1945
|
+
value.length > CODEX_QUESTIONNAIRE_TAIL_MAX_BYTES) {
|
|
1946
|
+
throw new Error("Codex questionnaire arguments are invalid");
|
|
1947
|
+
}
|
|
1948
|
+
let parsed;
|
|
1949
|
+
try {
|
|
1950
|
+
parsed = JSON.parse(value);
|
|
1951
|
+
}
|
|
1952
|
+
catch {
|
|
1953
|
+
throw new Error("Codex questionnaire arguments are not valid JSON");
|
|
1954
|
+
}
|
|
1955
|
+
const questions = isRecord(parsed) ? parsed.questions : undefined;
|
|
1956
|
+
if (!Array.isArray(questions) ||
|
|
1957
|
+
questions.length === 0 ||
|
|
1958
|
+
questions.length > CODEX_QUESTIONNAIRE_MAX_QUESTIONS) {
|
|
1959
|
+
throw new Error("Codex questionnaire question count is invalid");
|
|
1960
|
+
}
|
|
1961
|
+
return questions.map((question) => {
|
|
1962
|
+
if (!isRecord(question)) {
|
|
1963
|
+
throw new Error("Codex questionnaire question is invalid");
|
|
1964
|
+
}
|
|
1965
|
+
const rawOptions = question.options;
|
|
1966
|
+
if (rawOptions !== undefined &&
|
|
1967
|
+
(!Array.isArray(rawOptions) ||
|
|
1968
|
+
rawOptions.length > CODEX_QUESTIONNAIRE_MAX_OPTIONS)) {
|
|
1969
|
+
throw new Error("Codex questionnaire option count is invalid");
|
|
1970
|
+
}
|
|
1971
|
+
return {
|
|
1972
|
+
prompt: normalizedCodexQuestionnaireText(question.question, "Codex questionnaire prompt"),
|
|
1973
|
+
options: (rawOptions ?? []).map((option) => {
|
|
1974
|
+
if (!isRecord(option)) {
|
|
1975
|
+
throw new Error("Codex questionnaire option is invalid");
|
|
1976
|
+
}
|
|
1977
|
+
return {
|
|
1978
|
+
label: normalizedCodexQuestionnaireText(option.label, "Codex questionnaire option label"),
|
|
1979
|
+
...(option.description === undefined
|
|
1980
|
+
? {}
|
|
1981
|
+
: {
|
|
1982
|
+
description: normalizedCodexQuestionnaireText(option.description, "Codex questionnaire option description")
|
|
1983
|
+
})
|
|
1984
|
+
};
|
|
1985
|
+
})
|
|
1986
|
+
};
|
|
1987
|
+
});
|
|
1988
|
+
}
|
|
1989
|
+
function codexPendingQuestionnaireMatchesScreen(call, screen) {
|
|
1990
|
+
if (screen.currentStep < 1 ||
|
|
1991
|
+
screen.currentStep > screen.totalSteps ||
|
|
1992
|
+
call.questions.length !== screen.totalSteps) {
|
|
1993
|
+
return false;
|
|
1994
|
+
}
|
|
1995
|
+
const question = call.questions[screen.currentStep - 1];
|
|
1996
|
+
if (!question)
|
|
1997
|
+
return false;
|
|
1998
|
+
if (question.prompt !== screen.prompt)
|
|
1999
|
+
return false;
|
|
2000
|
+
if (screen.responseKind === "free_text")
|
|
2001
|
+
return true;
|
|
2002
|
+
if (screen.responseKind !== "single_select")
|
|
2003
|
+
return false;
|
|
2004
|
+
return codexRenderedOptionsMatch(question.options, screen.options ?? []);
|
|
2005
|
+
}
|
|
2006
|
+
function normalizedCodexQuestionnaireScreen(screen) {
|
|
2007
|
+
if (!Number.isSafeInteger(screen.currentStep) ||
|
|
2008
|
+
!Number.isSafeInteger(screen.totalSteps) ||
|
|
2009
|
+
screen.currentStep < 1 ||
|
|
2010
|
+
screen.totalSteps < 1 ||
|
|
2011
|
+
screen.currentStep > screen.totalSteps ||
|
|
2012
|
+
screen.totalSteps > CODEX_QUESTIONNAIRE_MAX_QUESTIONS ||
|
|
2013
|
+
!["single_select", "multi_select", "free_text", "confirm"].includes(screen.responseKind) ||
|
|
2014
|
+
(screen.options?.length ?? 0) > CODEX_QUESTIONNAIRE_MAX_OPTIONS) {
|
|
2015
|
+
throw new Error("Codex questionnaire screen evidence is invalid");
|
|
2016
|
+
}
|
|
2017
|
+
return {
|
|
2018
|
+
currentStep: screen.currentStep,
|
|
2019
|
+
totalSteps: screen.totalSteps,
|
|
2020
|
+
prompt: normalizedCodexQuestionnaireText(screen.prompt, "Codex questionnaire screen prompt"),
|
|
2021
|
+
responseKind: screen.responseKind,
|
|
2022
|
+
...(screen.options === undefined
|
|
2023
|
+
? {}
|
|
2024
|
+
: {
|
|
2025
|
+
options: screen.options.map((option) => ({
|
|
2026
|
+
label: normalizedCodexQuestionnaireText(option.label, "Codex questionnaire screen option label"),
|
|
2027
|
+
...(option.description === undefined
|
|
2028
|
+
? {}
|
|
2029
|
+
: {
|
|
2030
|
+
description: normalizedCodexQuestionnaireText(option.description, "Codex questionnaire screen option description")
|
|
2031
|
+
})
|
|
2032
|
+
}))
|
|
2033
|
+
})
|
|
2034
|
+
};
|
|
2035
|
+
}
|
|
2036
|
+
function normalizedCodexQuestionnaireText(value, label) {
|
|
2037
|
+
if (typeof value !== "string" ||
|
|
2038
|
+
value.length === 0 ||
|
|
2039
|
+
value.length > CODEX_QUESTIONNAIRE_MAX_TEXT_LENGTH ||
|
|
2040
|
+
value.includes("\0")) {
|
|
2041
|
+
throw new Error(`${label} is invalid`);
|
|
2042
|
+
}
|
|
2043
|
+
const normalized = value.replace(/\s+/gu, " ").trim();
|
|
2044
|
+
if (!normalized)
|
|
2045
|
+
throw new Error(`${label} is empty`);
|
|
2046
|
+
return normalized;
|
|
2047
|
+
}
|
|
2048
|
+
function codexRenderedOptionsMatch(durable, rendered) {
|
|
2049
|
+
if (sameCodexQuestionnaireOptions(durable, rendered))
|
|
2050
|
+
return true;
|
|
2051
|
+
const canonicalOther = {
|
|
2052
|
+
label: CODEX_QUESTIONNAIRE_OTHER_LABEL,
|
|
2053
|
+
description: CODEX_QUESTIONNAIRE_OTHER_DESCRIPTION
|
|
2054
|
+
};
|
|
2055
|
+
if (sameCodexQuestionnaireOptions([...durable, canonicalOther], rendered)) {
|
|
2056
|
+
return true;
|
|
2057
|
+
}
|
|
2058
|
+
return !durable.some((option) => option.label === CODEX_QUESTIONNAIRE_CUSTOM_LABEL) && sameCodexQuestionnaireOptions([
|
|
2059
|
+
...durable,
|
|
2060
|
+
{
|
|
2061
|
+
label: CODEX_QUESTIONNAIRE_CUSTOM_LABEL,
|
|
2062
|
+
description: CODEX_QUESTIONNAIRE_CUSTOM_DESCRIPTION
|
|
2063
|
+
},
|
|
2064
|
+
canonicalOther
|
|
2065
|
+
], rendered);
|
|
2066
|
+
}
|
|
2067
|
+
function sameCodexQuestionnaireOptions(left, right) {
|
|
2068
|
+
return left.length === right.length && left.every((option, index) => option.label === right[index]?.label &&
|
|
2069
|
+
option.description === right[index]?.description);
|
|
2070
|
+
}
|
|
2071
|
+
function validateCodexRecoveryCandidateForAcceptance(value, anchor) {
|
|
2072
|
+
if (!isRecord(value)) {
|
|
2073
|
+
throw new Error("persisted Codex recovery candidate is invalid");
|
|
2074
|
+
}
|
|
2075
|
+
const sessionId = exactNativeThreadId(value.sessionId);
|
|
2076
|
+
const rollout = normalizedRolloutIdentity(value.rollout);
|
|
2077
|
+
if (value.processUuid !== anchor.process_uuid ||
|
|
2078
|
+
value.processBirth !== anchor.process_birth ||
|
|
2079
|
+
value.evidence !== "codex_open_root_rollout") {
|
|
2080
|
+
throw new Error("persisted Codex recovery candidate has different process authority");
|
|
2081
|
+
}
|
|
2082
|
+
return {
|
|
2083
|
+
sessionId,
|
|
2084
|
+
processUuid: anchor.process_uuid,
|
|
2085
|
+
processBirth: anchor.process_birth,
|
|
2086
|
+
rollout,
|
|
2087
|
+
evidence: "codex_open_root_rollout"
|
|
2088
|
+
};
|
|
2089
|
+
}
|
|
1493
2090
|
function openExactRollout(rollout) {
|
|
1494
2091
|
if (!path.isAbsolute(rollout.path)) {
|
|
1495
2092
|
throw new Error("Codex rollout path is not absolute");
|
|
@@ -1556,10 +2153,16 @@ function sameStableFile(left, right) {
|
|
|
1556
2153
|
}
|
|
1557
2154
|
function sameRolloutIdentity(left, right) {
|
|
1558
2155
|
return left.fd === right.fd &&
|
|
1559
|
-
left
|
|
2156
|
+
sameRolloutFileIdentity(left, right);
|
|
2157
|
+
}
|
|
2158
|
+
function sameRolloutFileIdentity(left, right) {
|
|
2159
|
+
return left.device === right.device &&
|
|
1560
2160
|
left.inode === right.inode &&
|
|
1561
2161
|
left.path === right.path;
|
|
1562
2162
|
}
|
|
2163
|
+
function codexRolloutInodeKey(rollout) {
|
|
2164
|
+
return `${rollout.device}:${rollout.inode}`;
|
|
2165
|
+
}
|
|
1563
2166
|
function optionalString(value) {
|
|
1564
2167
|
return typeof value === "string" && value.trim().length > 0
|
|
1565
2168
|
? value.trim()
|