@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.
- package/dist/{agent-setup-B12VbvN_.mjs → agent-setup-Doy_AnPY.mjs} +2 -2
- package/dist/agent-setup-worker.mjs +5 -5
- package/dist/cli.mjs +10 -10
- package/dist/codex-title-worker.mjs +1 -1
- package/dist/configured-preview-worker.mjs +5 -5
- package/dist/configured-session-worker.mjs +5 -5
- package/dist/{frame-B3He21G8.mjs → frame-BPbypzmt.mjs} +1 -1
- package/dist/{heap-Blrat--4.mjs → heap-DCS_WXIA.mjs} +7 -3
- package/dist/inspection-worker.mjs +4 -4
- package/dist/{job-CBuIMTQ0.mjs → job--zxwgaPy.mjs} +135 -36
- package/dist/{locate-D-ZaHpI0.mjs → locate-PLEyLYW-.mjs} +287 -236
- package/dist/{model-DR9odhWP.mjs → model-CyXvsWLz.mjs} +1 -1
- package/dist/operation-executor.mjs +3 -3
- package/dist/preview-worker.mjs +1 -1
- package/dist/privacy-validator.mjs +1 -1
- package/dist/{query-BYDcNKy2.mjs → query-BC7MIaWc.mjs} +335 -115
- package/dist/{run-DQ9xyKSt.mjs → run-Dod7Vykp.mjs} +6 -6
- package/dist/{src-DeNJ8vOD.mjs → src-CP-K2PBv.mjs} +1779 -1686
- package/dist/{src-Do-qzuMn.mjs → src-CttNuxOn.mjs} +590 -542
- package/dist/{src-CKQlTA2Z.mjs → src-dL6y2mcu.mjs} +180 -30
- package/dist/store-database-worker.mjs +1 -1
- package/package.json +1 -1
- package/skills/tacitt/SKILL.md +3 -3
- package/skills/tacitt/references/b-method.md +4 -2
- package/skills/tacitt/references/bootstrap.md +1 -1
- package/skills/tacitt/references/commands.md +10 -4
- package/skills/tacitt/references/journey.md +3 -3
- package/skills/tacitt/references/parallel-review.md +22 -9
- package/skills/tacitt/references/remote-workers.md +6 -2
- package/skills/tacitt/scripts/INDEX.md +4 -4
- package/skills/tacitt/scripts/answer.mjs +12 -1
- package/skills/tacitt/scripts/describe.mjs +3 -2
- package/skills/tacitt/scripts/journey-classification.mjs +1 -0
- package/skills/tacitt/scripts/journey-scan-timing.mjs +3 -2
- package/skills/tacitt/scripts/journey.mjs +1 -1
- package/skills/tacitt/scripts/privacy-validator.mjs +84 -33
- package/skills/tacitt/scripts/review.d.mts +1 -0
- package/skills/tacitt/scripts/review.mjs +296 -35
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { A as
|
|
3
|
-
import { $t as
|
|
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-
|
|
5
|
-
import { An as scanExclusionResourceSchema, B as createApplication, Bt as newJourneyWork, Ci as
|
|
6
|
-
import { vt as isCoordinationBusy } from "./heap-
|
|
7
|
-
import { d as checkOpenSource, h as openSource } from "./run-
|
|
8
|
-
import { t as createAgentSetupPort } from "./agent-setup-
|
|
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((
|
|
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
|
-
|
|
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
|
-
|
|
800
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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")))
|
|
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:
|
|
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
|
-
/**
|
|
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:
|
|
4966
|
+
role: "main",
|
|
4812
4967
|
after,
|
|
4813
|
-
limit:
|
|
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 ===
|
|
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
|
|
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) +
|
|
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
|
-
|
|
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,
|
|
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();
|