@jskit-ai/assistant-core 0.2.7 → 0.2.9
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/examples/conversation/package.json +5 -5
- package/package.json +18 -7
- package/src/client/browser.js +21 -0
- package/src/client/conversation/AssistantAttachmentQueue.vue +25 -108
- package/src/client/conversation/AssistantComposerActions.vue +1 -1
- package/src/client/conversation/AssistantConversationElement.vue +14 -52
- package/src/client/conversation/AssistantGoalControl.vue +13 -10
- package/src/client/conversation/AssistantPromptInput.vue +6 -91
- package/src/client/conversation/AssistantTranscript.css +505 -0
- package/src/client/conversation/AssistantTranscript.vue +24 -1197
- package/src/client/conversation/attachmentFiles.js +158 -0
- package/src/client/conversation/attachmentQueueItems.js +121 -0
- package/src/client/conversation/attachmentUploadRows.js +265 -0
- package/src/client/conversation/conversationDisplay.js +54 -0
- package/src/client/conversation/messageDelivery.js +13 -9
- package/src/client/conversation/retainedConversation.js +14 -3
- package/src/client/conversation/submitText.js +13 -2
- package/src/client/conversation/transcriptEntries.js +225 -0
- package/src/client/conversation/useAssistantAttachments.js +86 -388
- package/src/client/conversation/useAssistantQuestions.js +16 -3
- package/src/client/conversation/useAssistantSuggestions.js +16 -2
- package/src/client/conversation/useScrollToBottom.js +17 -19
- package/src/client/conversation/useTextareaAutoGrow.js +115 -0
- package/src/client/conversation/useTranscriptScrollFollow.js +525 -0
- package/src/client/lib/assistantApi.js +192 -161
- package/src/client/types.js +110 -0
- package/src/server/conversation/attachments.js +21 -6
- package/src/server/conversation/boundedToolLoop.js +1 -0
- package/src/server/conversation/claudeClient.js +71 -20
- package/src/server/conversation/claudeCommandRun.js +191 -0
- package/src/server/conversation/claudeHistory.js +7 -0
- package/src/server/conversation/claudeNativeTurn.js +708 -0
- package/src/server/conversation/claudeProcess.js +186 -112
- package/src/server/conversation/claudeProcessStart.js +214 -0
- package/src/server/conversation/claudePromptDelivery.js +183 -0
- package/src/server/conversation/claudeSessionCleanup.js +248 -0
- package/src/server/conversation/claudeTools.js +15 -3
- package/src/server/conversation/claudeTurn.js +212 -1388
- package/src/server/conversation/codexAccountReader.js +168 -0
- package/src/server/conversation/codexAuthPreflight.js +5 -4
- package/src/server/conversation/codexClient.js +18 -6
- package/src/server/conversation/codexConfiguration.js +8 -844
- package/src/server/conversation/codexConversationStops.js +377 -0
- package/src/server/conversation/codexConversationStore.js +119 -85
- package/src/server/conversation/codexConversationTurns.js +378 -0
- package/src/server/conversation/codexConversations.js +134 -866
- package/src/server/conversation/codexDetachedConversation.js +203 -0
- package/src/server/conversation/codexDetachedTurn.js +145 -104
- package/src/server/conversation/codexEvents.js +5 -200
- package/src/server/conversation/codexGoalCommands.js +14 -4
- package/src/server/conversation/codexHelperExecution.js +57 -57
- package/src/server/conversation/codexHelperThreadLedger.js +30 -288
- package/src/server/conversation/codexHelperThreadRecord.js +304 -0
- package/src/server/conversation/codexHelperThreadRecovery.js +54 -76
- package/src/server/conversation/codexHelperThreads.js +94 -93
- package/src/server/conversation/codexHistoryAdapter.js +13 -8
- package/src/server/conversation/codexIsolation.js +672 -0
- package/src/server/conversation/codexLiveItems.js +379 -0
- package/src/server/conversation/codexMessageCommands.js +68 -286
- package/src/server/conversation/codexMessageDelivery.js +46 -37
- package/src/server/conversation/codexModelCatalog.js +86 -7
- package/src/server/conversation/codexNotificationObserver.js +81 -88
- package/src/server/conversation/codexNotificationQueue.js +21 -10
- package/src/server/conversation/codexObservationControl.js +105 -89
- package/src/server/conversation/codexOutput.js +84 -419
- package/src/server/conversation/codexProcess.js +29 -1053
- package/src/server/conversation/codexProcessLeader.js +1 -0
- package/src/server/conversation/codexPromptDispatch.js +241 -0
- package/src/server/conversation/codexProvider.js +147 -1807
- package/src/server/conversation/codexProviderAuth.js +348 -0
- package/src/server/conversation/codexProviderInvalidation.js +371 -0
- package/src/server/conversation/codexProviderInventories.js +437 -0
- package/src/server/conversation/codexProviderOwner.js +149 -957
- package/src/server/conversation/codexProviderRelease.js +465 -0
- package/src/server/conversation/codexProviderSelection.js +26 -15
- package/src/server/conversation/codexProviderThreads.js +698 -0
- package/src/server/conversation/codexProviderTurns.js +453 -0
- package/src/server/conversation/codexReasoning.js +32 -23
- package/src/server/conversation/codexRenewalCommands.js +18 -6
- package/src/server/conversation/codexRenewalThreads.js +7 -22
- package/src/server/conversation/codexResultSettlement.js +70 -88
- package/src/server/conversation/codexRunJournal.js +91 -97
- package/src/server/conversation/codexRuntime.js +426 -0
- package/src/server/conversation/codexRuntimeInventory.js +113 -0
- package/src/server/conversation/codexRuntimeLifecycle.js +39 -13
- package/src/server/conversation/codexRuntimeStart.js +431 -0
- package/src/server/conversation/codexRuntimeStop.js +324 -0
- package/src/server/conversation/codexSessionCleanup.js +25 -10
- package/src/server/conversation/codexSessionReconciliation.js +21 -7
- package/src/server/conversation/codexThreadReadiness.js +49 -52
- package/src/server/conversation/codexThreadSnapshot.js +210 -0
- package/src/server/conversation/codexThreadStatus.js +48 -62
- package/src/server/conversation/codexTurn.js +171 -55
- package/src/server/conversation/codexTurnActivity.js +210 -0
- package/src/server/conversation/codexTurnControl.js +44 -48
- package/src/server/conversation/codexTurnEndings.js +610 -0
- package/src/server/conversation/codexTurnRecovery.js +97 -848
- package/src/server/conversation/codexTurnState.js +138 -111
- package/src/server/conversation/completedResponses.js +227 -0
- package/src/server/conversation/configuration.js +2 -2
- package/src/server/conversation/continuity.js +43 -6
- package/src/server/conversation/conversationDelivery.js +394 -0
- package/src/server/conversation/conversationEntries.js +365 -0
- package/src/server/conversation/conversationEvents.js +192 -0
- package/src/server/conversation/conversationGoals.js +187 -0
- package/src/server/conversation/conversationHandles.js +237 -0
- package/src/server/conversation/conversationReads.js +206 -0
- package/src/server/conversation/conversationRefusals.js +58 -0
- package/src/server/conversation/conversationReplacement.js +254 -0
- package/src/server/conversation/conversationSending.js +273 -0
- package/src/server/conversation/conversationStopping.js +121 -0
- package/src/server/conversation/conversationTurnDelivery.js +413 -0
- package/src/server/conversation/conversationTurns.js +511 -0
- package/src/server/conversation/deliveryTiming.js +25 -3
- package/src/server/conversation/fileStorage.js +11 -6
- package/src/server/conversation/hookBridge.js +14 -3
- package/src/server/conversation/index.js +1 -1
- package/src/server/conversation/localExecution.js +82 -625
- package/src/server/conversation/nativeCommandCallbacks.js +10 -0
- package/src/server/conversation/nativeConversationLifecycle.js +379 -0
- package/src/server/conversation/nativeHistoryExport.js +27 -18
- package/src/server/conversation/normalize.js +9 -0
- package/src/server/conversation/openCodeClient.js +295 -366
- package/src/server/conversation/openCodeNativeHistory.js +141 -0
- package/src/server/conversation/openCodeNativeSession.js +79 -0
- package/src/server/conversation/openCodeProcess.js +109 -53
- package/src/server/conversation/openCodeProcessOwner.js +565 -0
- package/src/server/conversation/openCodeRuntime.js +111 -1814
- package/src/server/conversation/openCodeSessionTurns.js +547 -0
- package/src/server/conversation/openCodeTemporaryConversations.js +394 -0
- package/src/server/conversation/openCodeTools.js +28 -14
- package/src/server/conversation/openCodeTurn.js +80 -74
- package/src/server/conversation/openCodeTurnMonitor.js +388 -0
- package/src/server/conversation/processIdentity.js +582 -0
- package/src/server/conversation/provider.js +21 -6
- package/src/server/conversation/providers/api.js +268 -107
- package/src/server/conversation/providers/claude.js +29 -6
- package/src/server/conversation/providers/claudeDriver.js +325 -250
- package/src/server/conversation/providers/codex.js +69 -33
- package/src/server/conversation/providers/codexDriver.js +503 -1081
- package/src/server/conversation/providers/codexDriverConnection.js +331 -0
- package/src/server/conversation/providers/codexDriverTurns.js +464 -0
- package/src/server/conversation/providers/opencode.js +30 -4
- package/src/server/conversation/providers/opencodeDriver.js +321 -659
- package/src/server/conversation/providers/opencodeStandalone.js +475 -0
- package/src/server/conversation/providers/scoped.js +64 -19
- package/src/server/conversation/runtime.js +171 -2863
- package/src/server/conversation/storage.js +91 -50
- package/src/server/conversation/streams.js +15 -2
- package/src/server/conversation/structuredOutput.js +7 -11
- package/src/server/conversation/systemPrompt.js +13 -1
- package/src/server/conversation/tools.js +47 -15
- package/src/server/conversation/transcript.js +49 -20
- package/src/server/hosts/claudeTurn.js +2 -4
- package/src/server/hosts/codexConfiguration.js +6 -8
- package/src/server/hosts/codexEvents.js +4 -2
- package/src/server/hosts/codexProcess.js +1 -1
- package/src/server/hosts/codexProvider.js +11 -9
- package/src/server/hosts/codexTurn.js +3 -3
- package/src/server/hosts/openCodeClient.js +4 -2
- package/src/server/index.js +0 -2
- package/src/server/lib/aiConnectionClient.js +35 -25
- package/src/server/lib/ndjson.js +1 -0
- package/src/server/lib/providers/anthropicClient.js +15 -6
- package/src/server/lib/providers/common.js +10 -6
- package/src/server/lib/providers/openAiCompatibleClient.js +40 -23
- package/src/server/lib/serviceToolCatalog.js +29 -283
- package/src/server/lib/serviceToolEntries.js +270 -0
- package/src/server/types.js +78 -0
- package/src/shared/assistantResource.js +0 -23
- package/src/shared/conversation/answerChoiceSugar.js +2 -14
- package/src/shared/conversation/messageInputSugar.js +18 -0
- package/src/shared/conversation/numberedQuestionSugar.js +6 -16
- package/src/shared/conversation/pagination.js +1 -1
- package/src/shared/conversation/questions.js +1 -0
- package/src/shared/conversation/streaming.js +1 -0
- package/src/shared/index.js +0 -1
- package/src/testing/conversationStorageContract.js +20 -0
- package/test/aiConnectionClient.test.js +42 -1
- package/test/apiConversationDriver.test.js +217 -38
- package/test/assistantBoundedToolLoop.test.js +1 -1
- package/test/claudeConversationSession.test.js +15 -11
- package/test/claudeInstructionResume.test.js +1 -1
- package/test/claudeProcess.test.js +9 -9
- package/test/claudeTurn.test.js +21 -8
- package/test/codexAccountChange.test.js +5 -3
- package/test/codexConversationSession.test.js +22 -21
- package/test/codexEvents.test.js +3 -1
- package/test/codexHelperDeletion.test.js +3 -7
- package/test/codexIsolation.test.js +6 -6
- package/test/codexModelCatalog.test.js +6 -7
- package/test/codexProcess.test.js +3 -3
- package/test/codexThreadSelection.test.js +4 -1
- package/test/codexTurn.test.js +29 -20
- package/test/conversationApiFacilities.test.js +11 -9
- package/test/conversationAttachments.test.js +3 -3
- package/test/conversationConfiguration.test.js +4 -4
- package/test/conversationNativeStorage.test.js +11 -10
- package/test/conversationReplacement.test.js +13 -13
- package/test/conversationRuntime.test.js +9 -5
- package/test/conversationSession.test.js +78 -22
- package/test/conversationTools.test.js +47 -26
- package/test/fileConversationStorage.test.js +10 -3
- package/test/fixtures/codexHelperThreadLedger.js +3 -3
- package/test/frameworkActions.test.js +33 -18
- package/test/localConversationExecution.test.js +4 -4
- package/test/localExecution.test.js +1 -1
- package/test/openCodeConnectionChange.test.js +3 -2
- package/test/openCodeRuntime.test.js +8 -7
- package/test/openCodeTools.test.js +1 -1
- package/test/opencodeConversationSession.test.js +4 -3
- package/test/serviceToolCatalog.test.js +3 -8
- package/src/server/lib/assistantCompletion.js +0 -190
- package/src/server/lib/assistantToolLoop.js +0 -259
|
@@ -1,304 +1,48 @@
|
|
|
1
1
|
import crypto from "node:crypto";
|
|
2
2
|
import { link, mkdir, open, readdir, rename, rm } from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
|
-
import { isRecord } from "@jskit-ai/kernel/shared/support/normalize";
|
|
5
4
|
import { normalizeNullableText } from "./normalize.js";
|
|
6
5
|
|
|
7
6
|
import { tryAcquireExclusiveFileLock } from "@jskit-ai/kernel/server/support";
|
|
7
|
+
import { createCodexHelperThreadRecordSchema } from "./codexHelperThreadRecord.js";
|
|
8
8
|
|
|
9
|
-
const CODEX_HELPER_THREAD_LEDGER_SCHEMA_VERSION = 1;
|
|
10
9
|
const CODEX_HELPER_THREAD_LEDGER_DIRECTORY = "codex-helper-thread-ownership";
|
|
11
10
|
const CODEX_HELPER_THREAD_LEDGER_MAX_FILE_BYTES = 64 * 1024;
|
|
12
11
|
const CODEX_HELPER_THREAD_LEDGER_MAX_RECORDS = 1024;
|
|
13
12
|
const CODEX_HELPER_THREAD_LEDGER_LOCK_WAIT_MS = 5000;
|
|
14
13
|
const CODEX_HELPER_THREAD_LEDGER_LOCK_POLL_MS = 20;
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
"createdAt",
|
|
26
|
-
"executionProfile",
|
|
27
|
-
"identity",
|
|
28
|
-
"lifecycle",
|
|
29
|
-
"ownershipId",
|
|
30
|
-
"projectContextRoot",
|
|
31
|
-
"projectRuntimeRoot",
|
|
32
|
-
"revision",
|
|
33
|
-
"schemaVersion",
|
|
34
|
-
"sessionId",
|
|
35
|
-
"threadId",
|
|
36
|
-
"turnId",
|
|
37
|
-
"updatedAt",
|
|
38
|
-
"workdir"
|
|
39
|
-
]);
|
|
40
|
-
const IDENTITY_KEYS = Object.freeze([
|
|
41
|
-
"providerId",
|
|
42
|
-
"providerKeyFingerprint",
|
|
43
|
-
"runtime",
|
|
44
|
-
"server",
|
|
45
|
-
"transportId"
|
|
46
|
-
]);
|
|
47
|
-
const RUNTIME_IDENTITY_KEYS = Object.freeze([
|
|
48
|
-
"accountIdentitySignature",
|
|
49
|
-
"authStateSignature",
|
|
50
|
-
"endpoint",
|
|
51
|
-
"executionMode",
|
|
52
|
-
"executionContextHash",
|
|
53
|
-
"provider",
|
|
54
|
-
"runtimeDir",
|
|
55
|
-
"runtimesHash",
|
|
56
|
-
"terminalEnvHash",
|
|
57
|
-
"toolHomeSource",
|
|
58
|
-
"transport"
|
|
59
|
-
]);
|
|
60
|
-
const SERVER_IDENTITY_KEYS = Object.freeze([
|
|
61
|
-
"userAgent"
|
|
62
|
-
]);
|
|
63
|
-
|
|
64
|
-
// The application retains its profile vocabulary and policy. Each owner retains
|
|
65
|
-
// the original module's independent mutation queue and lock-instance identity.
|
|
14
|
+
function observeNoLock() {}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* The application retains its profile vocabulary and policy. Each owner retains its own mutation queue and
|
|
18
|
+
* lock-instance identity.
|
|
19
|
+
* @param {object} options
|
|
20
|
+
* @param {(profile: unknown) => object} options.executionProfile The application's execution-profile codec.
|
|
21
|
+
* @param {(profile: unknown) => object} [options.snapshotExecutionProfile]
|
|
22
|
+
* @param {string} [options.errorPrefix]
|
|
23
|
+
*/
|
|
66
24
|
function createCodexHelperThreadLedgerOwner({
|
|
67
25
|
executionProfile,
|
|
68
26
|
snapshotExecutionProfile = executionProfile,
|
|
69
27
|
errorPrefix = "codex_helper_"
|
|
70
|
-
}
|
|
28
|
+
}) {
|
|
29
|
+
// readable-check: allow method-probe - input validation: the application must supply its execution-profile codec
|
|
71
30
|
if (typeof executionProfile !== "function") {
|
|
72
31
|
throw new TypeError("Codex helper ownership requires its execution-profile codec.");
|
|
73
32
|
}
|
|
33
|
+
const {
|
|
34
|
+
ledgerError,
|
|
35
|
+
assertExactKeys,
|
|
36
|
+
requiredText,
|
|
37
|
+
absolutePath,
|
|
38
|
+
timestamp,
|
|
39
|
+
positiveInteger,
|
|
40
|
+
codexHelperThreadRecordId,
|
|
41
|
+
defineCodexHelperThreadRecord
|
|
42
|
+
} = createCodexHelperThreadRecordSchema({ executionProfile, errorPrefix });
|
|
74
43
|
const codexHelperThreadLedgerMutationQueues = new Map();
|
|
75
44
|
const codexHelperThreadLedgerProcessId = crypto.randomUUID();
|
|
76
45
|
|
|
77
|
-
function ledgerError(message = "", code = `${errorPrefix}ledger_invalid`) {
|
|
78
|
-
const error = new Error(normalizeNullableText(message) || "Codex helper ownership record is invalid.");
|
|
79
|
-
error.code = code;
|
|
80
|
-
error.retryable = true;
|
|
81
|
-
return error;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
function assertExactKeys(value, expectedKeys, label) {
|
|
85
|
-
if (!isRecord(value)) {
|
|
86
|
-
throw ledgerError(`${label} must be an object.`);
|
|
87
|
-
}
|
|
88
|
-
const actual = Object.keys(value).sort();
|
|
89
|
-
const expected = [...expectedKeys].sort();
|
|
90
|
-
if (
|
|
91
|
-
actual.length !== expected.length ||
|
|
92
|
-
actual.some((key, index) => key !== expected[index])
|
|
93
|
-
) {
|
|
94
|
-
throw ledgerError(`${label} has an unsupported shape.`);
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
function requiredText(value, label, maxLength = 4096) {
|
|
99
|
-
const normalized = normalizeNullableText(value);
|
|
100
|
-
if (!normalized) {
|
|
101
|
-
throw ledgerError(`${label} is required.`);
|
|
102
|
-
}
|
|
103
|
-
if (normalized.length > maxLength) {
|
|
104
|
-
throw ledgerError(`${label} exceeds ${maxLength} characters.`);
|
|
105
|
-
}
|
|
106
|
-
return normalized;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
function requiredSha256Signature(value, label) {
|
|
110
|
-
const signature = requiredText(value, label, 71);
|
|
111
|
-
if (!/^sha256:[a-f0-9]{64}$/u.test(signature)) {
|
|
112
|
-
throw ledgerError(`${label} must be a SHA-256 signature.`);
|
|
113
|
-
}
|
|
114
|
-
return signature;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
function requiredVersionedStateSignature(value, label) {
|
|
118
|
-
const signature = requiredText(value, label, 32);
|
|
119
|
-
if (!/^v[1-9][0-9]*:[a-f0-9]{24}$/u.test(signature)) {
|
|
120
|
-
throw ledgerError(`${label} must be a versioned state signature.`);
|
|
121
|
-
}
|
|
122
|
-
return signature;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
function requiredStableHash(value, label) {
|
|
126
|
-
const hash = requiredText(value, label, 12);
|
|
127
|
-
if (!/^[a-f0-9]{12}$/u.test(hash)) {
|
|
128
|
-
throw ledgerError(`${label} must be a stable hash.`);
|
|
129
|
-
}
|
|
130
|
-
return hash;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
function optionalText(value, label = "value", maxLength = 4096) {
|
|
134
|
-
const normalized = normalizeNullableText(value);
|
|
135
|
-
if (normalized.length > maxLength) {
|
|
136
|
-
throw ledgerError(`${label} exceeds ${maxLength} characters.`);
|
|
137
|
-
}
|
|
138
|
-
return normalized;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
function absolutePath(value, label) {
|
|
142
|
-
const normalized = requiredText(value, label, 8192);
|
|
143
|
-
if (!path.isAbsolute(normalized) || path.normalize(normalized) !== normalized) {
|
|
144
|
-
throw ledgerError(`${label} must be a normalized absolute path.`);
|
|
145
|
-
}
|
|
146
|
-
return normalized;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
function optionalAbsolutePath(value, label) {
|
|
150
|
-
const normalized = optionalText(value, label, 8192);
|
|
151
|
-
if (normalized && (!path.isAbsolute(normalized) || path.normalize(normalized) !== normalized)) {
|
|
152
|
-
throw ledgerError(`${label} must be a normalized absolute path when present.`);
|
|
153
|
-
}
|
|
154
|
-
return normalized;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
function timestamp(value, label) {
|
|
158
|
-
const normalized = requiredText(value, label);
|
|
159
|
-
const parsed = Date.parse(normalized);
|
|
160
|
-
if (!Number.isFinite(parsed) || new Date(parsed).toISOString() !== normalized) {
|
|
161
|
-
throw ledgerError(`${label} must be an ISO timestamp.`);
|
|
162
|
-
}
|
|
163
|
-
return normalized;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
function positiveInteger(value, label) {
|
|
167
|
-
const normalized = Number(value);
|
|
168
|
-
if (!Number.isSafeInteger(normalized) || normalized < 1) {
|
|
169
|
-
throw ledgerError(`${label} must be a positive integer.`);
|
|
170
|
-
}
|
|
171
|
-
return normalized;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
function strictIdentity(value) {
|
|
175
|
-
assertExactKeys(value, IDENTITY_KEYS, "identity");
|
|
176
|
-
assertExactKeys(value.runtime, RUNTIME_IDENTITY_KEYS, "identity.runtime");
|
|
177
|
-
assertExactKeys(value.server, SERVER_IDENTITY_KEYS, "identity.server");
|
|
178
|
-
const runtime = value.runtime;
|
|
179
|
-
const providerId = requiredText(value.providerId, "identity.providerId", 64);
|
|
180
|
-
const executionMode = requiredText(
|
|
181
|
-
runtime.executionMode,
|
|
182
|
-
"identity.runtime.executionMode",
|
|
183
|
-
64
|
|
184
|
-
);
|
|
185
|
-
const provider = requiredText(runtime.provider, "identity.runtime.provider");
|
|
186
|
-
const toolHomeSource = optionalAbsolutePath(
|
|
187
|
-
runtime.toolHomeSource,
|
|
188
|
-
"identity.runtime.toolHomeSource"
|
|
189
|
-
);
|
|
190
|
-
const transportId = requiredText(value.transportId, "identity.transportId", 64);
|
|
191
|
-
const transport = requiredText(runtime.transport, "identity.runtime.transport", 64);
|
|
192
|
-
const endpoint = requiredText(runtime.endpoint, "identity.runtime.endpoint", 8192);
|
|
193
|
-
const endpointPath = endpoint.startsWith("unix://") ? endpoint.slice("unix://".length) : "";
|
|
194
|
-
if (
|
|
195
|
-
providerId !== "codex" ||
|
|
196
|
-
!["helper", "interactive"].includes(executionMode) ||
|
|
197
|
-
provider !== "codex_app_server" ||
|
|
198
|
-
transport !== "unix" ||
|
|
199
|
-
!endpointPath ||
|
|
200
|
-
!path.isAbsolute(endpointPath) ||
|
|
201
|
-
path.normalize(endpointPath) !== endpointPath ||
|
|
202
|
-
transportId !== "codex_app_server"
|
|
203
|
-
) {
|
|
204
|
-
throw ledgerError(
|
|
205
|
-
"Codex helper ownership identity must use the managed Codex app-server."
|
|
206
|
-
);
|
|
207
|
-
}
|
|
208
|
-
return Object.freeze({
|
|
209
|
-
providerId,
|
|
210
|
-
providerKeyFingerprint: requiredSha256Signature(
|
|
211
|
-
value.providerKeyFingerprint,
|
|
212
|
-
"identity.providerKeyFingerprint"
|
|
213
|
-
),
|
|
214
|
-
runtime: Object.freeze({
|
|
215
|
-
accountIdentitySignature: requiredSha256Signature(
|
|
216
|
-
runtime.accountIdentitySignature,
|
|
217
|
-
"identity.runtime.accountIdentitySignature"
|
|
218
|
-
),
|
|
219
|
-
authStateSignature: requiredVersionedStateSignature(
|
|
220
|
-
runtime.authStateSignature,
|
|
221
|
-
"identity.runtime.authStateSignature"
|
|
222
|
-
),
|
|
223
|
-
endpoint,
|
|
224
|
-
executionMode,
|
|
225
|
-
executionContextHash: requiredStableHash(
|
|
226
|
-
runtime.executionContextHash,
|
|
227
|
-
"identity.runtime.executionContextHash"
|
|
228
|
-
),
|
|
229
|
-
provider,
|
|
230
|
-
runtimeDir: absolutePath(runtime.runtimeDir, "identity.runtime.runtimeDir"),
|
|
231
|
-
runtimesHash: requiredStableHash(
|
|
232
|
-
runtime.runtimesHash,
|
|
233
|
-
"identity.runtime.runtimesHash"
|
|
234
|
-
),
|
|
235
|
-
terminalEnvHash: requiredStableHash(
|
|
236
|
-
runtime.terminalEnvHash,
|
|
237
|
-
"identity.runtime.terminalEnvHash"
|
|
238
|
-
),
|
|
239
|
-
toolHomeSource,
|
|
240
|
-
transport
|
|
241
|
-
}),
|
|
242
|
-
server: Object.freeze({
|
|
243
|
-
userAgent: requiredText(value.server.userAgent, "identity.server.userAgent", 512)
|
|
244
|
-
}),
|
|
245
|
-
transportId
|
|
246
|
-
});
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
function codexHelperThreadRecordId({
|
|
250
|
-
projectRuntimeRoot = "",
|
|
251
|
-
sessionId = "",
|
|
252
|
-
threadId = ""
|
|
253
|
-
} = {}) {
|
|
254
|
-
return crypto.createHash("sha256").update([
|
|
255
|
-
absolutePath(projectRuntimeRoot, "projectRuntimeRoot"),
|
|
256
|
-
requiredText(sessionId, "sessionId", 256),
|
|
257
|
-
requiredText(threadId, "threadId", 256)
|
|
258
|
-
].join("\0")).digest("hex");
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
function defineCodexHelperThreadRecord(value = {}) {
|
|
262
|
-
assertExactKeys(value, RECORD_KEYS, "Codex helper ownership record");
|
|
263
|
-
if (Number(value.schemaVersion) !== CODEX_HELPER_THREAD_LEDGER_SCHEMA_VERSION) {
|
|
264
|
-
throw ledgerError(
|
|
265
|
-
`Unsupported Codex helper ownership schema: ${String(value.schemaVersion ?? "missing")}.`
|
|
266
|
-
);
|
|
267
|
-
}
|
|
268
|
-
const lifecycle = requiredText(value.lifecycle, "lifecycle");
|
|
269
|
-
if (!CODEX_HELPER_THREAD_LIFECYCLE_VALUES.has(lifecycle)) {
|
|
270
|
-
throw ledgerError(`Unsupported Codex helper ownership lifecycle: ${lifecycle}.`);
|
|
271
|
-
}
|
|
272
|
-
const turnId = optionalText(value.turnId);
|
|
273
|
-
if (lifecycle === CODEX_HELPER_THREAD_LIFECYCLES.ACTIVE && !turnId) {
|
|
274
|
-
throw ledgerError("An active Codex helper ownership record requires turnId.");
|
|
275
|
-
}
|
|
276
|
-
if (lifecycle === CODEX_HELPER_THREAD_LIFECYCLES.READY && turnId) {
|
|
277
|
-
throw ledgerError(`${lifecycle} Codex helper ownership cannot retain turnId.`);
|
|
278
|
-
}
|
|
279
|
-
const createdAt = timestamp(value.createdAt, "createdAt");
|
|
280
|
-
const updatedAt = timestamp(value.updatedAt, "updatedAt");
|
|
281
|
-
if (Date.parse(updatedAt) < Date.parse(createdAt)) {
|
|
282
|
-
throw ledgerError("updatedAt cannot precede createdAt.");
|
|
283
|
-
}
|
|
284
|
-
return Object.freeze({
|
|
285
|
-
createdAt,
|
|
286
|
-
executionProfile: executionProfile(value.executionProfile),
|
|
287
|
-
identity: strictIdentity(value.identity),
|
|
288
|
-
lifecycle,
|
|
289
|
-
ownershipId: requiredText(value.ownershipId, "ownershipId", 128),
|
|
290
|
-
projectContextRoot: absolutePath(value.projectContextRoot, "projectContextRoot"),
|
|
291
|
-
projectRuntimeRoot: absolutePath(value.projectRuntimeRoot, "projectRuntimeRoot"),
|
|
292
|
-
revision: positiveInteger(value.revision, "revision"),
|
|
293
|
-
schemaVersion: CODEX_HELPER_THREAD_LEDGER_SCHEMA_VERSION,
|
|
294
|
-
sessionId: requiredText(value.sessionId, "sessionId", 256),
|
|
295
|
-
threadId: requiredText(value.threadId, "threadId", 256),
|
|
296
|
-
turnId: optionalText(turnId, "turnId", 256),
|
|
297
|
-
updatedAt,
|
|
298
|
-
workdir: absolutePath(value.workdir, "workdir")
|
|
299
|
-
});
|
|
300
|
-
}
|
|
301
|
-
|
|
302
46
|
function codexHelperThreadLedgerRoot(projectRuntimeRoot = "") {
|
|
303
47
|
return path.join(
|
|
304
48
|
absolutePath(projectRuntimeRoot, "projectRuntimeRoot"),
|
|
@@ -436,7 +180,7 @@ function createCodexHelperThreadLedgerOwner({
|
|
|
436
180
|
}
|
|
437
181
|
|
|
438
182
|
async function acquireHostLock(root = "", recordId = "", {
|
|
439
|
-
observeLock =
|
|
183
|
+
observeLock = observeNoLock
|
|
440
184
|
} = {}) {
|
|
441
185
|
const lockPath = path.join(
|
|
442
186
|
root,
|
|
@@ -448,7 +192,7 @@ function createCodexHelperThreadLedgerOwner({
|
|
|
448
192
|
try {
|
|
449
193
|
releaseFileLock = await tryAcquireExclusiveFileLock(lockPath, { cwd: root });
|
|
450
194
|
if (!releaseFileLock) {
|
|
451
|
-
await observeLock
|
|
195
|
+
await observeLock({ stage: "host-contended" });
|
|
452
196
|
if (Date.now() >= deadline) {
|
|
453
197
|
throw ledgerError(
|
|
454
198
|
"Codex helper ownership is busy in another process.",
|
|
@@ -458,7 +202,7 @@ function createCodexHelperThreadLedgerOwner({
|
|
|
458
202
|
await delay(CODEX_HELPER_THREAD_LEDGER_LOCK_POLL_MS);
|
|
459
203
|
continue;
|
|
460
204
|
}
|
|
461
|
-
await observeLock
|
|
205
|
+
await observeLock({ stage: "host-acquired" });
|
|
462
206
|
let released = false;
|
|
463
207
|
return async () => {
|
|
464
208
|
if (released) {
|
|
@@ -472,7 +216,7 @@ function createCodexHelperThreadLedgerOwner({
|
|
|
472
216
|
}
|
|
473
217
|
};
|
|
474
218
|
} catch (error) {
|
|
475
|
-
throw error?.code
|
|
219
|
+
throw typeof error?.code === "string" && error.code.startsWith(errorPrefix)
|
|
476
220
|
? error
|
|
477
221
|
: ledgerError(`Codex helper ownership host lock failed: ${error.message}`);
|
|
478
222
|
}
|
|
@@ -480,7 +224,7 @@ function createCodexHelperThreadLedgerOwner({
|
|
|
480
224
|
}
|
|
481
225
|
|
|
482
226
|
async function acquireFilesystemLock(root = "", recordId = "", {
|
|
483
|
-
observeLock =
|
|
227
|
+
observeLock = observeNoLock
|
|
484
228
|
} = {}) {
|
|
485
229
|
const locksRoot = path.join(root, ".locks");
|
|
486
230
|
await mkdir(locksRoot, { mode: 0o770, recursive: true });
|
|
@@ -530,7 +274,7 @@ function createCodexHelperThreadLedgerOwner({
|
|
|
530
274
|
throw readError;
|
|
531
275
|
});
|
|
532
276
|
if (verified?.token === current.token) {
|
|
533
|
-
await observeLock
|
|
277
|
+
await observeLock({ stage: "stale-verified" });
|
|
534
278
|
await rm(lockPath).catch((removeError) => {
|
|
535
279
|
if (removeError?.code !== "ENOENT") {
|
|
536
280
|
throw removeError;
|
|
@@ -591,7 +335,7 @@ function createCodexHelperThreadLedgerOwner({
|
|
|
591
335
|
}
|
|
592
336
|
|
|
593
337
|
function createCodexHelperThreadLedger({
|
|
594
|
-
observeLock =
|
|
338
|
+
observeLock = observeNoLock,
|
|
595
339
|
projectRuntimeRoot = ""
|
|
596
340
|
} = {}) {
|
|
597
341
|
const normalizedProjectRuntimeRoot = absolutePath(projectRuntimeRoot, "projectRuntimeRoot");
|
|
@@ -792,7 +536,5 @@ function createCodexHelperThreadLedgerOwner({
|
|
|
792
536
|
}
|
|
793
537
|
|
|
794
538
|
export {
|
|
795
|
-
CODEX_HELPER_THREAD_LEDGER_SCHEMA_VERSION,
|
|
796
|
-
CODEX_HELPER_THREAD_LIFECYCLES,
|
|
797
539
|
createCodexHelperThreadLedgerOwner
|
|
798
540
|
};
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { isRecord } from "@jskit-ai/kernel/shared/support/normalize";
|
|
4
|
+
import { normalizeNullableText } from "./normalize.js";
|
|
5
|
+
|
|
6
|
+
const CODEX_HELPER_THREAD_LEDGER_SCHEMA_VERSION = 1;
|
|
7
|
+
const CODEX_HELPER_THREAD_LIFECYCLES = Object.freeze({
|
|
8
|
+
ACTIVE: "active",
|
|
9
|
+
CLEANUP_REQUIRED: "cleanup_required",
|
|
10
|
+
READY: "ready",
|
|
11
|
+
STARTING_TURN: "starting_turn"
|
|
12
|
+
});
|
|
13
|
+
const CODEX_HELPER_THREAD_LIFECYCLE_VALUES = new Set(
|
|
14
|
+
Object.values(CODEX_HELPER_THREAD_LIFECYCLES)
|
|
15
|
+
);
|
|
16
|
+
const RECORD_KEYS = Object.freeze([
|
|
17
|
+
"createdAt",
|
|
18
|
+
"executionProfile",
|
|
19
|
+
"identity",
|
|
20
|
+
"lifecycle",
|
|
21
|
+
"ownershipId",
|
|
22
|
+
"projectContextRoot",
|
|
23
|
+
"projectRuntimeRoot",
|
|
24
|
+
"revision",
|
|
25
|
+
"schemaVersion",
|
|
26
|
+
"sessionId",
|
|
27
|
+
"threadId",
|
|
28
|
+
"turnId",
|
|
29
|
+
"updatedAt",
|
|
30
|
+
"workdir"
|
|
31
|
+
]);
|
|
32
|
+
const IDENTITY_KEYS = Object.freeze([
|
|
33
|
+
"providerId",
|
|
34
|
+
"providerKeyFingerprint",
|
|
35
|
+
"runtime",
|
|
36
|
+
"server",
|
|
37
|
+
"transportId"
|
|
38
|
+
]);
|
|
39
|
+
const RUNTIME_IDENTITY_KEYS = Object.freeze([
|
|
40
|
+
"accountIdentitySignature",
|
|
41
|
+
"authStateSignature",
|
|
42
|
+
"endpoint",
|
|
43
|
+
"executionMode",
|
|
44
|
+
"executionContextHash",
|
|
45
|
+
"provider",
|
|
46
|
+
"runtimeDir",
|
|
47
|
+
"runtimesHash",
|
|
48
|
+
"terminalEnvHash",
|
|
49
|
+
"toolHomeSource",
|
|
50
|
+
"transport"
|
|
51
|
+
]);
|
|
52
|
+
const SERVER_IDENTITY_KEYS = Object.freeze([
|
|
53
|
+
"userAgent"
|
|
54
|
+
]);
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* The helper thread ownership record: its exact shape, its identity and the validation every stored record passes.
|
|
58
|
+
* @param {object} options
|
|
59
|
+
* @param {(profile: unknown) => object} options.executionProfile The application's execution-profile codec.
|
|
60
|
+
* @param {string} options.errorPrefix
|
|
61
|
+
*/
|
|
62
|
+
function createCodexHelperThreadRecordSchema({ executionProfile, errorPrefix }) {
|
|
63
|
+
function ledgerError(message = "", code = `${errorPrefix}ledger_invalid`) {
|
|
64
|
+
const error = new Error(normalizeNullableText(message) || "Codex helper ownership record is invalid.");
|
|
65
|
+
error.code = code;
|
|
66
|
+
error.retryable = true;
|
|
67
|
+
return error;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function assertExactKeys(value, expectedKeys, label) {
|
|
71
|
+
if (!isRecord(value)) {
|
|
72
|
+
throw ledgerError(`${label} must be an object.`);
|
|
73
|
+
}
|
|
74
|
+
const actual = Object.keys(value).sort();
|
|
75
|
+
const expected = [...expectedKeys].sort();
|
|
76
|
+
if (
|
|
77
|
+
actual.length !== expected.length ||
|
|
78
|
+
actual.some((key, index) => key !== expected[index])
|
|
79
|
+
) {
|
|
80
|
+
throw ledgerError(`${label} has an unsupported shape.`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function requiredText(value, label, maxLength = 4096) {
|
|
85
|
+
const normalized = normalizeNullableText(value);
|
|
86
|
+
if (!normalized) {
|
|
87
|
+
throw ledgerError(`${label} is required.`);
|
|
88
|
+
}
|
|
89
|
+
if (normalized.length > maxLength) {
|
|
90
|
+
throw ledgerError(`${label} exceeds ${maxLength} characters.`);
|
|
91
|
+
}
|
|
92
|
+
return normalized;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function requiredSha256Signature(value, label) {
|
|
96
|
+
const signature = requiredText(value, label, 71);
|
|
97
|
+
if (!/^sha256:[a-f0-9]{64}$/u.test(signature)) {
|
|
98
|
+
throw ledgerError(`${label} must be a SHA-256 signature.`);
|
|
99
|
+
}
|
|
100
|
+
return signature;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function requiredVersionedStateSignature(value, label) {
|
|
104
|
+
const signature = requiredText(value, label, 32);
|
|
105
|
+
if (!/^v[1-9][0-9]*:[a-f0-9]{24}$/u.test(signature)) {
|
|
106
|
+
throw ledgerError(`${label} must be a versioned state signature.`);
|
|
107
|
+
}
|
|
108
|
+
return signature;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function requiredStableHash(value, label) {
|
|
112
|
+
const hash = requiredText(value, label, 12);
|
|
113
|
+
if (!/^[a-f0-9]{12}$/u.test(hash)) {
|
|
114
|
+
throw ledgerError(`${label} must be a stable hash.`);
|
|
115
|
+
}
|
|
116
|
+
return hash;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function optionalText(value, label = "value", maxLength = 4096) {
|
|
120
|
+
const normalized = normalizeNullableText(value);
|
|
121
|
+
if (normalized.length > maxLength) {
|
|
122
|
+
throw ledgerError(`${label} exceeds ${maxLength} characters.`);
|
|
123
|
+
}
|
|
124
|
+
return normalized;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function absolutePath(value, label) {
|
|
128
|
+
const normalized = requiredText(value, label, 8192);
|
|
129
|
+
if (!path.isAbsolute(normalized) || path.normalize(normalized) !== normalized) {
|
|
130
|
+
throw ledgerError(`${label} must be a normalized absolute path.`);
|
|
131
|
+
}
|
|
132
|
+
return normalized;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function optionalAbsolutePath(value, label) {
|
|
136
|
+
const normalized = optionalText(value, label, 8192);
|
|
137
|
+
if (normalized && (!path.isAbsolute(normalized) || path.normalize(normalized) !== normalized)) {
|
|
138
|
+
throw ledgerError(`${label} must be a normalized absolute path when present.`);
|
|
139
|
+
}
|
|
140
|
+
return normalized;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function timestamp(value, label) {
|
|
144
|
+
const normalized = requiredText(value, label);
|
|
145
|
+
const parsed = Date.parse(normalized);
|
|
146
|
+
if (!Number.isFinite(parsed) || new Date(parsed).toISOString() !== normalized) {
|
|
147
|
+
throw ledgerError(`${label} must be an ISO timestamp.`);
|
|
148
|
+
}
|
|
149
|
+
return normalized;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function positiveInteger(value, label) {
|
|
153
|
+
const normalized = Number(value);
|
|
154
|
+
if (!Number.isSafeInteger(normalized) || normalized < 1) {
|
|
155
|
+
throw ledgerError(`${label} must be a positive integer.`);
|
|
156
|
+
}
|
|
157
|
+
return normalized;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function strictIdentity(value) {
|
|
161
|
+
assertExactKeys(value, IDENTITY_KEYS, "identity");
|
|
162
|
+
assertExactKeys(value.runtime, RUNTIME_IDENTITY_KEYS, "identity.runtime");
|
|
163
|
+
assertExactKeys(value.server, SERVER_IDENTITY_KEYS, "identity.server");
|
|
164
|
+
const runtime = value.runtime;
|
|
165
|
+
const providerId = requiredText(value.providerId, "identity.providerId", 64);
|
|
166
|
+
const executionMode = requiredText(
|
|
167
|
+
runtime.executionMode,
|
|
168
|
+
"identity.runtime.executionMode",
|
|
169
|
+
64
|
|
170
|
+
);
|
|
171
|
+
const provider = requiredText(runtime.provider, "identity.runtime.provider");
|
|
172
|
+
const toolHomeSource = optionalAbsolutePath(
|
|
173
|
+
runtime.toolHomeSource,
|
|
174
|
+
"identity.runtime.toolHomeSource"
|
|
175
|
+
);
|
|
176
|
+
const transportId = requiredText(value.transportId, "identity.transportId", 64);
|
|
177
|
+
const transport = requiredText(runtime.transport, "identity.runtime.transport", 64);
|
|
178
|
+
const endpoint = requiredText(runtime.endpoint, "identity.runtime.endpoint", 8192);
|
|
179
|
+
const endpointPath = endpoint.startsWith("unix://") ? endpoint.slice("unix://".length) : "";
|
|
180
|
+
if (
|
|
181
|
+
providerId !== "codex" ||
|
|
182
|
+
!["helper", "interactive"].includes(executionMode) ||
|
|
183
|
+
provider !== "codex_app_server" ||
|
|
184
|
+
transport !== "unix" ||
|
|
185
|
+
!endpointPath ||
|
|
186
|
+
!path.isAbsolute(endpointPath) ||
|
|
187
|
+
path.normalize(endpointPath) !== endpointPath ||
|
|
188
|
+
transportId !== "codex_app_server"
|
|
189
|
+
) {
|
|
190
|
+
throw ledgerError(
|
|
191
|
+
"Codex helper ownership identity must use the managed Codex app-server."
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
return Object.freeze({
|
|
195
|
+
providerId,
|
|
196
|
+
providerKeyFingerprint: requiredSha256Signature(
|
|
197
|
+
value.providerKeyFingerprint,
|
|
198
|
+
"identity.providerKeyFingerprint"
|
|
199
|
+
),
|
|
200
|
+
runtime: Object.freeze({
|
|
201
|
+
accountIdentitySignature: requiredSha256Signature(
|
|
202
|
+
runtime.accountIdentitySignature,
|
|
203
|
+
"identity.runtime.accountIdentitySignature"
|
|
204
|
+
),
|
|
205
|
+
authStateSignature: requiredVersionedStateSignature(
|
|
206
|
+
runtime.authStateSignature,
|
|
207
|
+
"identity.runtime.authStateSignature"
|
|
208
|
+
),
|
|
209
|
+
endpoint,
|
|
210
|
+
executionMode,
|
|
211
|
+
executionContextHash: requiredStableHash(
|
|
212
|
+
runtime.executionContextHash,
|
|
213
|
+
"identity.runtime.executionContextHash"
|
|
214
|
+
),
|
|
215
|
+
provider,
|
|
216
|
+
runtimeDir: absolutePath(runtime.runtimeDir, "identity.runtime.runtimeDir"),
|
|
217
|
+
runtimesHash: requiredStableHash(
|
|
218
|
+
runtime.runtimesHash,
|
|
219
|
+
"identity.runtime.runtimesHash"
|
|
220
|
+
),
|
|
221
|
+
terminalEnvHash: requiredStableHash(
|
|
222
|
+
runtime.terminalEnvHash,
|
|
223
|
+
"identity.runtime.terminalEnvHash"
|
|
224
|
+
),
|
|
225
|
+
toolHomeSource,
|
|
226
|
+
transport
|
|
227
|
+
}),
|
|
228
|
+
server: Object.freeze({
|
|
229
|
+
userAgent: requiredText(value.server.userAgent, "identity.server.userAgent", 512)
|
|
230
|
+
}),
|
|
231
|
+
transportId
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function codexHelperThreadRecordId({
|
|
236
|
+
projectRuntimeRoot = "",
|
|
237
|
+
sessionId = "",
|
|
238
|
+
threadId = ""
|
|
239
|
+
} = {}) {
|
|
240
|
+
return crypto.createHash("sha256").update([
|
|
241
|
+
absolutePath(projectRuntimeRoot, "projectRuntimeRoot"),
|
|
242
|
+
requiredText(sessionId, "sessionId", 256),
|
|
243
|
+
requiredText(threadId, "threadId", 256)
|
|
244
|
+
].join("\0")).digest("hex");
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function defineCodexHelperThreadRecord(value = {}) {
|
|
248
|
+
assertExactKeys(value, RECORD_KEYS, "Codex helper ownership record");
|
|
249
|
+
if (Number(value.schemaVersion) !== CODEX_HELPER_THREAD_LEDGER_SCHEMA_VERSION) {
|
|
250
|
+
throw ledgerError(
|
|
251
|
+
`Unsupported Codex helper ownership schema: ${String(value.schemaVersion ?? "missing")}.`
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
const lifecycle = requiredText(value.lifecycle, "lifecycle");
|
|
255
|
+
if (!CODEX_HELPER_THREAD_LIFECYCLE_VALUES.has(lifecycle)) {
|
|
256
|
+
throw ledgerError(`Unsupported Codex helper ownership lifecycle: ${lifecycle}.`);
|
|
257
|
+
}
|
|
258
|
+
const turnId = optionalText(value.turnId);
|
|
259
|
+
if (lifecycle === CODEX_HELPER_THREAD_LIFECYCLES.ACTIVE && !turnId) {
|
|
260
|
+
throw ledgerError("An active Codex helper ownership record requires turnId.");
|
|
261
|
+
}
|
|
262
|
+
if (lifecycle === CODEX_HELPER_THREAD_LIFECYCLES.READY && turnId) {
|
|
263
|
+
throw ledgerError(`${lifecycle} Codex helper ownership cannot retain turnId.`);
|
|
264
|
+
}
|
|
265
|
+
const createdAt = timestamp(value.createdAt, "createdAt");
|
|
266
|
+
const updatedAt = timestamp(value.updatedAt, "updatedAt");
|
|
267
|
+
if (Date.parse(updatedAt) < Date.parse(createdAt)) {
|
|
268
|
+
throw ledgerError("updatedAt cannot precede createdAt.");
|
|
269
|
+
}
|
|
270
|
+
return Object.freeze({
|
|
271
|
+
createdAt,
|
|
272
|
+
executionProfile: executionProfile(value.executionProfile),
|
|
273
|
+
identity: strictIdentity(value.identity),
|
|
274
|
+
lifecycle,
|
|
275
|
+
ownershipId: requiredText(value.ownershipId, "ownershipId", 128),
|
|
276
|
+
projectContextRoot: absolutePath(value.projectContextRoot, "projectContextRoot"),
|
|
277
|
+
projectRuntimeRoot: absolutePath(value.projectRuntimeRoot, "projectRuntimeRoot"),
|
|
278
|
+
revision: positiveInteger(value.revision, "revision"),
|
|
279
|
+
schemaVersion: CODEX_HELPER_THREAD_LEDGER_SCHEMA_VERSION,
|
|
280
|
+
sessionId: requiredText(value.sessionId, "sessionId", 256),
|
|
281
|
+
threadId: requiredText(value.threadId, "threadId", 256),
|
|
282
|
+
turnId: optionalText(turnId, "turnId", 256),
|
|
283
|
+
updatedAt,
|
|
284
|
+
workdir: absolutePath(value.workdir, "workdir")
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
return Object.freeze({
|
|
289
|
+
ledgerError,
|
|
290
|
+
assertExactKeys,
|
|
291
|
+
requiredText,
|
|
292
|
+
absolutePath,
|
|
293
|
+
timestamp,
|
|
294
|
+
positiveInteger,
|
|
295
|
+
codexHelperThreadRecordId,
|
|
296
|
+
defineCodexHelperThreadRecord
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
export {
|
|
301
|
+
CODEX_HELPER_THREAD_LEDGER_SCHEMA_VERSION,
|
|
302
|
+
CODEX_HELPER_THREAD_LIFECYCLES,
|
|
303
|
+
createCodexHelperThreadRecordSchema
|
|
304
|
+
};
|