@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.
Files changed (41) hide show
  1. package/dist/{agent-setup-Bpsi-BCv.mjs → agent-setup-Dv_mQ38P.mjs} +2 -2
  2. package/dist/agent-setup-worker.mjs +5 -5
  3. package/dist/cli.mjs +6 -5
  4. package/dist/codex-title-worker.mjs +1 -1
  5. package/dist/configured-preview-worker.mjs +5 -5
  6. package/dist/configured-session-worker.mjs +5 -5
  7. package/dist/{frame-IeDOLy1o.mjs → frame-DGun31Uc.mjs} +1 -1
  8. package/dist/{heap-DiOlSa8T.mjs → heap-DY5n4EVC.mjs} +5 -4
  9. package/dist/inspection-worker.mjs +4 -4
  10. package/dist/{job-BII94L2c.mjs → job-BxUVFocm.mjs} +383 -8
  11. package/dist/{locate-CBiw-CJK.mjs → locate-JYtFmKu-.mjs} +115 -3
  12. package/dist/{model-DGTsARtV.mjs → model-C1VzgKLi.mjs} +1 -1
  13. package/dist/operation-executor.mjs +3 -3
  14. package/dist/preview-worker.mjs +1 -1
  15. package/dist/privacy-validator.mjs +1 -1
  16. package/dist/{query-CVqWNFYf.mjs → query-Cm5lumS4.mjs} +588 -64
  17. package/dist/{run-BGlf-W0M.mjs → run-hQ_W2p_Y.mjs} +4 -4
  18. package/dist/{src-CBTvOa44.mjs → src--Ln5GHdz.mjs} +44 -9
  19. package/dist/{src-D4nrfC2S.mjs → src-BMSLVcgs.mjs} +6 -5
  20. package/dist/{src-37kIclGK.mjs → src-CGFwLUd6.mjs} +4 -4
  21. package/dist/store-database-worker.mjs +1 -1
  22. package/dist/web/assets/index-Bbck2aOB.css +1 -0
  23. package/dist/web/assets/index-CXm08nyc.js +69 -0
  24. package/dist/web/index.html +2 -2
  25. package/package.json +1 -1
  26. package/skills/tacitt/SKILL.md +4 -4
  27. package/skills/tacitt/references/INDEX.md +11 -10
  28. package/skills/tacitt/references/commands.md +25 -12
  29. package/skills/tacitt/references/journey.md +3 -3
  30. package/skills/tacitt/references/remote-workers.md +46 -0
  31. package/skills/tacitt/references/send.md +5 -1
  32. package/skills/tacitt/scripts/INDEX.md +36 -36
  33. package/skills/tacitt/scripts/journey-scan-timing.mjs +5 -4
  34. package/skills/tacitt/scripts/journey-state.d.mts +9 -0
  35. package/skills/tacitt/scripts/journey-state.mjs +19 -2
  36. package/skills/tacitt/scripts/journey.d.mts +4 -0
  37. package/skills/tacitt/scripts/journey.mjs +4 -2
  38. package/skills/tacitt/scripts/privacy-validator.mjs +76 -3
  39. package/skills/tacitt/scripts/review.mjs +6 -6
  40. package/dist/web/assets/index-D3g_ATU-.js +0 -69
  41. 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-D4nrfC2S.mjs";
2
- import { Bt as sessionPeriodSchema, Dn as entityDictionaryVersion, F as bClassificationKey, Fn as frameRecords, Ft as termTypeInfo, Gt as safeField, H as codePointLength, Ht as ApplicationError, Jt as reviewFamilies, K as utf16Offset, N as bUnitFragments, Ot as privacyTermKey, Qn as admitGitName, T as bLiteralTerms, Ut as UnfinishedJourneyError, V as bDigest, Wt as safeCode, Xn as createEntityMatcher, Zn as admitCustomTerm, Zt as observeSource, b as validateBReview, c as textDigest, h as reviewFindingsDigest, ht as journeyDescribeSchema, jt as reviewTermsInputSchema, l as validateReview, mn as currentDetectorVersion, n as compileBFindings, r as reviewBatchChunks, s as reviewSubmitSchema, t as locateTerms, yn as createStaticProcessor, z as bAnchorKey } from "./locate-CBiw-CJK.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-37kIclGK.mjs";
4
- import { $ as settingsQuerySchema, $r as packageCommandSchema, 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, Gr as withReadingProgress, Hr as isReadingComplete, Ht as journeyContextQuerySchema, Ir as sendReceiptSchema, Jn as reviewCandidateResourceSchema, Jr as journeyFindingsSchema, 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 commandSchema, 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 isSelectedWork, Vn as maskedHitsStale, Vr as isClassifying, Vt as reviewCompileSchema, Wn as workDecisionDigest, Xn as reviewRepairResourceSchema, Xr as journeyRenderInputSchema, Xt as journeyPolicyRespondSchema, Yn as reviewRepairInputSchema, Yr as journeyPreviewSchema, Yt as searchScopeDigest, Zr as journeyRenderSchema, _t as maximumTermsFileBytes, a as bCrossSegmentReviewSchema, ar as scanGitSeedsId, b as bReviewScope, bn as confirmedHideAdditions, br as journeyClassificationSessionsSchema, bt as sendActionSchema, c as reconstructBFields, cr as scanExclusionResourceSchema, ct as validateFailureLocation, d as selectedValueMembers, dn as sessionReviewInputSchema, dr as journeyInventoryQuerySchema, dt as manualUploadChoiceSchema, ei as selectionBatchSchema, 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, ii as stateSchema, ir as scanSessionResourceSchema, j as profileUrlMatchesIdentity, k as deviceAuthorizationSchema, ln as sessionRereadInputSchema, lt as manualUploadActionSchema, m as hideScopeDigest, mt as manualUploadStartSchema, n as scanBatchEquivalenceDigest, nt as appendSensitiveTerms, o as bRereadDigest, oi as recommendParallelism, on as sessionReconcileInputSchema, or as scanGitSeedsResource, ot as artifactFailureDetailSchema, p as activeJourneyTerms, pt as manualUploadSchema, qn as reviewCandidateInputSchema, qr as journeyFindingImpactSchema, qt as journeySearchResultSchema, r as locateTermEvidence, ri as producerVersion, rn as reviewAttemptResourceSchema, rr as scanPreparationDigest, s as reconcileBSession, sr as verifiedScanGitSeeds, st as resolveFailureLocation, t as scanBatchContextDigest, ti as selectionSchema, 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-CBTvOa44.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-DiOlSa8T.mjs";
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-BII94L2c.mjs";
7
- import { t as createAgentSetupPort } from "./agent-setup-Bpsi-BCv.mjs";
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
- let submittedWorks = 0;
7430
- let works = [];
7431
- let submitted = Promise.withResolvers();
7432
- const submission = () => {
7433
- let done = false;
7434
- return () => {
7435
- if (done) return;
7436
- done = true;
7437
- if (++submittedWorks === works.length) submitted.resolve();
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 prepareWorkOnce = async (planned, onSubmitted) => {
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(`${work.id}:${work.inputRevision}`);
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
- if (work && await isolateRoots(work, error)) return true;
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 blockWork(work, error);
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 pass = async (batch) => {
7552
- works = batch;
7553
- next = 0;
7554
- submittedWorks = 0;
7555
- submitted = Promise.withResolvers();
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
- await Promise.all(Array.from({ length: run.pool ? works.length : Math.min(1, works.length) }, async () => {
7559
- for (let work = works[next++]; work && !fatal; work = works[next++]) {
7560
- const planned = work;
7561
- const onSubmitted = submission();
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
- while (submittedWorks < works.length) submission()();
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
- for (let batch = scanWorks; batch.length && !fatal; batch = await later()) {
7590
- if (!run.workers && !run.pool) run = await runWorkers(options.workers);
7591
- await pass(batch);
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) => !isSelectedWork(item) || item.status === "ready") ? "ready" : current.phase
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 || ["ACCOUNT_PROFILE_UNAVAILABLE", "ACCOUNT_CHANGED"].includes(send.error ?? "") || send.status === "blocked" && send.items.some((item) => [
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
- return captureProfile(connection, guard, signal, dependencies.failure);
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));