@gitroll/tacitt-cli 0.4.2 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{agent-setup-Bpsi-BCv.mjs → agent-setup-Dv_mQ38P.mjs} +2 -2
- package/dist/agent-setup-worker.mjs +5 -5
- package/dist/cli.mjs +6 -5
- package/dist/codex-title-worker.mjs +1 -1
- package/dist/configured-preview-worker.mjs +5 -5
- package/dist/configured-session-worker.mjs +5 -5
- package/dist/{frame-IeDOLy1o.mjs → frame-DGun31Uc.mjs} +1 -1
- package/dist/{heap-DiOlSa8T.mjs → heap-DY5n4EVC.mjs} +5 -4
- package/dist/inspection-worker.mjs +4 -4
- package/dist/{job-BII94L2c.mjs → job-BxUVFocm.mjs} +383 -8
- package/dist/{locate-CBiw-CJK.mjs → locate-JYtFmKu-.mjs} +115 -3
- package/dist/{model-DGTsARtV.mjs → model-C1VzgKLi.mjs} +1 -1
- package/dist/operation-executor.mjs +3 -3
- package/dist/preview-worker.mjs +1 -1
- package/dist/privacy-validator.mjs +1 -1
- package/dist/{query-CVqWNFYf.mjs → query-Cm5lumS4.mjs} +588 -64
- package/dist/{run-BGlf-W0M.mjs → run-hQ_W2p_Y.mjs} +4 -4
- package/dist/{src-CBTvOa44.mjs → src--Ln5GHdz.mjs} +44 -9
- package/dist/{src-D4nrfC2S.mjs → src-BMSLVcgs.mjs} +6 -5
- package/dist/{src-37kIclGK.mjs → src-CGFwLUd6.mjs} +4 -4
- package/dist/store-database-worker.mjs +1 -1
- package/dist/web/assets/index-Bbck2aOB.css +1 -0
- package/dist/web/assets/index-CXm08nyc.js +69 -0
- package/dist/web/index.html +2 -2
- package/package.json +1 -1
- package/skills/tacitt/SKILL.md +4 -4
- package/skills/tacitt/references/INDEX.md +11 -10
- package/skills/tacitt/references/commands.md +25 -12
- package/skills/tacitt/references/journey.md +3 -3
- package/skills/tacitt/references/remote-workers.md +46 -0
- package/skills/tacitt/references/send.md +5 -1
- package/skills/tacitt/scripts/INDEX.md +36 -36
- package/skills/tacitt/scripts/journey-scan-timing.mjs +5 -4
- package/skills/tacitt/scripts/journey-state.d.mts +9 -0
- package/skills/tacitt/scripts/journey-state.mjs +19 -2
- package/skills/tacitt/scripts/journey.d.mts +4 -0
- package/skills/tacitt/scripts/journey.mjs +4 -2
- package/skills/tacitt/scripts/privacy-validator.mjs +76 -3
- package/skills/tacitt/scripts/review.mjs +6 -6
- package/dist/web/assets/index-D3g_ATU-.js +0 -69
- package/dist/web/assets/index-D5DEG5Ue.css +0 -1
|
@@ -1,16 +1,17 @@
|
|
|
1
|
-
import { $ as createCleanup, B as parallelismLimits, C as readOriginalSearchFields, E as checkReusableBundle, F as SessionWorkerError, G as processJourneyMetadataFields, H as privateJourneyDirectory, I as sessionWorkerFailure, K as staticMaskCategory, L as privateCollectionDirectory, M as readCollectionIndex, N as parseCollectionWorkspaceSummary, O as captureLogicalJourneySources, P as createSharedSessionPool, Q as removeNamedTemporarySpools, R as defaultParallelism, T as createCollectionArtifacts, U as readLogicalJourneyFinalMasks, W as readConfiguredOutputInfo, Z as createTemporary, _ as processJourneyMetadata, a as scanReviewFields, at as windowsOwnerOnly, c as readSessionScanRecords, g as readCapturedOriginalRows, i as buildWorkScanBatches, it as restrictToOwner, j as runConfiguredSessionJob, k as capturedJourneyScope, l as scanPreparedSession, m as openConfiguredUpload, n as verifyPreparedSessionSources, nt as makePrivateDirectory, o as createWorkPieces, ot as isWindowsAclFailure, p as configuredSessionUploads, q as readWorkspacePair, r as prepareSessionScans, rt as openPrivateFile, s as readSessionScanFields, st as windowsAclCodes, t as preparedScanConfiguration, v as readStaticSessionMasks, w as readConfiguredSearchFields, y as staticSessionJobs, z as gitSeedsProvider } from "./src-
|
|
2
|
-
import { Bt as sessionPeriodSchema, Dn as
|
|
3
|
-
import { a as mainTextChunks, c as previewInWorker, i as nearestSortingRepository, l as createNativeAdapters, n as createProjectEvidenceCollector, o as mainTextFields, r as sortingRepositoryId, s as textChunks, t as createHarnesses, u as createGitNameSeedProvider } from "./src-
|
|
4
|
-
import { $ as settingsQuerySchema, $r as
|
|
5
|
-
import { $ as checkVersion, A as resourceSelectionQuerySchema, B as termReuseQuerySchema, C as loadAcceptedReview, D as aggregateUsefulness, E as loadPrivateResource, F as validateUnitCandidate, G as requireActiveBatch, H as batchHeader, I as reuseQuerySchema, J as validateDatabase, K as initializeAndClaim, L as batchQuery, M as evaluateUnitEvidence, N as sameCandidateRequest, O as sessionUsefulnessRecommendation, P as validateCandidateAttempt, Q as validateState, R as isPrivateResourceKind, S as validateSessionBindings, T as loadBatch, U as createJourneyPayloadFiles, V as privateReviewHash, W as validateBatchBody, X as readBundleDirectory, Y as withDatabase, Z as readStoredState, _ as loadReconciliation, _t as isCoordinationBusy, a as decodeError, at as hasResetMarker, b as validatePreparationSelection, c as dropsAnchor, ct as privatePath, d as parseDelivery, dt as stateDirectory, et as schemaVersion, f as validateCandidate, ft as syncDirectory, g as loadPreparedBodies, gt as errorCode$1, h as loadPreparation, ht as StoreError, i as createExecutor, it as finishReset, j as inspectionQuerySchema$1, k as sessionQuery, l as replySchema, lt as readResetMarker, m as loadStatus, mt as withFileCheckTimings, n as releaseInheritedHeapFlags, nt as createDatabaseFile, o as ProtocolError, ot as inspectStateFiles, p as classificationJourneyCommit, pt as withStateSnapshot, q as ownership, r as storeHeapLimits, rt as databaseName, s as allowedReplies, st as present, tt as beginReset, u as operations, ut as recheckPrivatePath, v as loadRereadBodies, vt as isSqliteCorruption, w as loadAuthenticatedReview, x as validateSessionReconciliation, y as loadSessionBodies, z as privateResourceHeader } from "./heap-
|
|
6
|
-
import { _ as staticBaseProduction, a as writeJourneyOutput, c as mergeHideIndexTokens, f as writeJourneyBase, g as staticBaseInputs, h as staticSessions, l as packRenderHideIndex, m as saveStaticSession, o as createHideIndexTokens, p as readStaticResult, s as hideIndexTexts, t as journeyOutputJob, u as journeyBaseJob, v as prepareSourcePlan, y as workApproval } from "./job-
|
|
7
|
-
import { t as createAgentSetupPort } from "./agent-setup-
|
|
1
|
+
import { $ as createCleanup, B as parallelismLimits, C as readOriginalSearchFields, E as checkReusableBundle, F as SessionWorkerError, G as processJourneyMetadataFields, H as privateJourneyDirectory, I as sessionWorkerFailure, K as staticMaskCategory, L as privateCollectionDirectory, M as readCollectionIndex, N as parseCollectionWorkspaceSummary, O as captureLogicalJourneySources, P as createSharedSessionPool, Q as removeNamedTemporarySpools, R as defaultParallelism, T as createCollectionArtifacts, U as readLogicalJourneyFinalMasks, W as readConfiguredOutputInfo, Z as createTemporary, _ as processJourneyMetadata, a as scanReviewFields, at as windowsOwnerOnly, c as readSessionScanRecords, g as readCapturedOriginalRows, i as buildWorkScanBatches, it as restrictToOwner, j as runConfiguredSessionJob, k as capturedJourneyScope, l as scanPreparedSession, m as openConfiguredUpload, n as verifyPreparedSessionSources, nt as makePrivateDirectory, o as createWorkPieces, ot as isWindowsAclFailure, p as configuredSessionUploads, q as readWorkspacePair, r as prepareSessionScans, rt as openPrivateFile, s as readSessionScanFields, st as windowsAclCodes, t as preparedScanConfiguration, v as readStaticSessionMasks, w as readConfiguredSearchFields, y as staticSessionJobs, z as gitSeedsProvider } from "./src-BMSLVcgs.mjs";
|
|
2
|
+
import { $t as safeField, Bt as sessionPeriodSchema, Dn as createStaticProcessor, F as bClassificationKey, Fn as entityDictionaryVersion, Ft as termTypeInfo, Gt as ownerProfileSchema, H as codePointLength, Ht as journeyProfileAddressActionSchema, K as utf16Offset, Kt as passportAddressSchema, N as bUnitFragments, Ot as privacyTermKey, Qt as safeCode, Sn as currentDetectorVersion, T as bLiteralTerms, Un as frameRecords, Ut as journeyProfileLinksActionSchema, V as bDigest, Wt as mergeOwnerProfile, Xt as ApplicationError, Zt as UnfinishedJourneyError, an as observeSource, ar as admitCustomTerm, b as validateBReview, c as textDigest, h as reviewFindingsDigest, ht as journeyDescribeSchema, ir as createEntityMatcher, jt as reviewTermsInputSchema, l as validateReview, n as compileBFindings, nn as reviewFamilies, or as admitGitName, qt as profileHasChoices, r as reviewBatchChunks, s as reviewSubmitSchema, t as locateTerms, tn as familySelected, z as bAnchorKey, zt as filterInventoryByPeriod } from "./locate-JYtFmKu-.mjs";
|
|
3
|
+
import { a as mainTextChunks, c as previewInWorker, i as nearestSortingRepository, l as createNativeAdapters, n as createProjectEvidenceCollector, o as mainTextFields, r as sortingRepositoryId, s as textChunks, t as createHarnesses, u as createGitNameSeedProvider } from "./src-CGFwLUd6.mjs";
|
|
4
|
+
import { $ as settingsQuerySchema, $r as journeyRenderSchema, A as deviceProfileScopeSchema, B as validateInspectionBatch, Bn as journeyConfirmedHides, Br as hasCompleteWorkDescription, Bt as newJourneyWork, C as rebindReviewSubmit, Cr as privacyDecisionDigest, Ct as sendStartSchema, D as createAccount, Dr as hasCurrentStaticReview, E as staticallyHiddenFindings, Er as nativeRespondSchema, Et as obsoleteJourney, Fn as confirmedHideGroupIds, Fr as sendArtifactSchema, Hr as isIncludedWork, Ht as journeyContextQuerySchema, Ir as sendReceiptSchema, Jn as reviewCandidateResourceSchema, Kn as workCorrectionBinding, L as createApplication, Ln as confirmedHideKinds, Lr as sendReplaceResultSchema, Lt as classificationPartition, M as readAccountAvatarUrl, Mr as agentSourceSchema, Mt as applyClassification, N as storedCredentialSchema, Nn as bTermMatcher, Nr as agentSourcesSchema, O as accountProfileSchema, Or as staticReviewDigest, Pr as journeySendSchema, Pt as journeyReviewStorageQuerySchema, Q as settingsBatchSchema, Qn as bReviewDigest, Qr as journeyRenderInputSchema, Qt as journeyResourceQuerySchema, R as inspectionCancelSchema, Rr as sendResolutionSchema, Rt as classificationWorks, S as rebindReviewOverlay, Sn as readConfirmedHideChange, Sr as assertJourneyPrivacyReady, St as sendPrepareSchema, T as guardedFindingText, Tn as termHideAdditions, Tr as nativeGetSchema, Tt as currentJourneyAdapters, U as inspectionQuerySchema, Un as retireWorkDescription, Ur as isReadingComplete, Vn as maskedHitsStale, Vr as isClassifying, Vt as reviewCompileSchema, Wn as workDecisionDigest, Wr as isSelectedWork, Xn as reviewRepairResourceSchema, Xr as journeyFindingsSchema, Xt as journeyPolicyRespondSchema, Yn as reviewRepairInputSchema, Yr as journeyFindingImpactSchema, Yt as searchScopeDigest, Zr as journeyPreviewSchema, _t as maximumTermsFileBytes, a as bCrossSegmentReviewSchema, ai as producerVersion, ar as scanGitSeedsId, b as bReviewScope, bn as confirmedHideAdditions, br as journeyClassificationSessionsSchema, bt as sendActionSchema, c as reconstructBFields, ci as recommendParallelism, cr as scanExclusionResourceSchema, ct as validateFailureLocation, d as selectedValueMembers, dn as sessionReviewInputSchema, dr as journeyInventoryQuerySchema, dt as manualUploadChoiceSchema, ei as commandSchema, er as planBRepair, et as readWorkspaceOutcomes, f as splitSelectorValues, fn as sessionReviewQuerySchema, ft as manualUploadPrepareSchema, g as changeJourney, gt as emptyTermsRevision, hn as sessionUsefulnessInputSchema, hr as emptyPreparationFacts, i as validateBCrossSegmentReview, ir as scanSessionResourceSchema, j as profileUrlMatchesIdentity, k as deviceAuthorizationSchema, ln as sessionRereadInputSchema, lt as manualUploadActionSchema, m as hideScopeDigest, mt as manualUploadStartSchema, n as scanBatchEquivalenceDigest, ni as selectionBatchSchema, nt as appendSensitiveTerms, o as bRereadDigest, oi as stateSchema, on as sessionReconcileInputSchema, or as scanGitSeedsResource, ot as artifactFailureDetailSchema, p as activeJourneyTerms, pt as manualUploadSchema, qn as reviewCandidateInputSchema, qr as withReadingProgress, qt as journeySearchResultSchema, r as locateTermEvidence, ri as selectionSchema, rn as reviewAttemptResourceSchema, rr as scanPreparationDigest, s as reconcileBSession, sr as verifiedScanGitSeeds, st as resolveFailureLocation, t as scanBatchContextDigest, ti as packageCommandSchema, tn as journeyPlaceSchema, vt as normalizeSensitiveTerms, w as reviewBatchReuse, wr as nativeEventsSchema, x as rebindReviewAlignment, xt as sendChoiceSchema, y as isGenericPublicTerm, yt as replaceSensitiveTermsSchema, z as inspectionStartSchema, zr as sendSettingsSchema, zt as retireScanGeneration } from "./src--Ln5GHdz.mjs";
|
|
5
|
+
import { $ as checkVersion, A as resourceSelectionQuerySchema, B as termReuseQuerySchema, C as loadAcceptedReview, D as aggregateUsefulness, E as loadPrivateResource, F as validateUnitCandidate, G as requireActiveBatch, H as batchHeader, I as reuseQuerySchema, J as validateDatabase, K as initializeAndClaim, L as batchQuery, M as evaluateUnitEvidence, N as sameCandidateRequest, O as sessionUsefulnessRecommendation, P as validateCandidateAttempt, Q as validateState, R as isPrivateResourceKind, S as validateSessionBindings, T as loadBatch, U as createJourneyPayloadFiles, V as privateReviewHash, W as validateBatchBody, X as readBundleDirectory, Y as withDatabase, Z as readStoredState, _ as loadReconciliation, _t as isCoordinationBusy, a as decodeError, at as hasResetMarker, b as validatePreparationSelection, c as dropsAnchor, ct as privatePath, d as parseDelivery, dt as stateDirectory, et as schemaVersion, f as validateCandidate, ft as syncDirectory, g as loadPreparedBodies, gt as errorCode$1, h as loadPreparation, ht as StoreError, i as createExecutor, it as finishReset, j as inspectionQuerySchema$1, k as sessionQuery, l as replySchema, lt as readResetMarker, m as loadStatus, mt as withFileCheckTimings, n as releaseInheritedHeapFlags, nt as createDatabaseFile, o as ProtocolError, ot as inspectStateFiles, p as classificationJourneyCommit, pt as withStateSnapshot, q as ownership, r as storeHeapLimits, rt as databaseName, s as allowedReplies, st as present, tt as beginReset, u as operations, ut as recheckPrivatePath, v as loadRereadBodies, vt as isSqliteCorruption, w as loadAuthenticatedReview, x as validateSessionReconciliation, y as loadSessionBodies, z as privateResourceHeader } from "./heap-DY5n4EVC.mjs";
|
|
6
|
+
import { C as journeyDirectory, S as saveRemoteState, _ as staticBaseProduction, a as writeJourneyOutput, b as isRemoteImport, c as mergeHideIndexTokens, f as writeJourneyBase, g as staticBaseInputs, h as staticSessions, l as packRenderHideIndex, m as saveStaticSession, o as createHideIndexTokens, p as readStaticResult, s as hideIndexTexts, t as journeyOutputJob, u as journeyBaseJob, v as prepareSourcePlan, w as collectRemoteWorkers, x as readRemoteState, y as workApproval } from "./job-BxUVFocm.mjs";
|
|
7
|
+
import { t as createAgentSetupPort } from "./agent-setup-Dv_mQ38P.mjs";
|
|
8
8
|
import { t as workerMessageSchema } from "./messages-D3m3bhva.mjs";
|
|
9
9
|
import { constants, write } from "node:fs";
|
|
10
10
|
import { z } from "zod";
|
|
11
11
|
import { copyFile, link, lstat, mkdir, mkdtemp, open, opendir, readFile, readdir, realpath, rename, rm, unlink, writeFile } from "node:fs/promises";
|
|
12
12
|
import { execFile, spawn } from "node:child_process";
|
|
13
|
-
import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
13
|
+
import { basename, dirname, isAbsolute, join, posix, relative, resolve, sep } from "node:path";
|
|
14
|
+
import { createInterface } from "node:readline";
|
|
14
15
|
import { Writable } from "node:stream";
|
|
15
16
|
import { parseArgs, promisify, types } from "node:util";
|
|
16
17
|
import { createHash, randomBytes, randomUUID, timingSafeEqual } from "node:crypto";
|
|
@@ -27,6 +28,7 @@ import { pipeline } from "node:stream/promises";
|
|
|
27
28
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
28
29
|
import { DatabaseSync } from "node:sqlite";
|
|
29
30
|
import { Worker } from "node:worker_threads";
|
|
31
|
+
import { parse } from "shell-quote";
|
|
30
32
|
import { rgPath } from "@vscode/ripgrep";
|
|
31
33
|
import { pino } from "pino";
|
|
32
34
|
import { SeverityNumber } from "@opentelemetry/api-logs";
|
|
@@ -858,7 +860,9 @@ const nativePanelRespondSchema = z.object({
|
|
|
858
860
|
z.object({
|
|
859
861
|
kind: z.literal("retry-work"),
|
|
860
862
|
workId: z.uuid()
|
|
861
|
-
}).strict()
|
|
863
|
+
}).strict(),
|
|
864
|
+
journeyProfileAddressActionSchema,
|
|
865
|
+
journeyProfileLinksActionSchema
|
|
862
866
|
])
|
|
863
867
|
}).strict();
|
|
864
868
|
function createRouter(application, account, stopping, delivery, nativeInspection, agentPresence) {
|
|
@@ -932,7 +936,10 @@ function createRouter(application, account, stopping, delivery, nativeInspection
|
|
|
932
936
|
pause: t.procedure.input(sendActionSchema).mutation(({ input }) => sending().pause(input)),
|
|
933
937
|
resume: t.procedure.input(sendActionSchema).mutation(({ input }) => sending().resume(input)),
|
|
934
938
|
withdraw: t.procedure.input(sendActionSchema).mutation(({ input }) => sending().withdraw(input)),
|
|
935
|
-
advance: t.procedure.input(sendAddress).mutation(({ input }) => sending().advance(input, stopping))
|
|
939
|
+
advance: t.procedure.input(sendAddress).mutation(({ input }) => sending().advance(input, stopping)),
|
|
940
|
+
ownerProfile: t.procedure.query(({ signal }) => sending().ownerProfile(signal ?? stopping)),
|
|
941
|
+
addressTaken: t.procedure.input(z.object({ address: passportAddressSchema }).strict()).query(({ input, signal }) => sending().addressTaken(input, signal ?? stopping)),
|
|
942
|
+
saveSentProfile: t.procedure.input(z.object({ journeyId: z.uuid() }).strict()).mutation(({ input }) => sending().saveSentProfile(input, stopping))
|
|
936
943
|
}),
|
|
937
944
|
manualUpload: t.router({
|
|
938
945
|
get: t.procedure.input(z.object({ uploadId: z.uuid().optional() }).strict()).query(({ input }) => uploading().get(input.uploadId)),
|
|
@@ -5345,6 +5352,7 @@ const businessCommands = [
|
|
|
5345
5352
|
"send.resume",
|
|
5346
5353
|
"send.advance",
|
|
5347
5354
|
"journey.start",
|
|
5355
|
+
"journey.remoteWorkers",
|
|
5348
5356
|
"journey.serve",
|
|
5349
5357
|
"journey.get",
|
|
5350
5358
|
"journey.groupItems",
|
|
@@ -5634,6 +5642,334 @@ function helpText(source = sourceCheckout) {
|
|
|
5634
5642
|
return source ? fullHelp : fullHelp.replace(/^ {2}--dev .*\n/m, "");
|
|
5635
5643
|
}
|
|
5636
5644
|
//#endregion
|
|
5645
|
+
//#region apps/cli/src/remote/dispatch.ts
|
|
5646
|
+
function balancedQuotes(command) {
|
|
5647
|
+
let quote = "";
|
|
5648
|
+
let escaped = false;
|
|
5649
|
+
for (const character of command) {
|
|
5650
|
+
if (escaped) {
|
|
5651
|
+
escaped = false;
|
|
5652
|
+
continue;
|
|
5653
|
+
}
|
|
5654
|
+
if (character === "\\" && quote !== "'") escaped = true;
|
|
5655
|
+
else if (quote === character) quote = "";
|
|
5656
|
+
else if (!quote && (character === "'" || character === "\"")) quote = character;
|
|
5657
|
+
}
|
|
5658
|
+
return !quote && !escaped;
|
|
5659
|
+
}
|
|
5660
|
+
/** Parse inert simple commands. Expansions and unsupported compound syntax fail closed. */
|
|
5661
|
+
function commands$1(command) {
|
|
5662
|
+
if (Buffer.byteLength(command) > 65536 || /`|\$\(|[\0\r]/u.test(command) || !balancedQuotes(command)) return [];
|
|
5663
|
+
try {
|
|
5664
|
+
const segments = [[]];
|
|
5665
|
+
let redirect = false;
|
|
5666
|
+
for (const token of parse(command.replaceAll("\n", ";"), () => "\0")) if (typeof token === "string") {
|
|
5667
|
+
if (token.includes("\0")) return [];
|
|
5668
|
+
if (redirect) redirect = false;
|
|
5669
|
+
else segments.at(-1)?.push(token);
|
|
5670
|
+
} else if ("comment" in token) continue;
|
|
5671
|
+
else if ("op" in token && [
|
|
5672
|
+
">",
|
|
5673
|
+
">>",
|
|
5674
|
+
"<",
|
|
5675
|
+
">&",
|
|
5676
|
+
"<&"
|
|
5677
|
+
].includes(token.op)) {
|
|
5678
|
+
if (redirect) return [];
|
|
5679
|
+
redirect = true;
|
|
5680
|
+
} else if ("op" in token && [
|
|
5681
|
+
";",
|
|
5682
|
+
"&&",
|
|
5683
|
+
"||",
|
|
5684
|
+
"|",
|
|
5685
|
+
"&"
|
|
5686
|
+
].includes(token.op)) segments.push([]);
|
|
5687
|
+
else return [];
|
|
5688
|
+
return redirect ? [] : segments.filter((words) => words.length > 0);
|
|
5689
|
+
} catch {
|
|
5690
|
+
return [];
|
|
5691
|
+
}
|
|
5692
|
+
}
|
|
5693
|
+
function agent(words) {
|
|
5694
|
+
const executable = posix.basename(words[0] ?? "");
|
|
5695
|
+
if (words.some((word) => [
|
|
5696
|
+
"--help",
|
|
5697
|
+
"-h",
|
|
5698
|
+
"--version"
|
|
5699
|
+
].includes(word))) return null;
|
|
5700
|
+
if (executable === "codex" && words[1] === "exec") return "codex";
|
|
5701
|
+
if (executable === "claude" && words.slice(1).some((word) => word === "-p" || word === "--print")) return "claude";
|
|
5702
|
+
return null;
|
|
5703
|
+
}
|
|
5704
|
+
const validHost = (host) => /^(?:[a-zA-Z0-9_][a-zA-Z0-9_.-]*@)?(?:[a-zA-Z0-9_][a-zA-Z0-9_.-]*|\[[a-fA-F0-9:]+\])$/u.test(host);
|
|
5705
|
+
/** Consent and import identity include every retained connection selector. */
|
|
5706
|
+
function endpoint(host, transport, args) {
|
|
5707
|
+
return transport === "ssh" && args.length === 1 ? host : `${host} (${transport}: ${args.join(" ")})`;
|
|
5708
|
+
}
|
|
5709
|
+
function ssh(words) {
|
|
5710
|
+
const args = [];
|
|
5711
|
+
let index = 1;
|
|
5712
|
+
for (; index < words.length; index++) {
|
|
5713
|
+
const word = words[index] ?? "";
|
|
5714
|
+
if (word === "--") {
|
|
5715
|
+
index++;
|
|
5716
|
+
break;
|
|
5717
|
+
}
|
|
5718
|
+
if (!word.startsWith("-")) break;
|
|
5719
|
+
if (word === "--help" || /^-[^-]*[NVGQOW]/u.test(word)) return null;
|
|
5720
|
+
if (/^-[pLlIiFJoObcDEeRSwWQ]/u.test(word)) {
|
|
5721
|
+
const flag = word.slice(0, 2);
|
|
5722
|
+
const value = word.length > 2 ? word.slice(2) : words[++index];
|
|
5723
|
+
if (!value) return null;
|
|
5724
|
+
if (flag === "-p" && /^\d{1,5}$/u.test(value)) args.push(flag, value);
|
|
5725
|
+
if (flag === "-l" && /^[a-zA-Z0-9_.-]+$/u.test(value)) args.push(flag, value);
|
|
5726
|
+
} else if (!/^-[46AaCfKkMnqsTtVvXxYy]+$/u.test(word)) return null;
|
|
5727
|
+
}
|
|
5728
|
+
const host = words[index];
|
|
5729
|
+
if (!host || !validHost(host) || index + 1 >= words.length) return null;
|
|
5730
|
+
return {
|
|
5731
|
+
host,
|
|
5732
|
+
transport: "ssh",
|
|
5733
|
+
args: [...args, host],
|
|
5734
|
+
command: words.slice(index + 1).join(" ")
|
|
5735
|
+
};
|
|
5736
|
+
}
|
|
5737
|
+
function gcloud(words) {
|
|
5738
|
+
if (words[1] !== "compute" || words[2] !== "ssh") return null;
|
|
5739
|
+
let host;
|
|
5740
|
+
let command;
|
|
5741
|
+
const selectors = [];
|
|
5742
|
+
for (let index = 3; index < words.length; index++) {
|
|
5743
|
+
const word = words[index] ?? "";
|
|
5744
|
+
if (word === "--help" || word === "-h" || word === "--dry-run") return null;
|
|
5745
|
+
if (word === "--command" || word.startsWith("--command=")) command = word === "--command" ? words[++index] : word.slice(10);
|
|
5746
|
+
else if (["--zone", "--project"].some((flag) => word === flag || word.startsWith(`${flag}=`))) {
|
|
5747
|
+
const separator = word.indexOf("=");
|
|
5748
|
+
const flag = separator === -1 ? word : word.slice(0, separator);
|
|
5749
|
+
const value = separator === -1 ? words[++index] : word.slice(separator + 1);
|
|
5750
|
+
if (!value || !/^[a-zA-Z0-9_.:-]+$/u.test(value)) return null;
|
|
5751
|
+
selectors.push(`${flag}=${value}`);
|
|
5752
|
+
} else if (word === "--tunnel-through-iap" || word === "--internal-ip") selectors.push(word);
|
|
5753
|
+
else if (word === "--quiet" || word === "-q") continue;
|
|
5754
|
+
else if (!word.startsWith("-") && !host) host = word;
|
|
5755
|
+
else return null;
|
|
5756
|
+
}
|
|
5757
|
+
if (!host || !validHost(host) || !command) return null;
|
|
5758
|
+
return {
|
|
5759
|
+
host,
|
|
5760
|
+
transport: "gcloud",
|
|
5761
|
+
args: [host, ...selectors],
|
|
5762
|
+
command
|
|
5763
|
+
};
|
|
5764
|
+
}
|
|
5765
|
+
/** Matches executables at shell command positions, never mentions in prose or other commands. */
|
|
5766
|
+
function parseRemoteDispatch(command) {
|
|
5767
|
+
return commands$1(command).flatMap((words) => {
|
|
5768
|
+
const executable = posix.basename(words[0] ?? "");
|
|
5769
|
+
const remote = executable === "ssh" ? ssh(words) : executable === "gcloud" ? gcloud(words) : null;
|
|
5770
|
+
if (!remote) return [];
|
|
5771
|
+
return [...new Set(commands$1(remote.command).map(agent))].flatMap((harness) => harness ? [{
|
|
5772
|
+
host: endpoint(remote.host, remote.transport, remote.args),
|
|
5773
|
+
harness,
|
|
5774
|
+
transport: remote.transport,
|
|
5775
|
+
args: remote.args
|
|
5776
|
+
}] : []);
|
|
5777
|
+
});
|
|
5778
|
+
}
|
|
5779
|
+
//#endregion
|
|
5780
|
+
//#region apps/cli/src/remote/scan.ts
|
|
5781
|
+
function object(value) {
|
|
5782
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
5783
|
+
}
|
|
5784
|
+
function timestamp(value) {
|
|
5785
|
+
return typeof value === "string" && Number.isFinite(Date.parse(value)) ? new Date(value).toISOString() : null;
|
|
5786
|
+
}
|
|
5787
|
+
function commands(row) {
|
|
5788
|
+
const payload = row["payload"];
|
|
5789
|
+
if (row["type"] === "response_item" && object(payload) && payload["type"] === "function_call" && typeof payload["name"] === "string" && [
|
|
5790
|
+
"exec_command",
|
|
5791
|
+
"shell_command",
|
|
5792
|
+
"shell"
|
|
5793
|
+
].includes(payload["name"])) {
|
|
5794
|
+
let args = payload["arguments"];
|
|
5795
|
+
if (typeof args === "string") try {
|
|
5796
|
+
args = JSON.parse(args);
|
|
5797
|
+
} catch {
|
|
5798
|
+
return [];
|
|
5799
|
+
}
|
|
5800
|
+
if (!object(args)) return [];
|
|
5801
|
+
const command = args["cmd"] ?? args["command"];
|
|
5802
|
+
return typeof command === "string" ? [{
|
|
5803
|
+
id: typeof payload["call_id"] === "string" ? payload["call_id"] : "",
|
|
5804
|
+
command
|
|
5805
|
+
}] : [];
|
|
5806
|
+
}
|
|
5807
|
+
const message = row["message"];
|
|
5808
|
+
if (row["type"] !== "assistant" || !object(message) || !Array.isArray(message["content"])) return [];
|
|
5809
|
+
return message["content"].flatMap((block) => {
|
|
5810
|
+
if (!object(block) || block["type"] !== "tool_use" || block["name"] !== "Bash" || !object(block["input"]) || typeof block["input"]["command"] !== "string") return [];
|
|
5811
|
+
return [{
|
|
5812
|
+
id: typeof block["id"] === "string" ? block["id"] : "",
|
|
5813
|
+
command: block["input"]["command"]
|
|
5814
|
+
}];
|
|
5815
|
+
});
|
|
5816
|
+
}
|
|
5817
|
+
function resultIds(row) {
|
|
5818
|
+
const payload = row["payload"];
|
|
5819
|
+
if (object(payload) && payload["type"] === "function_call_output" && typeof payload["call_id"] === "string") return [payload["call_id"]];
|
|
5820
|
+
const message = row["message"];
|
|
5821
|
+
if (!object(message) || !Array.isArray(message["content"])) return [];
|
|
5822
|
+
return message["content"].flatMap((block) => object(block) && block["type"] === "tool_result" && typeof block["tool_use_id"] === "string" ? [block["tool_use_id"]] : []);
|
|
5823
|
+
}
|
|
5824
|
+
/** Tool executable positions only. Source bytes stay inside this deterministic CLI scanner. */
|
|
5825
|
+
async function scanRemoteDispatches(sessions, signal) {
|
|
5826
|
+
const windows = [];
|
|
5827
|
+
for (const session of sessions) {
|
|
5828
|
+
const pending = /* @__PURE__ */ new Map();
|
|
5829
|
+
for (const part of session.parts) {
|
|
5830
|
+
signal.throwIfAborted();
|
|
5831
|
+
if (part.representation !== "jsonl") throw new Error("REMOTE_SOURCE_FORMAT_UNSUPPORTED");
|
|
5832
|
+
const file = await open(part.path, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
5833
|
+
try {
|
|
5834
|
+
const before = await file.stat({ bigint: true });
|
|
5835
|
+
if (String(before.dev) !== part.snapshot.dev || String(before.ino) !== part.snapshot.ino || before.size !== BigInt(part.snapshot.size) || String(before.mtimeNs) !== part.snapshot.mtimeNs || String(before.ctimeNs) !== part.snapshot.ctimeNs) throw new Error("REMOTE_SOURCE_CHANGED");
|
|
5836
|
+
if (!part.snapshot.size) continue;
|
|
5837
|
+
const stream = file.createReadStream({
|
|
5838
|
+
autoClose: false,
|
|
5839
|
+
start: 0,
|
|
5840
|
+
end: part.snapshot.size - 1,
|
|
5841
|
+
signal
|
|
5842
|
+
});
|
|
5843
|
+
const lines = createInterface({
|
|
5844
|
+
input: stream,
|
|
5845
|
+
crlfDelay: Infinity
|
|
5846
|
+
});
|
|
5847
|
+
try {
|
|
5848
|
+
for await (const line of lines) {
|
|
5849
|
+
signal.throwIfAborted();
|
|
5850
|
+
if (line.length > 16777216) throw new Error("REMOTE_SOURCE_RECORD_TOO_LARGE");
|
|
5851
|
+
if (!line.trim()) continue;
|
|
5852
|
+
const row = JSON.parse(line);
|
|
5853
|
+
if (!object(row)) continue;
|
|
5854
|
+
const at = timestamp(row["timestamp"]);
|
|
5855
|
+
if (!at) continue;
|
|
5856
|
+
for (const id of resultIds(row)) {
|
|
5857
|
+
for (const window of pending.get(id) ?? []) if (at >= window.start) window.end = at;
|
|
5858
|
+
pending.delete(id);
|
|
5859
|
+
}
|
|
5860
|
+
for (const command of commands(row)) {
|
|
5861
|
+
const end = timestamp(session.lastInteractionAt);
|
|
5862
|
+
const found = parseRemoteDispatch(command.command).map((dispatch) => ({
|
|
5863
|
+
...dispatch,
|
|
5864
|
+
sourceSessionId: session.id,
|
|
5865
|
+
start: at,
|
|
5866
|
+
end: end && end >= at ? end : at
|
|
5867
|
+
}));
|
|
5868
|
+
windows.push(...found);
|
|
5869
|
+
pending.set(command.id, found);
|
|
5870
|
+
}
|
|
5871
|
+
}
|
|
5872
|
+
const after = await file.stat({ bigint: true });
|
|
5873
|
+
if (after.mtimeNs !== before.mtimeNs || after.size !== before.size) throw new Error("REMOTE_SOURCE_CHANGED");
|
|
5874
|
+
} finally {
|
|
5875
|
+
lines.close();
|
|
5876
|
+
stream.destroy();
|
|
5877
|
+
}
|
|
5878
|
+
} finally {
|
|
5879
|
+
await file.close();
|
|
5880
|
+
}
|
|
5881
|
+
}
|
|
5882
|
+
}
|
|
5883
|
+
return windows;
|
|
5884
|
+
}
|
|
5885
|
+
//#endregion
|
|
5886
|
+
//#region apps/cli/src/remote/preflight.ts
|
|
5887
|
+
const inputSchema = z.object({
|
|
5888
|
+
journeyId: z.uuid(),
|
|
5889
|
+
confirmations: z.record(z.string(), z.boolean()).optional()
|
|
5890
|
+
}).strict();
|
|
5891
|
+
function createRemotePreflight(options) {
|
|
5892
|
+
return async (value, signal = new AbortController().signal) => {
|
|
5893
|
+
const input = inputSchema.parse(value);
|
|
5894
|
+
const current = await options.storage.current(input.journeyId);
|
|
5895
|
+
if (!current?.consent) throw new ApplicationError("CONFIRMATION_REQUIRED");
|
|
5896
|
+
if (current.phase === "paused") throw new ApplicationError("OPERATION_CANCELED");
|
|
5897
|
+
if (current.snapshotId) return {
|
|
5898
|
+
hosts: [],
|
|
5899
|
+
copied: 0,
|
|
5900
|
+
failures: [],
|
|
5901
|
+
skipped: "already-frozen"
|
|
5902
|
+
};
|
|
5903
|
+
const lease = await options.execution.tryAcquire("workflow");
|
|
5904
|
+
if (!lease) throw new ApplicationError("DISCOVERY_BUSY");
|
|
5905
|
+
try {
|
|
5906
|
+
const admitted = await options.storage.current(input.journeyId);
|
|
5907
|
+
if (!admitted?.consent || admitted.phase === "paused") throw new ApplicationError("OPERATION_CANCELED");
|
|
5908
|
+
if (admitted.snapshotId) return {
|
|
5909
|
+
hosts: [],
|
|
5910
|
+
copied: 0,
|
|
5911
|
+
failures: [],
|
|
5912
|
+
skipped: "already-frozen"
|
|
5913
|
+
};
|
|
5914
|
+
let state = await readRemoteState(options.stateDir, input.journeyId);
|
|
5915
|
+
if (!state) {
|
|
5916
|
+
const discovered = await options.harnesses.discover(signal);
|
|
5917
|
+
if (!discovered.complete) throw new ApplicationError("SCOPE_NOT_READY");
|
|
5918
|
+
const inventory = filterInventoryByPeriod(discovered, current.period ?? {});
|
|
5919
|
+
const families = reviewFamilies(inventory);
|
|
5920
|
+
const sessions = inventory.sessions.filter((session) => familySelected(inventory, families, session) && !session.warnings.includes("NON_INTERACTIVE_LAUNCH") && session.parts.every((part) => !isRemoteImport(part.path)));
|
|
5921
|
+
state = {
|
|
5922
|
+
windows: [],
|
|
5923
|
+
decisions: {},
|
|
5924
|
+
parents: Object.fromEntries(sessions.map((session) => [session.id, session.sourceKey])),
|
|
5925
|
+
copied: [],
|
|
5926
|
+
failures: []
|
|
5927
|
+
};
|
|
5928
|
+
for (const session of sessions) try {
|
|
5929
|
+
state.windows.push(...await scanRemoteDispatches([session], signal));
|
|
5930
|
+
} catch (error) {
|
|
5931
|
+
signal.throwIfAborted();
|
|
5932
|
+
options.reportFailure(error);
|
|
5933
|
+
state.failures.push("source-scan");
|
|
5934
|
+
}
|
|
5935
|
+
await saveRemoteState(options.stateDir, input.journeyId, state);
|
|
5936
|
+
}
|
|
5937
|
+
const decisions = input.confirmations ?? {};
|
|
5938
|
+
const known = new Set(state.windows.map((window) => window.host));
|
|
5939
|
+
if (Object.keys(decisions).some((host) => !known.has(host))) throw new ApplicationError("INVALID_ARGUMENTS");
|
|
5940
|
+
const fresh = {};
|
|
5941
|
+
for (const [host, allow] of Object.entries(decisions)) {
|
|
5942
|
+
if (Object.hasOwn(state.decisions, host)) continue;
|
|
5943
|
+
fresh[host] = allow;
|
|
5944
|
+
state.decisions[host] = allow;
|
|
5945
|
+
}
|
|
5946
|
+
await saveRemoteState(options.stateDir, input.journeyId, state);
|
|
5947
|
+
if (Object.keys(fresh).length) {
|
|
5948
|
+
const collected = await collectRemoteWorkers(state.windows, fresh, options.home, options.transport, (_host, error) => options.reportFailure(new Error("REMOTE_COLLECTION_FAILED", { cause: error })), options.roots);
|
|
5949
|
+
state.copied.push(...collected.copied);
|
|
5950
|
+
state.failures.push(...collected.failures);
|
|
5951
|
+
await saveRemoteState(options.stateDir, input.journeyId, state);
|
|
5952
|
+
}
|
|
5953
|
+
return result(state);
|
|
5954
|
+
} finally {
|
|
5955
|
+
await lease.release();
|
|
5956
|
+
}
|
|
5957
|
+
};
|
|
5958
|
+
}
|
|
5959
|
+
function result(state) {
|
|
5960
|
+
return {
|
|
5961
|
+
hosts: [...new Set(state.windows.map((window) => window.host))].filter((host) => !Object.hasOwn(state.decisions, host)).map((host) => ({
|
|
5962
|
+
host,
|
|
5963
|
+
windows: state.windows.filter((window) => window.host === host).map(({ start, end }) => ({
|
|
5964
|
+
start,
|
|
5965
|
+
end
|
|
5966
|
+
}))
|
|
5967
|
+
})),
|
|
5968
|
+
copied: state.copied.length,
|
|
5969
|
+
failures: state.failures
|
|
5970
|
+
};
|
|
5971
|
+
}
|
|
5972
|
+
//#endregion
|
|
5637
5973
|
//#region apps/cli/src/journey/confirmed-hides.ts
|
|
5638
5974
|
/** The Work's explicit exception values; an occurrence inside one stays visible (ADR 0069). */
|
|
5639
5975
|
const workExceptions = (state, workId) => state.exceptions.filter((item) => item.workId === workId).map((item) => item.value);
|
|
@@ -7193,22 +7529,6 @@ async function prepareStaticWork(options) {
|
|
|
7193
7529
|
});
|
|
7194
7530
|
}
|
|
7195
7531
|
//#endregion
|
|
7196
|
-
//#region apps/cli/src/journey/directory.ts
|
|
7197
|
-
async function journeyDirectory(stateDir, journeyId) {
|
|
7198
|
-
let path = stateDir;
|
|
7199
|
-
for (const name of ["journeys", journeyId]) {
|
|
7200
|
-
path = join(path, name);
|
|
7201
|
-
try {
|
|
7202
|
-
await makePrivateDirectory(path);
|
|
7203
|
-
} catch (cause) {
|
|
7204
|
-
if (!(cause instanceof Error && "code" in cause && cause.code === "EEXIST")) throw cause;
|
|
7205
|
-
}
|
|
7206
|
-
const stat = await lstat(path, { bigint: true });
|
|
7207
|
-
if (!stat.isDirectory() || stat.isSymbolicLink() || await realpath(path) !== path || (process.platform === "win32" ? !await windowsOwnerOnly(path, "directory") : stat.uid !== BigInt(process.getuid?.() ?? -1) || (stat.mode & 63n) !== 0n)) throw new ApplicationError("UNSAFE_STATE_PATH");
|
|
7208
|
-
}
|
|
7209
|
-
return path;
|
|
7210
|
-
}
|
|
7211
|
-
//#endregion
|
|
7212
7532
|
//#region apps/cli/src/journey/observe.ts
|
|
7213
7533
|
/** Observe whole-journey pause while a single large Work is still being processed. */
|
|
7214
7534
|
function observeJourneyPause(storage, journeyId, processing, onFailure, requireConfirmedScope = false) {
|
|
@@ -7396,7 +7716,7 @@ function createJourneyPreparation(options) {
|
|
|
7396
7716
|
revision: "empty",
|
|
7397
7717
|
terms: []
|
|
7398
7718
|
};
|
|
7399
|
-
const plan = await prepareSourcePlan(storage, options.harnesses, journeyId, terms, abort.signal, current.period);
|
|
7719
|
+
const plan = await prepareSourcePlan(storage, options.harnesses, journeyId, terms, abort.signal, current.period, options.stateDir);
|
|
7400
7720
|
const initial = await storage.current(journeyId);
|
|
7401
7721
|
if (!initial?.consent) throw new ApplicationError("CONFIRMATION_REQUIRED");
|
|
7402
7722
|
const formed = initial.snapshotId ? [] : namedJourneyWorks(formJourneyWorks(plan.approval, options.processing.environment), options.processing, abort.signal);
|
|
@@ -7417,26 +7737,61 @@ function createJourneyPreparation(options) {
|
|
|
7417
7737
|
const frozen = state;
|
|
7418
7738
|
const blockWork = (work, error) => changeJourney(storage, journeyId, "snapshot", (before) => {
|
|
7419
7739
|
if (before.phase === "paused") throw new ApplicationError("OPERATION_CANCELED");
|
|
7740
|
+
if (before.phase === "sending" || before.phase === "completed") return before;
|
|
7420
7741
|
return withReadingProgress({
|
|
7421
7742
|
...before,
|
|
7422
|
-
works: before.works.map((item) => item.id === work.id && item.inputRevision === work.inputRevision ? {
|
|
7743
|
+
works: before.works.map((item) => item.id === work.id && item.inputRevision === work.inputRevision && !item.blockedCodes.includes("PRIVACY_CHANGE_PENDING") ? {
|
|
7423
7744
|
...item,
|
|
7424
7745
|
status: "blocked",
|
|
7425
7746
|
blockedCodes: blockedCodes(error, safeCode(error))
|
|
7426
7747
|
} : item)
|
|
7427
7748
|
});
|
|
7428
7749
|
}, 100);
|
|
7429
|
-
|
|
7430
|
-
|
|
7431
|
-
let
|
|
7432
|
-
const
|
|
7433
|
-
|
|
7434
|
-
|
|
7435
|
-
|
|
7436
|
-
|
|
7437
|
-
|
|
7438
|
-
|
|
7750
|
+
const retryLimit = options.retryLimit ?? workRetryDelaysMs.length;
|
|
7751
|
+
const generation = (work) => `${work.id}:${work.inputRevision}`;
|
|
7752
|
+
let serialAdmission = Promise.resolve();
|
|
7753
|
+
const acquireRead = async () => {
|
|
7754
|
+
if (run.pool) return () => {};
|
|
7755
|
+
const previous = serialAdmission;
|
|
7756
|
+
const released = Promise.withResolvers();
|
|
7757
|
+
serialAdmission = released.promise;
|
|
7758
|
+
await previous;
|
|
7759
|
+
return () => released.resolve();
|
|
7439
7760
|
};
|
|
7761
|
+
const releaseAdmission = /* @__PURE__ */ new Map();
|
|
7762
|
+
const retryWork = async (work, error, attempt) => {
|
|
7763
|
+
if (blockedCodes(error, safeCode(error)).includes("PRIVACY_CHANGE_PENDING") || work.blockedCodes.includes("PRIVACY_CHANGE_PENDING")) return false;
|
|
7764
|
+
const delay = workRetryDelaysMs[attempt];
|
|
7765
|
+
if (attempt >= retryLimit || delay === void 0) return false;
|
|
7766
|
+
const blocked = await blockWork(work, error);
|
|
7767
|
+
const target = blocked.works.find((item) => item.id === work.id);
|
|
7768
|
+
if (blocked.phase === "sending" || blocked.phase === "completed" || target?.blockedCodes.includes("PRIVACY_CHANGE_PENDING")) return false;
|
|
7769
|
+
releaseAdmission.get(work.id)?.();
|
|
7770
|
+
await (options.retryDelay ?? waitForWorkRetry)(delay, abort.signal);
|
|
7771
|
+
abort.signal.throwIfAborted();
|
|
7772
|
+
let resumed = false;
|
|
7773
|
+
await changeJourney(storage, journeyId, "snapshot", (before) => {
|
|
7774
|
+
resumed = false;
|
|
7775
|
+
const target = before.works.find((item) => item.id === work.id);
|
|
7776
|
+
if (before.phase === "sending" || before.phase === "completed" || before.phase === "paused" || !target || !isSelectedWork(target) || target.inputRevision !== work.inputRevision || target.status !== "blocked" || target.blockedCodes.includes("PRIVACY_CHANGE_PENDING")) return before;
|
|
7777
|
+
resumed = !before.pendingHideId;
|
|
7778
|
+
return withReadingProgress({
|
|
7779
|
+
...before,
|
|
7780
|
+
privacy: {
|
|
7781
|
+
...before.privacy,
|
|
7782
|
+
packageApproval: null
|
|
7783
|
+
},
|
|
7784
|
+
works: before.works.map((item) => item.id === work.id ? {
|
|
7785
|
+
...item,
|
|
7786
|
+
status: resumed ? work.status : "pending",
|
|
7787
|
+
blockedCodes: []
|
|
7788
|
+
} : item)
|
|
7789
|
+
});
|
|
7790
|
+
}, 100);
|
|
7791
|
+
return resumed;
|
|
7792
|
+
};
|
|
7793
|
+
const readRetries = /* @__PURE__ */ new Map();
|
|
7794
|
+
const staticRetries = /* @__PURE__ */ new Map();
|
|
7440
7795
|
/** A failed isolation leaves the Work blocked as before; cancellation and contention escape. */
|
|
7441
7796
|
const isolateRoots = async (work, error) => {
|
|
7442
7797
|
try {
|
|
@@ -7458,19 +7813,19 @@ function createJourneyPreparation(options) {
|
|
|
7458
7813
|
/** Scan generations this run prepared, including those its own isolation started. */
|
|
7459
7814
|
const prepared = /* @__PURE__ */ new Set();
|
|
7460
7815
|
/** True when failing roots left the Work, which is then prepared again (ADR 0188). */
|
|
7461
|
-
const
|
|
7816
|
+
const prepareWorkAttempt = async (planned, onSubmitted, releaseRead) => {
|
|
7462
7817
|
abort.signal.throwIfAborted();
|
|
7463
7818
|
const latest = await storage.current(journeyId) ?? frozen;
|
|
7464
7819
|
abort.signal.throwIfAborted();
|
|
7465
7820
|
if (latest.phase === "paused") throw new ApplicationError("OPERATION_CANCELED");
|
|
7466
7821
|
let work = latest.works.find((item) => item.id === planned.id);
|
|
7467
|
-
if (!work || !isSelectedWork(work)) return false;
|
|
7822
|
+
if (!work || !isSelectedWork(work) || latest.phase === "sending" || latest.phase === "completed") return false;
|
|
7468
7823
|
if (![
|
|
7469
7824
|
"pending",
|
|
7470
7825
|
"reading",
|
|
7471
7826
|
"review"
|
|
7472
7827
|
].includes(work.status)) return false;
|
|
7473
|
-
prepared.add(
|
|
7828
|
+
prepared.add(generation(work));
|
|
7474
7829
|
await prepareClassificationMetadata(storage, latest, work, options.processing, abort.signal);
|
|
7475
7830
|
let failures = collectPreparationFailures(plan.approval.inventory, work.rootSessionIds, {
|
|
7476
7831
|
journeyId,
|
|
@@ -7504,11 +7859,36 @@ function createJourneyPreparation(options) {
|
|
|
7504
7859
|
if (await droppedMidScan(planned.id)) return false;
|
|
7505
7860
|
throw error;
|
|
7506
7861
|
}
|
|
7507
|
-
|
|
7862
|
+
onSubmitted();
|
|
7863
|
+
releaseRead();
|
|
7864
|
+
const attempt = work ? readRetries.get(generation(work)) ?? 0 : 0;
|
|
7865
|
+
if (work && await retryWork(work, error, attempt)) {
|
|
7866
|
+
readRetries.set(generation(work), attempt + 1);
|
|
7867
|
+
return prepareWorkOnce(planned, onSubmitted);
|
|
7868
|
+
}
|
|
7869
|
+
const now = await storage.current(journeyId);
|
|
7870
|
+
if (now?.phase === "sending" || now?.phase === "completed") return false;
|
|
7871
|
+
if (blockedCodes(error, code).includes("PRIVACY_CHANGE_PENDING")) {
|
|
7872
|
+
if (work) await blockWork(work, error);
|
|
7873
|
+
return false;
|
|
7874
|
+
}
|
|
7875
|
+
if (attempt < retryLimit) return false;
|
|
7876
|
+
if (work && await isolateRoots(work, error)) {
|
|
7877
|
+
readRetries.delete(generation(work));
|
|
7878
|
+
return true;
|
|
7879
|
+
}
|
|
7508
7880
|
if (work) await blockWork(work, error);
|
|
7509
7881
|
}
|
|
7510
7882
|
return false;
|
|
7511
7883
|
};
|
|
7884
|
+
const prepareWorkOnce = async (planned, onSubmitted) => {
|
|
7885
|
+
const releaseRead = await acquireRead();
|
|
7886
|
+
try {
|
|
7887
|
+
return await prepareWorkAttempt(planned, onSubmitted, releaseRead);
|
|
7888
|
+
} finally {
|
|
7889
|
+
releaseRead();
|
|
7890
|
+
}
|
|
7891
|
+
};
|
|
7512
7892
|
const prepareWork = async (planned, onSubmitted) => {
|
|
7513
7893
|
for (let attempt = 0; attempt < planned.rootSessionIds.length; attempt++) if (!await prepareWorkOnce(planned, onSubmitted)) return;
|
|
7514
7894
|
};
|
|
@@ -7520,7 +7900,8 @@ function createJourneyPreparation(options) {
|
|
|
7520
7900
|
abort.signal.throwIfAborted();
|
|
7521
7901
|
if (!latest?.consent || latest.phase === "paused" || latest.scope.status !== "confirmed") throw new ApplicationError("OPERATION_CANCELED");
|
|
7522
7902
|
const work = latest.works.find((item) => item.id === planned.id);
|
|
7523
|
-
if (!work || !isSelectedWork(work)) return;
|
|
7903
|
+
if (!work || !isSelectedWork(work) || latest.phase === "sending" || latest.phase === "completed") return;
|
|
7904
|
+
const attempt = staticRetries.get(generation(work)) ?? 0;
|
|
7524
7905
|
try {
|
|
7525
7906
|
await inBackgroundStoreLane(() => prepareStaticWork({
|
|
7526
7907
|
storage,
|
|
@@ -7538,38 +7919,63 @@ function createJourneyPreparation(options) {
|
|
|
7538
7919
|
if (code === "OPERATION_CANCELED" && await droppedMidScan(work.id)) return;
|
|
7539
7920
|
if (code === "COORDINATION_BUSY" || code === "OPERATION_CANCELED") throw error;
|
|
7540
7921
|
options.onFailure(error);
|
|
7541
|
-
await
|
|
7922
|
+
if (await retryWork(work, error, attempt)) {
|
|
7923
|
+
staticRetries.set(generation(work), attempt + 1);
|
|
7924
|
+
return staticWork(planned, passSubmitted);
|
|
7925
|
+
}
|
|
7926
|
+
const now = await storage.current(journeyId);
|
|
7927
|
+
if (now?.phase !== "sending" && now?.phase !== "completed" && (attempt >= retryLimit || blockedCodes(error, safeCode(error)).includes("PRIVACY_CHANGE_PENDING"))) await blockWork(work, error);
|
|
7542
7928
|
}
|
|
7543
7929
|
};
|
|
7544
|
-
let next = 0;
|
|
7545
7930
|
let fatal;
|
|
7546
7931
|
const stop = (error) => {
|
|
7547
7932
|
fatal ??= { error };
|
|
7548
7933
|
abort.abort(error);
|
|
7549
7934
|
};
|
|
7550
7935
|
const statics = [];
|
|
7551
|
-
const
|
|
7552
|
-
|
|
7553
|
-
|
|
7554
|
-
|
|
7555
|
-
|
|
7936
|
+
const reads = [];
|
|
7937
|
+
const pass = async (works) => {
|
|
7938
|
+
let submittedWorks = 0;
|
|
7939
|
+
const submitted = Promise.withResolvers();
|
|
7940
|
+
const submission = () => {
|
|
7941
|
+
let done = false;
|
|
7942
|
+
return () => {
|
|
7943
|
+
if (done) return;
|
|
7944
|
+
done = true;
|
|
7945
|
+
if (++submittedWorks === works.length) submitted.resolve();
|
|
7946
|
+
};
|
|
7947
|
+
};
|
|
7556
7948
|
if (!works.length) submitted.resolve();
|
|
7557
7949
|
const passSubmitted = submitted.promise;
|
|
7558
|
-
|
|
7559
|
-
|
|
7560
|
-
|
|
7561
|
-
|
|
7950
|
+
let admission = Promise.resolve();
|
|
7951
|
+
const admissions = [];
|
|
7952
|
+
for (const planned of works) {
|
|
7953
|
+
const previous = admission;
|
|
7954
|
+
const released = Promise.withResolvers();
|
|
7955
|
+
admission = released.promise;
|
|
7956
|
+
admissions.push(admission);
|
|
7957
|
+
releaseAdmission.set(planned.id, () => released.resolve());
|
|
7958
|
+
const onSubmitted = submission();
|
|
7959
|
+
const read = async () => {
|
|
7960
|
+
if (!run.pool) await previous;
|
|
7961
|
+
if (fatal) {
|
|
7962
|
+
onSubmitted();
|
|
7963
|
+
released.resolve();
|
|
7964
|
+
return;
|
|
7965
|
+
}
|
|
7562
7966
|
try {
|
|
7563
7967
|
await retryContention(() => prepareWork(planned, onSubmitted), (error) => options.onFailure(error), abort.signal);
|
|
7564
7968
|
} catch (error) {
|
|
7565
7969
|
stop(error);
|
|
7566
7970
|
} finally {
|
|
7567
7971
|
onSubmitted();
|
|
7972
|
+
released.resolve();
|
|
7568
7973
|
}
|
|
7569
7974
|
statics.push(retryContention(() => staticWork(planned, passSubmitted), (error) => options.onFailure(error), abort.signal).catch(stop));
|
|
7570
|
-
}
|
|
7571
|
-
|
|
7572
|
-
|
|
7975
|
+
};
|
|
7976
|
+
reads.push(read());
|
|
7977
|
+
}
|
|
7978
|
+
await Promise.all(admissions);
|
|
7573
7979
|
};
|
|
7574
7980
|
/** Selected Works this run has not prepared yet, waiting while grouping continues (E2). */
|
|
7575
7981
|
const later = async () => {
|
|
@@ -7586,11 +7992,15 @@ function createJourneyPreparation(options) {
|
|
|
7586
7992
|
await setTimeout$1(laterWorksPollMs, void 0, { signal: abort.signal }).catch(() => abort.signal.throwIfAborted());
|
|
7587
7993
|
}
|
|
7588
7994
|
};
|
|
7589
|
-
|
|
7590
|
-
|
|
7591
|
-
|
|
7995
|
+
try {
|
|
7996
|
+
for (let batch = scanWorks; batch.length && !fatal; batch = await later()) {
|
|
7997
|
+
if (!run.workers && !run.pool) run = await runWorkers(options.workers);
|
|
7998
|
+
await pass(batch);
|
|
7999
|
+
}
|
|
8000
|
+
} finally {
|
|
8001
|
+
await Promise.all(reads);
|
|
8002
|
+
await Promise.all(statics);
|
|
7592
8003
|
}
|
|
7593
|
-
await Promise.all(statics);
|
|
7594
8004
|
if (scanWorks.length) options.onTiming?.(journeyId, {
|
|
7595
8005
|
scope: "preparation",
|
|
7596
8006
|
phases: { "journey-prepare": Math.round(performance$1.now() - startedAt) },
|
|
@@ -7616,6 +8026,11 @@ function createJourneyPreparation(options) {
|
|
|
7616
8026
|
}
|
|
7617
8027
|
};
|
|
7618
8028
|
}
|
|
8029
|
+
/** Ray, 2026-11-05: two automatic retries per Work, after two then five seconds. */
|
|
8030
|
+
const workRetryDelaysMs = [2e3, 5e3];
|
|
8031
|
+
async function waitForWorkRetry(milliseconds, signal) {
|
|
8032
|
+
await setTimeout$1(milliseconds, void 0, { signal }).catch(() => signal.throwIfAborted());
|
|
8033
|
+
}
|
|
7619
8034
|
/** How often a run waiting for grouping to publish more Works checks the journey (E2). */
|
|
7620
8035
|
const laterWorksPollMs = 2e3;
|
|
7621
8036
|
/**
|
|
@@ -8396,7 +8811,7 @@ function createJourneyRenderer(options) {
|
|
|
8396
8811
|
return {
|
|
8397
8812
|
...current,
|
|
8398
8813
|
works,
|
|
8399
|
-
phase: current.phase === "paused" ? "paused" : current.readingComplete && works.every((item) => !
|
|
8814
|
+
phase: current.phase === "paused" ? "paused" : current.readingComplete && works.every((item) => !isIncludedWork(item) || item.status === "ready") ? "ready" : current.phase
|
|
8400
8815
|
};
|
|
8401
8816
|
});
|
|
8402
8817
|
}
|
|
@@ -10374,6 +10789,22 @@ function createJourneyOwnerClient(options) {
|
|
|
10374
10789
|
const { headers } = await requestWithHeaders("/me/profile", publicProfileSchema, signal);
|
|
10375
10790
|
return { parallelCompletes: (headers.get("passport-capabilities") ?? "").split(",").some((token) => token.trim() === "parallel-completes") };
|
|
10376
10791
|
},
|
|
10792
|
+
readProfile(signal) {
|
|
10793
|
+
return request("/me/profile", ownerProfileSchema, signal, void 0, "GET");
|
|
10794
|
+
},
|
|
10795
|
+
async saveProfile(profile, signal) {
|
|
10796
|
+
await request("/me/profile", ownerProfileSchema, signal, profile, "PUT");
|
|
10797
|
+
},
|
|
10798
|
+
async addressTaken(address, signal) {
|
|
10799
|
+
const path = `/addresses/${encodeURIComponent(passportAddressSchema.parse(address))}`;
|
|
10800
|
+
try {
|
|
10801
|
+
await request(path, z.object({ publicId: z.string() }).strict(), signal, void 0, "GET");
|
|
10802
|
+
return true;
|
|
10803
|
+
} catch (error) {
|
|
10804
|
+
if (error instanceof JourneyRemoteError && error.status === 404) return false;
|
|
10805
|
+
throw error;
|
|
10806
|
+
}
|
|
10807
|
+
},
|
|
10377
10808
|
resolve(input, signal) {
|
|
10378
10809
|
return request("/me/work-bundles/resolve", sendResolutionSchema.refine((value) => value.logicalWorkId === input.logicalWorkId), signal, input);
|
|
10379
10810
|
},
|
|
@@ -10963,7 +11394,13 @@ function resumeState(send) {
|
|
|
10963
11394
|
status: "sign-in",
|
|
10964
11395
|
error: null
|
|
10965
11396
|
};
|
|
10966
|
-
const profileRequired = ["paused", "sign-in"].includes(send.status) || send.subject === null || [
|
|
11397
|
+
const profileRequired = ["paused", "sign-in"].includes(send.status) || send.subject === null || [
|
|
11398
|
+
"ACCOUNT_PROFILE_UNAVAILABLE",
|
|
11399
|
+
"ACCOUNT_CHANGED",
|
|
11400
|
+
"PASSPORT_ADDRESS_TAKEN",
|
|
11401
|
+
"PASSPORT_ADDRESS_RESERVED",
|
|
11402
|
+
"PASSPORT_ADDRESS_LIMIT"
|
|
11403
|
+
].includes(send.error ?? "") || send.status === "blocked" && send.items.some((item) => [
|
|
10967
11404
|
"ready",
|
|
10968
11405
|
"uploading",
|
|
10969
11406
|
"assessing",
|
|
@@ -11047,7 +11484,42 @@ async function prepareProfile(dependencies, snapshot, connection, signal) {
|
|
|
11047
11484
|
assertScope(latest.journey, latest.send);
|
|
11048
11485
|
signal.throwIfAborted();
|
|
11049
11486
|
}
|
|
11050
|
-
|
|
11487
|
+
const captured = await captureProfile(connection, guard, signal, dependencies.failure);
|
|
11488
|
+
const chosen = snapshot.journey.profile;
|
|
11489
|
+
if (captured.status !== "sending" || !chosen || !profileHasChoices(chosen)) return captured;
|
|
11490
|
+
return writeProfile(connection, chosen, guard, signal, dependencies.failure, captured);
|
|
11491
|
+
}
|
|
11492
|
+
/** Passport's refusal of the chosen address, as the code that re-shows the address card. */
|
|
11493
|
+
const addressRefusals = {
|
|
11494
|
+
address_taken: "PASSPORT_ADDRESS_TAKEN",
|
|
11495
|
+
address_reserved: "PASSPORT_ADDRESS_RESERVED",
|
|
11496
|
+
address_limit_reached: "PASSPORT_ADDRESS_LIMIT"
|
|
11497
|
+
};
|
|
11498
|
+
/**
|
|
11499
|
+
* The Journey's address and links over the owner's current settings, saved before the first
|
|
11500
|
+
* Work upload (ADR 0246). A refused address blocks the Send until the person keeps another.
|
|
11501
|
+
*/
|
|
11502
|
+
async function writeProfile(connection, chosen, guard, signal, failure, captured) {
|
|
11503
|
+
try {
|
|
11504
|
+
const current = await connection.readProfile(signal);
|
|
11505
|
+
await guard();
|
|
11506
|
+
await connection.saveProfile(mergeOwnerProfile(current.profile, chosen), signal);
|
|
11507
|
+
await guard();
|
|
11508
|
+
return captured;
|
|
11509
|
+
} catch (error) {
|
|
11510
|
+
if (signal.aborted) throw error;
|
|
11511
|
+
if (error instanceof JourneyRemoteError && error.status === 409 && addressRefusals[error.code]) return {
|
|
11512
|
+
status: "blocked",
|
|
11513
|
+
error: addressRefusals[error.code] ?? null
|
|
11514
|
+
};
|
|
11515
|
+
if (error instanceof ApplicationError) throw error;
|
|
11516
|
+
const unavailable = new ApplicationError("ACCOUNT_PROFILE_UNAVAILABLE", void 0, { cause: error });
|
|
11517
|
+
failure(unavailable.code, unavailable);
|
|
11518
|
+
return {
|
|
11519
|
+
status: "blocked",
|
|
11520
|
+
error: unavailable.code
|
|
11521
|
+
};
|
|
11522
|
+
}
|
|
11051
11523
|
}
|
|
11052
11524
|
/** Shared by native Send and Manual upload; `guard` rechecks the caller's own scope. */
|
|
11053
11525
|
async function captureProfile(connection, guard, signal, failure) {
|
|
@@ -12951,6 +13423,45 @@ function createJourneySender(dependencies, phases = 3) {
|
|
|
12951
13423
|
get(address) {
|
|
12952
13424
|
return dependencies.storage.loadSend(address.journeyId, address.sendId);
|
|
12953
13425
|
},
|
|
13426
|
+
/** The owner's current address, links and account email; null while signed out. */
|
|
13427
|
+
async ownerProfile(signal) {
|
|
13428
|
+
const connection = await dependencies.auth.connect(signal);
|
|
13429
|
+
if (!connection) return null;
|
|
13430
|
+
const { profile, accountEmail } = await connection.readProfile(signal);
|
|
13431
|
+
return {
|
|
13432
|
+
address: profile.address,
|
|
13433
|
+
links: profile.links,
|
|
13434
|
+
accountEmail
|
|
13435
|
+
};
|
|
13436
|
+
},
|
|
13437
|
+
/** A best-effort availability check for the address card; Send's save decides. */
|
|
13438
|
+
async addressTaken(input, signal) {
|
|
13439
|
+
const address = passportAddressSchema.parse(input.address);
|
|
13440
|
+
const connection = await dependencies.auth.connect(signal);
|
|
13441
|
+
if (!connection) return null;
|
|
13442
|
+
return { taken: await connection.addressTaken(address, signal) };
|
|
13443
|
+
},
|
|
13444
|
+
/**
|
|
13445
|
+
* The address or links chosen on the Sent page, after the Send reached Passport (ADR 0246,
|
|
13446
|
+
* BL-230): an explicit Keep or Add saves them at once over the current settings.
|
|
13447
|
+
*/
|
|
13448
|
+
async saveSentProfile(input, signal) {
|
|
13449
|
+
const latest = requireSend(await dependencies.storage.loadSend(input.journeyId));
|
|
13450
|
+
const chosen = latest.journey.profile;
|
|
13451
|
+
if (latest.send.status !== "completed" || !chosen || !profileHasChoices(chosen)) throw new ApplicationError("INVALID_TRANSITION");
|
|
13452
|
+
const connection = await dependencies.auth.connect(signal);
|
|
13453
|
+
if (!connection) throw new ApplicationError("ACCOUNT_SIGNED_OUT");
|
|
13454
|
+
if (latest.send.subject !== connection.subject) throw new ApplicationError("IDENTITY_REJECTED");
|
|
13455
|
+
try {
|
|
13456
|
+
const current = await connection.readProfile(signal);
|
|
13457
|
+
await connection.saveProfile(mergeOwnerProfile(current.profile, chosen), signal);
|
|
13458
|
+
} catch (error) {
|
|
13459
|
+
if (signal.aborted || error instanceof ApplicationError) throw error;
|
|
13460
|
+
const code = error instanceof JourneyRemoteError && error.status === 409 ? addressRefusals[error.code] : void 0;
|
|
13461
|
+
if (code) throw new ApplicationError(code);
|
|
13462
|
+
throw new ApplicationError("ACCOUNT_PROFILE_UNAVAILABLE", void 0, { cause: error });
|
|
13463
|
+
}
|
|
13464
|
+
},
|
|
12954
13465
|
async prepare(raw, signal) {
|
|
12955
13466
|
const input = sendPrepareSchema.parse(raw);
|
|
12956
13467
|
const snapshot = await dependencies.storage.loadSend(input.journeyId);
|
|
@@ -14986,6 +15497,18 @@ async function compose(options, callbacks, bundles) {
|
|
|
14986
15497
|
})();
|
|
14987
15498
|
return {
|
|
14988
15499
|
application,
|
|
15500
|
+
remoteWorkers: createRemotePreflight({
|
|
15501
|
+
storage: journeyStorage,
|
|
15502
|
+
harnesses,
|
|
15503
|
+
execution,
|
|
15504
|
+
stateDir,
|
|
15505
|
+
home: homedir(),
|
|
15506
|
+
roots: {
|
|
15507
|
+
codexHome: options.codexHome,
|
|
15508
|
+
claudeHome: options.claudeHome
|
|
15509
|
+
},
|
|
15510
|
+
reportFailure: (error) => diagnostics.onFailure("remote-workers", error)
|
|
15511
|
+
}),
|
|
14989
15512
|
nativeInspection: {
|
|
14990
15513
|
originals: createOriginalView(journeyStorage, stateDir),
|
|
14991
15514
|
output: createOutputView(journeyStorage, processing),
|
|
@@ -15090,6 +15613,7 @@ async function invoke(runtime, command, value, signal) {
|
|
|
15090
15613
|
const app = runtime.application;
|
|
15091
15614
|
markAgentPresence(runtime.stateDir, value);
|
|
15092
15615
|
switch (command) {
|
|
15616
|
+
case "journey.remoteWorkers": return runtime.remoteWorkers(value, signal);
|
|
15093
15617
|
case "send.get": return app.journeySend?.get(sendAddress.partial({ sendId: true }).parse(value));
|
|
15094
15618
|
case "send.prepare": return app.journeySend?.prepare(sendPrepareSchema.parse(value));
|
|
15095
15619
|
case "send.start": return app.journeySend?.start(sendStartSchema.parse(value));
|