@gitroll/tacitt-cli 0.6.12 → 0.6.13

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 (38) hide show
  1. package/dist/{agent-setup-B12VbvN_.mjs → agent-setup-Doy_AnPY.mjs} +2 -2
  2. package/dist/agent-setup-worker.mjs +5 -5
  3. package/dist/cli.mjs +10 -10
  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-B3He21G8.mjs → frame-BPbypzmt.mjs} +1 -1
  8. package/dist/{heap-Blrat--4.mjs → heap-DCS_WXIA.mjs} +7 -3
  9. package/dist/inspection-worker.mjs +4 -4
  10. package/dist/{job-CBuIMTQ0.mjs → job--zxwgaPy.mjs} +135 -36
  11. package/dist/{locate-D-ZaHpI0.mjs → locate-PLEyLYW-.mjs} +287 -236
  12. package/dist/{model-DR9odhWP.mjs → model-CyXvsWLz.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-BYDcNKy2.mjs → query-BC7MIaWc.mjs} +335 -115
  17. package/dist/{run-DQ9xyKSt.mjs → run-Dod7Vykp.mjs} +6 -6
  18. package/dist/{src-DeNJ8vOD.mjs → src-CP-K2PBv.mjs} +1779 -1686
  19. package/dist/{src-Do-qzuMn.mjs → src-CttNuxOn.mjs} +590 -542
  20. package/dist/{src-CKQlTA2Z.mjs → src-dL6y2mcu.mjs} +180 -30
  21. package/dist/store-database-worker.mjs +1 -1
  22. package/package.json +1 -1
  23. package/skills/tacitt/SKILL.md +3 -3
  24. package/skills/tacitt/references/b-method.md +4 -2
  25. package/skills/tacitt/references/bootstrap.md +1 -1
  26. package/skills/tacitt/references/commands.md +10 -4
  27. package/skills/tacitt/references/journey.md +3 -3
  28. package/skills/tacitt/references/parallel-review.md +22 -9
  29. package/skills/tacitt/references/remote-workers.md +6 -2
  30. package/skills/tacitt/scripts/INDEX.md +4 -4
  31. package/skills/tacitt/scripts/answer.mjs +12 -1
  32. package/skills/tacitt/scripts/describe.mjs +3 -2
  33. package/skills/tacitt/scripts/journey-classification.mjs +1 -0
  34. package/skills/tacitt/scripts/journey-scan-timing.mjs +3 -2
  35. package/skills/tacitt/scripts/journey.mjs +1 -1
  36. package/skills/tacitt/scripts/privacy-validator.mjs +84 -33
  37. package/skills/tacitt/scripts/review.d.mts +1 -0
  38. package/skills/tacitt/scripts/review.mjs +296 -35
@@ -1,11 +1,11 @@
1
- import { $ as decodeZstd, A as captureLogicalJourneySources, B as defaultParallelism, D as createCollectionArtifacts, E as readConfiguredSearchFields, G as readLogicalJourneyFinalMasks, H as parallelismLimits, I as createSharedSessionPool, J as staticMaskCategory, K as readConfiguredOutputInfo, L as SessionWorkerError, N as runConfiguredSessionJob, R as sessionWorkerFailure, T as readOriginalSearchFields, V as gitSeedsProvider, W as privateJourneyDirectory, Y as readWorkspacePair, a as prepareSessionScans, at as makePrivateDirectory, b as readStaticSessionMasks, c as createWorkPieces, ct as windowsOwnerOnly, d as scanPreparedSession, et as createTemporary, g as openConfiguredUpload, h as configuredSessionUploads, i as verifyPreparedSessionSources, j as capturedJourneyScope, l as readSessionScanFields, lt as isWindowsAclFailure, n as sessionExtensions, nt as createCleanup, o as buildWorkScanBatches, ot as openPrivateFile, q as processJourneyMetadataFields, r as preparedScanConfiguration, s as scanReviewFields, st as restrictToOwner, t as hasRetiredSessionMarker, tt as removeNamedTemporarySpools, u as readSessionScanRecords, ut as windowsAclCodes, v as readCapturedOriginalRows, x as staticSessionJobs, y as processJourneyMetadata } from "./src-DeNJ8vOD.mjs";
2
- import { A as createExecutionPort, C as saveRemoteState, D as createPackagingProgressPort, E as exportDelivery, F as loadDeliveryResults, L as inBackgroundStoreLane, M as withStateMutex, N as createDeliveryService, O as createStore, P as preflight, R as isBackgroundStatusPoll, S as readRemoteState, T as collectRemoteWorkers, _ as staticBaseProduction, a as writeJourneyOutput, b as workApproval, 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 readSelectedCaptures, w as journeyDirectory, x as isRemoteImport, y as prepareSourcePlan, z as retrySharingViolation } from "./job-CBuIMTQ0.mjs";
3
- import { $t as profileHasChoices, D as bLiteralTerms, Gt as filterInventoryByPeriod, Kt as sessionPeriodSchema, Mt as privacyTermKey, Nn as createStaticProcessor, On as currentDetectorVersion, Qt as passportAddressSchema, Vn as entityDictionaryVersion, Vt as termTypeInfo, Xt as mergeOwnerProfile, Yn as frameRecords, Zt as ownerProfileSchema, an as safeField, br as processingSite, cn as reviewFamilies, dn as observeSource, dr as admitCustomTerm, fr as admitGitName, gr as ProcessingError, in as safeCode, nn as ApplicationError, rn as UnfinishedJourneyError, sn as familySelected, u as textDigest, ur as createEntityMatcher, xr as siteFields } from "./locate-D-ZaHpI0.mjs";
4
- 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-CKQlTA2Z.mjs";
5
- import { An as scanExclusionResourceSchema, B as createApplication, Bt as newJourneyWork, Ci as withReadingProgress, Ct as sendActionSchema, Di as selectionSchema, Dn as scanGitSeedsId, Dt as sendStartSchema, Ei as selectionBatchSchema, En as scanSessionResourceSchema, Et as sendRequestSchema, F as readAccountAvatarUrl, Fr as assertJourneyPrivacyReady, Gr as currentJourneyAdapters, H as inspectionStartSchema, I as storedCredentialSchema, Ir as privacyDecisionDigest, It as classificationPartition, Jr as obsoleteJourney, K as inspectionQuerySchema, Kr as hostDrives, Lt as classificationWorks, M as deviceAuthorizationSchema, Mi as recommendParallelism, Mr as displayedScopeDigest, N as deviceProfileScopeSchema, Nr as requestedScope, O as createAccount, On as scanGitSeedsResource, P as profileUrlMatchesIdentity, Rn as applyClassification, Rt as retireScanGeneration, Si as unfinishedPackagingCode, St as replaceSensitiveTermsSchema, Ti as packageCommandSchema, Tn as scanPreparationDigest, Tt as sendPrepareSchema, U as validateInspectionBatch, Ur as advanceStepsSend, V as inspectionCancelSchema, Vn as searchScopeDigest, Vr as nativeStartSchema, Zr as sendsOnNextAdvance, _ as changeJourney, _i as isIncludedWork, _r as workCorrectionBinding, _t as manualUploadStartSchema, at as appendSensitiveTerms, ci as journeySendSchema, cr as confirmedHideKinds, di as sendReplaceResultSchema, dr as journeyConfirmedHides, dt as validateFailureLocation, fi as sendResolutionSchema, ft as manualUploadActionSchema, gi as isClassifying, gt as manualUploadSchema, h as hideScopeDigest, hi as hasCompleteWorkDescription, hr as workDecisionDigest, ht as manualUploadPrepareSchema, ir as bTermMatcher, j as accountProfileSchema, k as renew, ki as producerVersion, kn as verifiedScanGitSeeds, li as sendArtifactSchema, lt as artifactFailureDetailSchema, m as activeJourneyTerms, mi as sendSettingsSchema, mr as retireWorkDescription, mt as manualUploadChoiceSchema, ni as staticReviewDigest, nt as settingsQuerySchema, oi as agentSourceSchema, qr as itemAtPassport, ri as staticSessionId, si as agentSourcesSchema, ti as hasCurrentStaticReview, tt as settingsBatchSchema, ui as sendReceiptSchema, vi as isReadingComplete, vr as emptyPreparationFacts, wi as commandSchema, wt as sendChoiceSchema, xt as normalizeSensitiveTerms, yi as isSelectedWork, yt as emptyTermsRevision, zn as journeySearchResultSchema, zt as isolatedWorkId } from "./src-Do-qzuMn.mjs";
6
- import { vt as isCoordinationBusy } from "./heap-Blrat--4.mjs";
7
- import { d as checkOpenSource, h as openSource } from "./run-DQ9xyKSt.mjs";
8
- import { t as createAgentSetupPort } from "./agent-setup-B12VbvN_.mjs";
1
+ import { A as captureLogicalJourneySources, B as defaultParallelism, D as createCollectionArtifacts, E as readConfiguredSearchFields, G as readLogicalJourneyFinalMasks, H as parallelismLimits, I as createSharedSessionPool, J as confirmedFamily, K as readConfiguredOutputInfo, L as SessionWorkerError, N as runConfiguredSessionJob, R as sessionWorkerFailure, T as readOriginalSearchFields, V as gitSeedsProvider, W as privateJourneyDirectory, X as staticMaskCategory, Y as confirmedTitleSpans, Z as readWorkspacePair, a as prepareSessionScans, b as readStaticSessionMasks, c as createWorkPieces, ct as openPrivateFile, d as scanPreparedSession, dt as isWindowsAclFailure, ft as windowsAclCodes, g as openConfiguredUpload, h as configuredSessionUploads, i as verifyPreparedSessionSources, it as createCleanup, j as capturedJourneyScope, l as readSessionScanFields, lt as restrictToOwner, n as sessionExtensions, nt as createTemporary, o as buildWorkScanBatches, q as processJourneyMetadataFields, r as preparedScanConfiguration, rt as removeNamedTemporarySpools, s as scanReviewFields, st as makePrivateDirectory, t as hasRetiredSessionMarker, tt as decodeZstd, u as readSessionScanRecords, ut as windowsOwnerOnly, v as readCapturedOriginalRows, x as staticSessionJobs, y as processJourneyMetadata } from "./src-CP-K2PBv.mjs";
2
+ import { A as exportDelivery, C as prepareSourcePlan, D as saveRemoteState, E as readRemoteState, H as isBackgroundStatusPoll, I as withStateMutex, L as createDeliveryService, M as createStore, O as journeyDirectory, P as createExecutionPort, R as preflight, S as changePreparation, T as isRemoteImport, U as retrySharingViolation, V as inBackgroundStoreLane, _ as staticBaseProduction, a as writeJourneyOutput, b as blockedCodes, c as mergeHideIndexTokens, f as writeJourneyBase, g as staticBaseInputs, h as staticSessions, j as createPackagingProgressPort, k as collectRemoteWorkers, l as packRenderHideIndex, m as saveStaticSession, o as createHideIndexTokens, p as readStaticResult, s as hideIndexTexts, t as journeyOutputJob, u as journeyBaseJob, v as readSelectedCaptures, w as workApproval, x as isolateFailingRoots, y as RootPreparationFailure, z as loadDeliveryResults } from "./job--zxwgaPy.mjs";
3
+ import { $t as ownerProfileSchema, An as currentDetectorVersion, Cr as siteFields, Fn as createStaticProcessor, Jt as sessionPeriodSchema, P as bLiteralTerms, Pt as privacyTermKey, Qt as mergeOwnerProfile, Sr as processingSite, Un as entityDictionaryVersion, Ut as termTypeInfo, Zn as frameRecords, an as UnfinishedJourneyError, en as passportAddressSchema, f as textDigest, fr as createEntityMatcher, in as ApplicationError, ln as familySelected, mr as admitGitName, on as safeCode, pn as observeSource, pr as admitCustomTerm, qt as filterInventoryByPeriod, sn as safeField, tn as profileHasChoices, un as reviewFamilies, vr as ProcessingError } from "./locate-PLEyLYW-.mjs";
4
+ 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-dL6y2mcu.mjs";
5
+ import { $r as sendReplaceResultSchema, An as scanExclusionResourceSchema, B as createApplication, Bt as newJourneyWork, Ci as bTermMatcher, Cr as privacyDecisionDigest, Ct as sendActionSchema, Di as packageCommandSchema, Dn as scanGitSeedsId, Dt as sendStartSchema, Ei as commandSchema, En as scanSessionResourceSchema, Et as sendRequestSchema, F as readAccountAvatarUrl, Fr as itemAtPassport, Gr as staticSessionId, H as inspectionStartSchema, I as storedCredentialSchema, Ir as obsoleteJourney, It as classificationPartition, Jr as agentSourceSchema, K as inspectionQuerySchema, Lt as classificationWorks, M as deviceAuthorizationSchema, N as deviceProfileScopeSchema, Nr as currentJourneyAdapters, O as createAccount, Oi as selectionBatchSchema, On as scanGitSeedsResource, P as profileUrlMatchesIdentity, Pi as recommendParallelism, Pr as hostDrives, Qr as sendReceiptSchema, Rn as applyClassification, Rt as retireScanGeneration, Sr as assertJourneyPrivacyReady, St as replaceSensitiveTermsSchema, Ti as replacementMarkerPattern, Tn as scanPreparationDigest, Tr as rememberedHideValues, Tt as sendPrepareSchema, U as validateInspectionBatch, Ur as hasCurrentStaticReview, V as inspectionCancelSchema, Vn as searchScopeDigest, Wr as staticReviewDigest, Xr as journeySendSchema, Yr as agentSourcesSchema, Zr as sendArtifactSchema, _ as changeJourney, _i as journeyConfirmedHides, _t as manualUploadStartSchema, ai as isIncludedWork, ar as emptyPreparationFacts, at as appendSensitiveTerms, br as requestedScope, di as withReadingProgress, dt as validateFailureLocation, ei as sendResolutionSchema, ft as manualUploadActionSchema, gt as manualUploadSchema, h as hideScopeDigest, ht as manualUploadPrepareSchema, ii as isClassifying, ir as workCorrectionBinding, j as accountProfileSchema, ji as producerVersion, jr as advanceStepsSend, k as renew, ki as selectionSchema, kn as verifiedScanGitSeeds, kr as nativeStartSchema, lt as artifactFailureDetailSchema, m as activeJourneyTerms, mi as confirmedHideKinds, mt as manualUploadChoiceSchema, ni as sendSettingsSchema, nr as workDecisionDigest, nt as settingsQuerySchema, oi as isReadingComplete, ri as hasCompleteWorkDescription, si as isSelectedWork, tr as retireWorkDescription, tt as settingsBatchSchema, ui as unfinishedPackagingCode, wt as sendChoiceSchema, xt as normalizeSensitiveTerms, yr as displayedScopeDigest, yt as emptyTermsRevision, zn as journeySearchResultSchema, zr as sendsOnNextAdvance, zt as isolatedWorkId } from "./src-CttNuxOn.mjs";
6
+ import { vt as isCoordinationBusy } from "./heap-DCS_WXIA.mjs";
7
+ import { d as checkOpenSource, h as openSource } from "./run-Dod7Vykp.mjs";
8
+ import { t as createAgentSetupPort } from "./agent-setup-Doy_AnPY.mjs";
9
9
  import { t as workerMessageSchema } from "./messages-D3m3bhva.mjs";
10
10
  import { constants, write } from "node:fs";
11
11
  import { z } from "zod";
@@ -166,6 +166,7 @@ const businessCommands = [
166
166
  "workflow.events",
167
167
  "bundleLibrary.get",
168
168
  "bundleLibrary.save",
169
+ "rememberedHides.remove",
169
170
  "sensitiveTerms.get",
170
171
  "sensitiveTerms.replace",
171
172
  "processing.catalog",
@@ -471,6 +472,81 @@ function commands$1(command) {
471
472
  return [];
472
473
  }
473
474
  }
475
+ /** Transparent prefixes that run their operand command; `sudo` is deliberately absent. */
476
+ const wrappers = {
477
+ nohup: {
478
+ flags: [],
479
+ valued: []
480
+ },
481
+ command: {
482
+ flags: ["-p"],
483
+ valued: []
484
+ },
485
+ time: {
486
+ flags: ["-p"],
487
+ valued: []
488
+ },
489
+ nice: {
490
+ flags: [],
491
+ valued: ["-n", "--adjustment"]
492
+ },
493
+ timeout: {
494
+ flags: [
495
+ "--preserve-status",
496
+ "--foreground",
497
+ "-v",
498
+ "--verbose"
499
+ ],
500
+ valued: [
501
+ "-s",
502
+ "--signal",
503
+ "-k",
504
+ "--kill-after"
505
+ ],
506
+ operand: /^\d+(?:\.\d+)?[smhd]?$/u
507
+ },
508
+ env: {
509
+ flags: [
510
+ "-i",
511
+ "-",
512
+ "--ignore-environment"
513
+ ],
514
+ valued: ["-u", "--unset"],
515
+ assignments: true
516
+ }
517
+ };
518
+ /** Strip known wrappers; an unknown wrapper option fails closed rather than guessing. */
519
+ function unwrap(words) {
520
+ let rest = words;
521
+ for (let depth = 0; depth < 8; depth++) {
522
+ const name = posix.basename(rest[0] ?? "");
523
+ const wrapper = Object.hasOwn(wrappers, name) ? wrappers[name] : void 0;
524
+ if (!wrapper) return rest;
525
+ let index = 1;
526
+ for (; index < rest.length; index++) {
527
+ const word = rest[index] ?? "";
528
+ if (word === "--") {
529
+ index++;
530
+ break;
531
+ }
532
+ if (wrapper.flags.includes(word)) continue;
533
+ if (wrapper.valued.includes(word)) {
534
+ index++;
535
+ continue;
536
+ }
537
+ if (wrapper.valued.some((flag) => word.startsWith(flag.startsWith("--") ? `${flag}=` : flag))) continue;
538
+ if (word.startsWith("-")) return [];
539
+ break;
540
+ }
541
+ if (wrapper.operand) {
542
+ if (!wrapper.operand.test(rest[index] ?? "")) return [];
543
+ index++;
544
+ }
545
+ if (wrapper.assignments) while (/^[A-Za-z_][A-Za-z0-9_]*=/u.test(rest[index] ?? "")) index++;
546
+ rest = rest.slice(index);
547
+ }
548
+ return [];
549
+ }
474
550
  function agent(words) {
475
551
  const executable = posix.basename(words[0] ?? "");
476
552
  if (words.some((word) => [
@@ -482,6 +558,38 @@ function agent(words) {
482
558
  if (executable === "claude" && words.slice(1).some((word) => word === "-p" || word === "--print")) return "claude";
483
559
  return null;
484
560
  }
561
+ const shells = /* @__PURE__ */ new Set([
562
+ "sh",
563
+ "bash",
564
+ "zsh",
565
+ "dash",
566
+ "ksh"
567
+ ]);
568
+ const interpreters = /* @__PURE__ */ new Set([
569
+ ...shells,
570
+ "python",
571
+ "python3"
572
+ ]);
573
+ /**
574
+ * Literal agents at command positions, including inside `sh -c` strings. A script file
575
+ * run by a shell or Python interpreter, or a relative or script-named path executable,
576
+ * is a launcher that may start an agent the log cannot show.
577
+ */
578
+ function launches(command, depth = 0) {
579
+ return commands$1(command).flatMap((parsed) => {
580
+ const words = unwrap(parsed);
581
+ const found = agent(words);
582
+ if (found) return [found];
583
+ const executable = words[0] ?? "";
584
+ const name = posix.basename(executable);
585
+ if (!interpreters.has(name)) return /^(?:\.\.?|~)\//u.test(executable) || executable.includes("/") && /\.(?:sh|bash|zsh|py)$/u.test(executable) ? ["script"] : [];
586
+ const operand = words.findIndex((word, index) => index > 0 && !word.startsWith("-"));
587
+ const options = words.slice(1, operand === -1 ? void 0 : operand);
588
+ if (options.some((word) => /^-[a-zA-Z]*c[a-zA-Z]*$/u.test(word))) return shells.has(name) && operand !== -1 && depth < 2 ? launches(words[operand] ?? "", depth + 1) : [];
589
+ if (options.some((word) => /^-[a-zA-Z]*[ms]|^--help$/u.test(word))) return [];
590
+ return operand === -1 ? [] : ["script"];
591
+ });
592
+ }
485
593
  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);
486
594
  /** Consent and import identity include every retained connection selector. */
487
595
  function endpoint(host, transport, args) {
@@ -545,20 +653,27 @@ function gcloud(words) {
545
653
  }
546
654
  /** Matches executables at shell command positions, never mentions in prose or other commands. */
547
655
  function parseRemoteDispatch(command) {
548
- return commands$1(command).flatMap((words) => {
656
+ return commands$1(command).flatMap((parsed) => {
657
+ const words = unwrap(parsed);
549
658
  const executable = posix.basename(words[0] ?? "");
550
659
  const remote = executable === "ssh" ? ssh(words) : executable === "gcloud" ? gcloud(words) : null;
551
660
  if (!remote) return [];
552
- return [...new Set(commands$1(remote.command).map(agent))].flatMap((harness) => harness ? [{
661
+ const found = new Set(launches(remote.command));
662
+ const agents = [...found].filter((harness) => harness !== "script");
663
+ return (agents.length ? agents : found.has("script") ? ["unknown"] : []).map((harness) => ({
553
664
  host: endpoint(remote.host, remote.transport, remote.args),
554
665
  harness,
555
666
  transport: remote.transport,
556
667
  args: remote.args
557
- }] : []);
668
+ }));
558
669
  });
559
670
  }
560
671
  //#endregion
561
672
  //#region apps/cli/src/remote/scan.ts
673
+ /** Escapes and shell quotes may split executable spelling; retain those candidates. */
674
+ function mayContainRemoteDispatch(line) {
675
+ return line.includes("ssh") || line.includes("gcloud") || line.includes("\\") || line.includes("'");
676
+ }
562
677
  function object(value) {
563
678
  return typeof value === "object" && value !== null && !Array.isArray(value);
564
679
  }
@@ -664,6 +779,7 @@ async function scanRemoteDispatches(sessions, signal) {
664
779
  signal.throwIfAborted();
665
780
  const line = framed.endsWith("\r") ? framed.slice(0, -1) : framed;
666
781
  if (!line.trim()) continue;
782
+ if (!pending.size && !mayContainRemoteDispatch(line)) continue;
667
783
  let row;
668
784
  try {
669
785
  row = JSON.parse(line);
@@ -794,13 +910,17 @@ function createRemotePreflight(options) {
794
910
  }
795
911
  function result(state) {
796
912
  return {
797
- hosts: [...new Set(state.windows.map((window) => window.host))].filter((host) => !Object.hasOwn(state.decisions, host)).map((host) => ({
798
- host,
799
- windows: state.windows.filter((window) => window.host === host).map(({ start, end }) => ({
800
- start,
801
- end
802
- }))
803
- })),
913
+ hosts: [...new Set(state.windows.map((window) => window.host))].filter((host) => !Object.hasOwn(state.decisions, host)).map((host) => {
914
+ const windows = state.windows.filter((window) => window.host === host);
915
+ return {
916
+ host,
917
+ windows: windows.map(({ start, end }) => ({
918
+ start,
919
+ end
920
+ })),
921
+ ...windows.some((window) => window.harness === "unknown") ? { scriptLaunch: true } : {}
922
+ };
923
+ }),
804
924
  copied: state.copied.length,
805
925
  failures: state.failures,
806
926
  ...state.sourceFailureCounts ? { sourceFailureCounts: state.sourceFailureCounts } : {},
@@ -1075,14 +1195,16 @@ function createJourneyTitles(storage, processing) {
1075
1195
  if (state.phase === "paused") throw new ApplicationError("OPERATION_CANCELED");
1076
1196
  const work = state.works.find((item) => item.id === action.workId);
1077
1197
  if (!work) throw new ApplicationError("UNKNOWN_WORKSPACE");
1078
- if (action.title === work.title) return commit();
1198
+ if (action.title === work.title && (work.titleOrigin === "explicit" || new RegExp(replacementMarkerPattern).test(work.title))) return commit();
1079
1199
  let title;
1200
+ let staticMasks = [];
1080
1201
  try {
1081
1202
  const [processed] = await processJourneyMetadataFields({
1082
1203
  ...await metadataProcessing(storage, state, work, processing),
1083
1204
  fields: [{ text: action.title }]
1084
1205
  }, signal);
1085
1206
  title = processed?.text;
1207
+ staticMasks = processed?.staticMasks ?? [];
1086
1208
  } catch (error) {
1087
1209
  if (typeof error === "object" && error !== null && "code" in error && error.code === "PROCESSING_INVALID_RECORD") throw new ApplicationError("GENERATED_METADATA_REPHRASE_REQUIRED", void 0, { cause: error });
1088
1210
  throw error;
@@ -1095,10 +1217,36 @@ function createJourneyTitles(storage, processing) {
1095
1217
  id: input.idempotencyKey,
1096
1218
  title: action.title
1097
1219
  });
1220
+ let suggestion = null;
1221
+ const described = work.suggestion ? (await storage.resources({
1222
+ journeyId: state.journeyId,
1223
+ kind: "work-description",
1224
+ id: work.suggestion.id
1225
+ }))[0] : void 0;
1226
+ if (work.suggestion && described?.kind === "work-description" && described.workId === work.id && described.inputRevision === work.inputRevision) {
1227
+ const id = randomUUID();
1228
+ await storage.saveResource({
1229
+ ...described,
1230
+ id,
1231
+ explicitTitleId: input.idempotencyKey,
1232
+ appliedFields: {
1233
+ ...described.appliedFields,
1234
+ title
1235
+ },
1236
+ appliedTitleStaticMasks: [...staticMasks],
1237
+ appliedTitleStaticText: title
1238
+ });
1239
+ suggestion = {
1240
+ ...work.suggestion,
1241
+ id,
1242
+ title
1243
+ };
1244
+ }
1098
1245
  return commit({
1099
1246
  ...state,
1100
1247
  works: state.works.map((item) => item.id !== work.id ? item : {
1101
1248
  ...retireWorkDescription(item),
1249
+ suggestion,
1102
1250
  title,
1103
1251
  titleOrigin: "explicit",
1104
1252
  explicitTitleId: input.idempotencyKey,
@@ -1404,32 +1552,6 @@ function bindSendDestination(storage, destination) {
1404
1552
  };
1405
1553
  }
1406
1554
  //#endregion
1407
- //#region apps/cli/src/journey/prepare/update.ts
1408
- /** Preparation owns only the changed Work rows, progress and package invalidation. */
1409
- async function changePreparation(storage, journeyId, kind, change, attempts = Number.POSITIVE_INFINITY, clearPackageApproval = false) {
1410
- for (let attempt = 0; attempt < attempts; attempt++) {
1411
- const before = await storage.preparationState(journeyId);
1412
- if (!before?.consent) throw new ApplicationError("CONFIRMATION_REQUIRED");
1413
- const after = change(before);
1414
- if (after === before) return before;
1415
- const previous = new Map(before.works.map((work) => [work.id, work]));
1416
- const result = await storage.updatePreparation({
1417
- journeyId,
1418
- expectedRevision: before.revision,
1419
- requestKey: randomUUID(),
1420
- at: (/* @__PURE__ */ new Date()).toISOString(),
1421
- kind,
1422
- works: after.works.filter((work) => work !== previous.get(work.id)),
1423
- phase: after.phase,
1424
- readingComplete: after.readingComplete,
1425
- readingStartedAt: after.readingStartedAt,
1426
- clearPackageApproval: clearPackageApproval && before !== after
1427
- });
1428
- if (result.status !== "stale") return result.state;
1429
- }
1430
- throw new ApplicationError("REVISION_CONFLICT");
1431
- }
1432
- //#endregion
1433
1555
  //#region apps/cli/src/journey/works/metadata.ts
1434
1556
  /** Original classification fields remain durable; only the derived display cache is replaced. */
1435
1557
  const classificationMetadataInput = (work) => ({
@@ -1784,49 +1906,6 @@ function preparationFacts(inventory, rootIds) {
1784
1906
  };
1785
1907
  }
1786
1908
  //#endregion
1787
- //#region apps/cli/src/journey/prepare/isolation.ts
1788
- /** A Work-level refusal caused by identified roots, each with its own public-safe code. */
1789
- var RootPreparationFailure = class extends ApplicationError {
1790
- roots;
1791
- constructor(code, roots, options) {
1792
- super(code, void 0, options);
1793
- this.roots = roots;
1794
- }
1795
- };
1796
- /** The distinct public-safe codes of the identified failing roots, else the Work-level code. */
1797
- function blockedCodes(error, code) {
1798
- return error instanceof RootPreparationFailure && error.roots.size ? [...new Set(error.roots.values())] : [code];
1799
- }
1800
- /**
1801
- * ADR 0188 (restoring ADR 0059 D06): moves the failing roots of a multi-root Work into its
1802
- * stable blocked "<title> – couldn't prepare" Work so the rest can be prepared again. False
1803
- * when the failure names no root or every root, or the Work changed or left pending.
1804
- */
1805
- async function isolateFailingRoots(storage, journeyId, work, error) {
1806
- if (!(error instanceof RootPreparationFailure)) return false;
1807
- const roots = work.rootSessionIds.flatMap((sessionId) => {
1808
- const code = error.roots.get(sessionId);
1809
- return code ? [{
1810
- sessionId,
1811
- code
1812
- }] : [];
1813
- });
1814
- if (roots.length === 0 || roots.length === work.rootSessionIds.length) return false;
1815
- for (;;) {
1816
- const state = await storage.preparationState(journeyId);
1817
- const current = state?.works.find((item) => item.id === work.id);
1818
- if (!state || state.phase === "paused" || state.pendingHideId || current?.inputRevision !== work.inputRevision || current.status !== "pending") return false;
1819
- if ((await storage.isolateRoots({
1820
- journeyId,
1821
- workId: work.id,
1822
- inputRevision: work.inputRevision,
1823
- roots,
1824
- expectedRevision: state.revision,
1825
- idempotencyKey: randomUUID()
1826
- })).status !== "stale") return true;
1827
- }
1828
- }
1829
- //#endregion
1830
1909
  //#region apps/cli/src/journey/prepare/empty-review.ts
1831
1910
  /** Empty AI coverage requires authenticated scanner proof and the original frozen source bindings. */
1832
1911
  async function verifyEmptyScanRoots(resources, roots, signal, storage, plan) {
@@ -1954,14 +2033,27 @@ async function prepareScanContext(storage, journeyId, work, selected, processing
1954
2033
  } } : {}
1955
2034
  }, signal);
1956
2035
  const seeds = frozenGitNames(storage, journeyId, current.snapshotId, processing.gitNames);
2036
+ const families = reviewFamilies(approval.inventory);
1957
2037
  const roots = /* @__PURE__ */ new Map();
1958
2038
  for (const session of approval.inventory.sessions) {
1959
2039
  const workspace = approval.inventory.workspaces.find((item) => item.id === session.workspaceId);
1960
2040
  if (!workspace) throw new ApplicationError("CORRUPTED_STATE");
1961
2041
  const paths = workspace.path === null ? [] : [workspace.path];
1962
- roots.set(JSON.stringify(paths), paths);
2042
+ const key = JSON.stringify(paths);
2043
+ const entry = roots.get(key) ?? {
2044
+ paths,
2045
+ rootIds: /* @__PURE__ */ new Set()
2046
+ };
2047
+ const root = families.owners.get(session.id);
2048
+ if (root) entry.rootIds.add(root.id);
2049
+ roots.set(key, entry);
2050
+ }
2051
+ for (const { paths, rootIds } of roots.values()) try {
2052
+ await seeds(paths, signal);
2053
+ } catch (error) {
2054
+ if (!(error instanceof ProcessingError) || error.code === "PROCESSING_CANCELED") throw error;
2055
+ throw new RootPreparationFailure(error.code, new Map([...rootIds].map((id) => [id, error.code])), { cause: error });
1963
2056
  }
1964
- for (const paths of roots.values()) await seeds(paths, signal);
1965
2057
  return {
1966
2058
  prepared,
1967
2059
  configurationDigest: textDigest(JSON.stringify({
@@ -2769,6 +2861,7 @@ function createJourneyPreparation(options) {
2769
2861
  const plan = await prepareSourcePlan(storage, options.harnesses, journeyId, terms, abort.signal, current.period, options.stateDir);
2770
2862
  const initial = await storage.preparationState(journeyId);
2771
2863
  if (!initial?.consent) throw new ApplicationError("CONFIRMATION_REQUIRED");
2864
+ const remembered = initial.snapshotId ? [] : await options.rememberedHides?.() ?? [];
2772
2865
  const formed = initial.snapshotId ? [] : namedJourneyWorks(formJourneyWorks(plan.approval, options.processing.environment), options.processing, abort.signal);
2773
2866
  let state;
2774
2867
  if (!initial.snapshotId) state = await changeJourney(storage, journeyId, "snapshot", (before) => ({
@@ -2779,6 +2872,7 @@ function createJourneyPreparation(options) {
2779
2872
  reviewMethod: "dictionary"
2780
2873
  },
2781
2874
  works: before.snapshotId ? before.works : formed,
2875
+ ...before.snapshotId || !remembered.length ? {} : { rememberedHides: [...remembered] },
2782
2876
  phase: before.phase !== "paused" && (!before.snapshotId || before.works.some(pendingSelected)) ? "processing" : before.phase
2783
2877
  }));
2784
2878
  else if (initial.phase !== "processing" && initial.phase !== "paused" && initial.works.some(pendingSelected)) state = await changePreparation(storage, journeyId, "snapshot", (before) => before.phase !== "paused" && before.works.some(pendingSelected) ? {
@@ -2822,7 +2916,9 @@ function createJourneyPreparation(options) {
2822
2916
  };
2823
2917
  const releaseAdmission = /* @__PURE__ */ new Map();
2824
2918
  const retryWork = async (work, error, attempt) => {
2825
- if (blockedCodes(error, safeCode(error)).includes("PRIVACY_CHANGE_PENDING") || work.blockedCodes.includes("PRIVACY_CHANGE_PENDING")) return false;
2919
+ const code = safeCode(error);
2920
+ if (deterministicGitFailure(error)) return false;
2921
+ if (blockedCodes(error, code).includes("PRIVACY_CHANGE_PENDING") || work.blockedCodes.includes("PRIVACY_CHANGE_PENDING")) return false;
2826
2922
  const delay = workRetryDelaysMs[attempt];
2827
2923
  if (attempt >= retryLimit || delay === void 0) return false;
2828
2924
  const blocked = await blockWork(work, error);
@@ -2855,9 +2951,9 @@ function createJourneyPreparation(options) {
2855
2951
  const readRetries = /* @__PURE__ */ new Map();
2856
2952
  const staticRetries = /* @__PURE__ */ new Map();
2857
2953
  /** A failed isolation leaves the Work blocked as before; cancellation and contention escape. */
2858
- const isolateRoots = async (work, error) => {
2954
+ const isolateRoots = async (work, error, afterRead = false) => {
2859
2955
  try {
2860
- return await isolateFailingRoots(storage, journeyId, work, error);
2956
+ return await isolateFailingRoots(storage, journeyId, work, error, afterRead);
2861
2957
  } catch (isolation) {
2862
2958
  abort.signal.throwIfAborted();
2863
2959
  const code = safeCode(isolation);
@@ -2935,7 +3031,7 @@ function createJourneyPreparation(options) {
2935
3031
  if (work) await blockWork(work, error);
2936
3032
  return false;
2937
3033
  }
2938
- if (attempt < retryLimit) return false;
3034
+ if (attempt < retryLimit && !deterministicGitFailure(error)) return false;
2939
3035
  if (work && await isolateRoots(work, error)) {
2940
3036
  readRetries.delete(generation(work));
2941
3037
  return true;
@@ -2988,7 +3084,13 @@ function createJourneyPreparation(options) {
2988
3084
  return staticWork(planned, passSubmitted);
2989
3085
  }
2990
3086
  const now = await storage.preparationState(journeyId);
2991
- if (now?.phase !== "sending" && now?.phase !== "completed" && (attempt >= retryLimit || blockedCodes(error, safeCode(error)).includes("PRIVACY_CHANGE_PENDING"))) await blockWork(work, error);
3087
+ if (now?.phase !== "sending" && now?.phase !== "completed" && (attempt >= retryLimit || deterministicGitFailure(error) || blockedCodes(error, safeCode(error)).includes("PRIVACY_CHANGE_PENDING"))) {
3088
+ if (await isolateRoots(work, error, true)) {
3089
+ staticRetries.delete(generation(work));
3090
+ return;
3091
+ }
3092
+ await blockWork(work, error);
3093
+ }
2992
3094
  }
2993
3095
  };
2994
3096
  let fatal;
@@ -3120,6 +3222,10 @@ function createJourneyPreparation(options) {
3120
3222
  }
3121
3223
  };
3122
3224
  }
3225
+ /** A broken local Git command will not recover by rereading the same snapshot. */
3226
+ function deterministicGitFailure(error) {
3227
+ return /^git-(?:show-ref|log|config|for-each-ref|rev-parse|cat-file)-exit-\d+$/.test(processingSite(error)?.site ?? "");
3228
+ }
3123
3229
  /** Two automatic retries per Work, after two then five seconds. */
3124
3230
  const workRetryDelaysMs = [2e3, 5e3];
3125
3231
  async function waitForWorkRetry(milliseconds, signal) {
@@ -3706,6 +3812,14 @@ function renderRetry(signal, slots = 1) {
3706
3812
  //#region apps/cli/src/journey/metadata/render.ts
3707
3813
  /** Reuse private raw provenance, never a displayed anonymized title as new source text. */
3708
3814
  async function renderTitle(storage, state, work) {
3815
+ const terms = [...await journeyConfirmedHides(storage, state)].map(([term, { kind }]) => ({
3816
+ term,
3817
+ category: kind
3818
+ }));
3819
+ const semantic = (text) => ({
3820
+ textSha256: textDigest(text),
3821
+ spans: work.titleOrigin === "explicit" ? [] : confirmedTitleSpans(text, confirmedFamily(terms, workExceptions(state, work.id)))
3822
+ });
3709
3823
  if (!work.suggestion) {
3710
3824
  if (work.explicitTitleId) throw new ApplicationError("INCOMPLETE_REVIEW");
3711
3825
  const cached = work.classificationMetadata;
@@ -3713,10 +3827,7 @@ async function renderTitle(storage, state, work) {
3713
3827
  return { cachedTitle: {
3714
3828
  text: cached.title,
3715
3829
  staticMasks: cached.titleStaticMasks,
3716
- semantic: {
3717
- textSha256: textDigest(cached.title),
3718
- spans: []
3719
- }
3830
+ semantic: semantic(cached.title)
3720
3831
  } };
3721
3832
  }
3722
3833
  const description = (await storage.resources({
@@ -3730,10 +3841,7 @@ async function renderTitle(storage, state, work) {
3730
3841
  return { cachedTitle: {
3731
3842
  text: title,
3732
3843
  staticMasks: description.appliedTitleStaticMasks,
3733
- semantic: {
3734
- textSha256: textDigest(title),
3735
- spans: []
3736
- }
3844
+ semantic: semantic(title)
3737
3845
  } };
3738
3846
  }
3739
3847
  //#endregion
@@ -3784,7 +3892,7 @@ async function describeItem(journey, work, dependencies, signal) {
3784
3892
  if (work.access === "drop") throw new ApplicationError("INVALID_STATE");
3785
3893
  return {
3786
3894
  workId: work.id,
3787
- title: work.title,
3895
+ title: artifact.title,
3788
3896
  access: work.access,
3789
3897
  artifact,
3790
3898
  operationKey: randomUUID(),
@@ -4124,6 +4232,14 @@ async function journeyOutputRequest(storage, state, work, processingConfig, _sig
4124
4232
  families: hides.request(base.sessions, dictionary, exceptions),
4125
4233
  metadata: {
4126
4234
  ...title,
4235
+ workspaceTitleHides: {
4236
+ terms: [...dictionary].flatMap(([term, keyword]) => confirmed.has(term) || !keyword.bounded ? [{
4237
+ term,
4238
+ category: keyword.kind,
4239
+ bounded: keyword.bounded
4240
+ }] : []),
4241
+ exceptions
4242
+ },
4127
4243
  access: work.access === "private" ? "private" : "public"
4128
4244
  },
4129
4245
  processing: {
@@ -4641,6 +4757,38 @@ function createClassificationSessions(storage, processing, stateDir) {
4641
4757
  };
4642
4758
  }
4643
4759
  //#endregion
4760
+ //#region apps/cli/src/journey/works/projection-prefix.ts
4761
+ async function readClassificationPrefix(source, options, signal) {
4762
+ const stamp = await projectionStamp(source.capture.path);
4763
+ const rows = [];
4764
+ let ordinal = -1;
4765
+ outer: for await (const original of readCapturedOriginalRows(source, {
4766
+ directory: options.directory,
4767
+ verification: "classification-prefix"
4768
+ }, signal)) {
4769
+ const value = JSON.parse(original.text);
4770
+ for (const field of mainTextFields(value, original.harness, "main", true)) for (const chunk of mainTextChunks(field)) {
4771
+ signal.throwIfAborted();
4772
+ if (++ordinal <= options.after) continue;
4773
+ rows.push({
4774
+ sessionId: original.sessionId,
4775
+ sourcePartId: original.sourcePartId,
4776
+ recordIndex: original.recordIndex,
4777
+ pointer: chunk.pointer,
4778
+ offset: chunk.offset,
4779
+ role: chunk.role,
4780
+ text: chunk.text
4781
+ });
4782
+ if (rows.length === options.limit) break outer;
4783
+ }
4784
+ }
4785
+ if (await projectionStamp(source.capture.path) !== stamp) throw new ApplicationError("CORRUPTED_STATE");
4786
+ return {
4787
+ rows,
4788
+ count: ordinal + 1
4789
+ };
4790
+ }
4791
+ //#endregion
4644
4792
  //#region apps/cli/src/journey/works/projection.ts
4645
4793
  const digest$1 = (bytes) => createHash("sha256").update(bytes).digest();
4646
4794
  async function buildProjection(directory, key, source, role, signal) {
@@ -4723,9 +4871,14 @@ async function buildProjection(directory, key, source, role, signal) {
4723
4871
  }));
4724
4872
  }
4725
4873
  }
4726
- /** Persisted derived content, never a scan receipt. Only the first read replays the capture. */
4874
+ /** Explicit prefixes stream; default reads reuse a fully verified projection. Neither is a scan receipt. */
4727
4875
  async function readClassificationProjection(source, options, signal) {
4728
4876
  signal.throwIfAborted();
4877
+ if (options.role !== "main") return {
4878
+ rows: [],
4879
+ count: 0
4880
+ };
4881
+ if (options.prefix) return readClassificationPrefix(source, options, signal);
4729
4882
  const key = digest$1(JSON.stringify([
4730
4883
  1,
4731
4884
  options.snapshotId,
@@ -4795,9 +4948,11 @@ function createClassificationReader(storage, stateDir) {
4795
4948
  const directory = await journeyDirectory(stateDir, state.journeyId);
4796
4949
  const rows = [];
4797
4950
  let cursor = null;
4951
+ const limit = input.limit ?? 8;
4798
4952
  outer: for (let partIndex = input.cursor?.partIndex ?? 0; partIndex < sources.length; partIndex++) {
4799
4953
  const source = sources[partIndex];
4800
4954
  if (!source) throw new ApplicationError("CORRUPTED_STATE");
4955
+ if (!work.rootSessionIds.includes(source.session.id)) continue;
4801
4956
  const capture = captures.find((item) => item.kind === "capture" && item.capture.partId === source.part.id);
4802
4957
  if (capture?.kind !== "capture") throw new ApplicationError("SCOPE_NOT_READY");
4803
4958
  const after = partIndex === input.cursor?.partIndex ? input.cursor.afterOrdinal : -1;
@@ -4808,12 +4963,13 @@ function createClassificationReader(storage, stateDir) {
4808
4963
  }, {
4809
4964
  directory,
4810
4965
  snapshotId: state.snapshotId,
4811
- role: work.rootSessionIds.includes(source.session.id) ? "main" : "worker",
4966
+ role: "main",
4812
4967
  after,
4813
- limit: 9 - rows.length
4968
+ limit: limit + (input.limit === void 0 ? 1 : 0) - rows.length,
4969
+ prefix: input.limit !== void 0
4814
4970
  }, signal);
4815
4971
  for (const [index, row] of page.rows.entries()) {
4816
- if (rows.length === 8) {
4972
+ if (rows.length === limit) {
4817
4973
  cursor = {
4818
4974
  snapshotId: state.snapshotId,
4819
4975
  inputRevision: work.inputRevision,
@@ -4824,6 +4980,16 @@ function createClassificationReader(storage, stateDir) {
4824
4980
  break outer;
4825
4981
  }
4826
4982
  rows.push(row);
4983
+ if (input.limit !== void 0 && rows.length === limit) {
4984
+ cursor = {
4985
+ snapshotId: state.snapshotId,
4986
+ inputRevision: work.inputRevision,
4987
+ ...input.rootSessionId ? { rootSessionId: input.rootSessionId } : {},
4988
+ partIndex,
4989
+ afterOrdinal: after + index + 1
4990
+ };
4991
+ break outer;
4992
+ }
4827
4993
  }
4828
4994
  }
4829
4995
  const current = await storage.current(state.journeyId);
@@ -5486,7 +5652,7 @@ function createJourneyPreview(storage) {
5486
5652
  const title = protectedSourceText({
5487
5653
  text: cachedTitle.text,
5488
5654
  protectedRanges: []
5489
- }, cachedTitle.semantic.spans);
5655
+ }, cachedTitle.semantic.spans, "[hidden]", false);
5490
5656
  await scanViewFence(storage, state, work, sha256);
5491
5657
  return {
5492
5658
  workId: work.id,
@@ -5518,16 +5684,16 @@ async function* conversationFields(fields) {
5518
5684
  }
5519
5685
  }
5520
5686
  /** Static detector spans cannot be restored by a semantic Keep or exception. */
5521
- function protectedSourceText(field, hidden) {
5687
+ function protectedSourceText(field, hidden, replacement = "[Hidden]", joinAdjacent = true) {
5522
5688
  const merged = [];
5523
5689
  for (const range of [...field.protectedRanges, ...hidden].sort((a, b) => a.start - b.start || b.end - a.end)) {
5524
5690
  if (range.start < 0 || range.end > field.text.length || range.end <= range.start) throw new ApplicationError("CORRUPTED_STATE");
5525
5691
  const prior = merged.at(-1);
5526
- if (prior && range.start <= prior.end) prior.end = Math.max(prior.end, range.end);
5692
+ if (prior && (range.start < prior.end || joinAdjacent && range.start === prior.end)) prior.end = Math.max(prior.end, range.end);
5527
5693
  else merged.push({ ...range });
5528
5694
  }
5529
5695
  let text = field.text;
5530
- for (const range of merged.toReversed()) text = text.slice(0, range.start) + "[Hidden]" + text.slice(range.end);
5696
+ for (const range of merged.toReversed()) text = text.slice(0, range.start) + replacement + text.slice(range.end);
5531
5697
  return text;
5532
5698
  }
5533
5699
  //#endregion
@@ -10626,6 +10792,16 @@ function createJourneySender(dependencies, phases = 3, host = {
10626
10792
  const poll = createAssessmentPoll();
10627
10793
  const running = /* @__PURE__ */ new Map();
10628
10794
  const runKey = (address) => `${address.journeyId}:${address.sendId}`;
10795
+ const remembered = /* @__PURE__ */ new Set();
10796
+ const remember = async (journey) => {
10797
+ if (!dependencies.rememberHides || remembered.has(journey.journeyId)) return;
10798
+ try {
10799
+ await dependencies.rememberHides(journey);
10800
+ remembered.add(journey.journeyId);
10801
+ } catch (error) {
10802
+ dependencies.failure("REMEMBERED_HIDES_FAILED", error);
10803
+ }
10804
+ };
10629
10805
  const load = async (address) => {
10630
10806
  const latest = requireSend(await dependencies.storage.loadSend(address.journeyId));
10631
10807
  sessionFacts.retain(address.journeyId, latest.send);
@@ -10871,12 +11047,14 @@ function createJourneySender(dependencies, phases = 3, host = {
10871
11047
  signal.throwIfAborted();
10872
11048
  const finalAuthorizationError = await requestError(dependencies, snapshot.send, hostId, signal);
10873
11049
  if (finalAuthorizationError) return block(snapshot, snapshot.send, finalAuthorizationError);
10874
- return saveStart({
11050
+ const started = await saveStart({
10875
11051
  ...snapshot.send,
10876
11052
  subject: connection?.subject ?? snapshot.send.subject,
10877
11053
  confirmedAt: snapshot.send.request && !connection ? null : dependencies.now(),
10878
11054
  ...profile
10879
11055
  });
11056
+ if (started.send?.confirmedAt) await remember(snapshot.journey);
11057
+ return started;
10880
11058
  },
10881
11059
  async respond(raw) {
10882
11060
  const input = sendChoiceSchema.parse(raw);
@@ -10954,6 +11132,7 @@ function createJourneySender(dependencies, phases = 3, host = {
10954
11132
  ...snapshot,
10955
11133
  status: "stale"
10956
11134
  };
11135
+ if (snapshot.send.confirmedAt) await remember(snapshot.journey);
10957
11136
  if (!host.serving && snapshot.send.request && !snapshot.send.confirmedAt) return {
10958
11137
  ...snapshot,
10959
11138
  status: "applied"
@@ -11541,9 +11720,9 @@ async function privateStat$2(path, stat, directory) {
11541
11720
  if (!(process.platform === "win32" ? await windowsOwnerOnly(path, directory ? "directory" : "file") : stat.uid === process.getuid?.() && (stat.mode & 63) === 0)) throw new ApplicationError("SENSITIVE_TERMS_UNSAFE_PATH");
11542
11721
  }
11543
11722
  /** Private user settings survive control-plane database reset. No learned entities enter here. */
11544
- function createSensitiveTermsFile(stateDirectory) {
11723
+ function createSensitiveTermsFile(stateDirectory, fileName = "sensitive-terms.json") {
11545
11724
  const directory = join(stateDirectory, "settings");
11546
- const path = join(directory, "sensitive-terms.json");
11725
+ const path = join(directory, fileName);
11547
11726
  let observed = false;
11548
11727
  async function checkedDirectory(create) {
11549
11728
  await privateStat$2(stateDirectory, await lstat(stateDirectory), true);
@@ -11650,6 +11829,41 @@ function createSensitiveTermsFile(stateDirectory) {
11650
11829
  };
11651
11830
  }
11652
11831
  //#endregion
11832
+ //#region apps/cli/src/remembered-hides.ts
11833
+ /**
11834
+ * Each value on its own: one value the file cannot hold never drops the others. `journeys` records
11835
+ * the journeys already kept, so a later restart never brings back a value the person removed.
11836
+ */
11837
+ async function appendRememberedHides(store, journeys, journeyId, candidates) {
11838
+ const done = await journeys.read();
11839
+ if (done.terms.includes(journeyId)) return;
11840
+ const values = candidates.filter((value) => {
11841
+ try {
11842
+ return normalizeSensitiveTerms([value]).length === 1;
11843
+ } catch {
11844
+ return false;
11845
+ }
11846
+ });
11847
+ if (values.length) {
11848
+ const saved = await store.read();
11849
+ const next = normalizeSensitiveTerms([...saved.terms, ...values]);
11850
+ if (next.length !== saved.terms.length) await store.replace(saved.revision, next);
11851
+ }
11852
+ await journeys.replace(done.revision, [...done.terms, journeyId]);
11853
+ }
11854
+ /** Counts only, so no other remembered value reaches the agent that asked. */
11855
+ async function removeRememberedHides(store, values) {
11856
+ const removed = new Set(values.map((item) => item.trim().normalize("NFC")));
11857
+ const saved = await store.read();
11858
+ const kept = saved.terms.filter((term) => !removed.has(term));
11859
+ if (kept.length !== saved.terms.length) await store.replace(saved.revision, kept);
11860
+ const found = saved.terms.length - kept.length;
11861
+ return {
11862
+ removed: found,
11863
+ notFound: removed.size - found
11864
+ };
11865
+ }
11866
+ //#endregion
11653
11867
  //#region apps/cli/src/account/snapshot.ts
11654
11868
  function accountSnapshot(value) {
11655
11869
  const empty = {
@@ -12870,6 +13084,8 @@ async function compose(options, callbacks, bundles) {
12870
13084
  traceStartup("library-end");
12871
13085
  const outputDir = library.path;
12872
13086
  const sensitiveTerms = createSensitiveTermsFile(stateDir);
13087
+ const rememberedHides = createSensitiveTermsFile(stateDir, "remembered-hides.json");
13088
+ const rememberedJourneys = createSensitiveTermsFile(stateDir, "remembered-hides-journeys.json");
12873
13089
  const processing = {
12874
13090
  sensitiveTerms,
12875
13091
  adapters: createNativeAdapters(),
@@ -12908,6 +13124,7 @@ async function compose(options, callbacks, bundles) {
12908
13124
  };
12909
13125
  const prepareJourney = createJourneyPreparation({
12910
13126
  storage: journeyStorage,
13127
+ rememberedHides: async () => (await rememberedHides.read()).terms,
12911
13128
  harnesses,
12912
13129
  processing,
12913
13130
  execution,
@@ -12956,6 +13173,7 @@ async function compose(options, callbacks, bundles) {
12956
13173
  auth: sendAuth,
12957
13174
  now: () => (/* @__PURE__ */ new Date()).toISOString(),
12958
13175
  failure: (code, cause) => diagnostics.onFailure(code, cause),
13176
+ rememberHides: (journey) => appendRememberedHides(rememberedHides, rememberedJourneys, journey.journeyId, rememberedHideValues(journey)),
12959
13177
  sessionTransfer: (workId, transfer) => diagnostics.onActivity({
12960
13178
  event: "journey.session_transfer",
12961
13179
  at: (/* @__PURE__ */ new Date()).toISOString(),
@@ -13116,6 +13334,7 @@ async function compose(options, callbacks, bundles) {
13116
13334
  })();
13117
13335
  return {
13118
13336
  application,
13337
+ rememberedHides,
13119
13338
  remoteWorkers: createRemotePreflight({
13120
13339
  storage: journeyStorage,
13121
13340
  harnesses,
@@ -13330,6 +13549,7 @@ async function invoke(runtime, command, value, signal, getAudience = "agent") {
13330
13549
  case "workflow.resume": return app.resume(commandSchema.parse(value));
13331
13550
  case "bundleLibrary.get": return app.getBundleLibrary();
13332
13551
  case "bundleLibrary.save": return app.saveBundleLibrary(z.object({ directory: z.string().max(4096).nullable() }).parse(value).directory);
13552
+ case "rememberedHides.remove": return removeRememberedHides(runtime.rememberedHides, z.object({ values: z.array(z.string()).min(1).max(1e3) }).strict().parse(value).values);
13333
13553
  case "sensitiveTerms.get": return app.getSensitiveTerms();
13334
13554
  case "sensitiveTerms.replace": return app.replaceSensitiveTerms(replaceSensitiveTermsSchema.parse(value));
13335
13555
  case "processing.catalog": return app.getProcessingCatalog();