tonedial 0.1.5 → 0.1.7

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.
@@ -1,4 +1,4 @@
1
- import { ANCHORS, AXIS_ORDER, CONTROLS, FORMAT_COMPOSITION, GENERATION_COMMENT, PERSONA, REGISTER_COMPOSITION, SCOPE_HEADER, SPEECH_LEVEL, STYLE_MARKER, } from "./anchors.js";
1
+ import { ANCHORS, AXIS_ORDER, CONTROLS, DELIVERY_OFF_MARKER, FORMAT_COMPOSITION, GENERATION_COMMENT, PERSONA, REGISTER_COMPOSITION, SCOPE_HEADER, SPEECH_LEVEL, STYLE_MARKER, } from "./anchors.js";
2
2
  import { composites, intensityConfirmation, PERSONA_DESCRIPTION_MAX } from "./combos.js";
3
3
  const UNSPECIFIED = "unspecified";
4
4
  const isSet = (v) => typeof v === "string" && v.trim() !== "" && v !== UNSPECIFIED;
@@ -29,6 +29,9 @@ function personaLines(p) {
29
29
  return lines;
30
30
  }
31
31
  function validate(config) {
32
+ if (config.enabled !== undefined && typeof config.enabled !== "boolean") {
33
+ throw new RangeError("enabled must be true or false");
34
+ }
32
35
  for (const axis of AXIS_ORDER) {
33
36
  const level = config.axes[axis];
34
37
  if (!Number.isInteger(level) || level < 1 || level > 5) {
@@ -59,8 +62,10 @@ export function compile(config) {
59
62
  const out = [
60
63
  GENERATION_COMMENT(config.preset),
61
64
  STYLE_MARKER(config.axes.verbosity, config.axes.care),
62
- SCOPE_HEADER,
63
65
  ];
66
+ if (config.enabled === false)
67
+ out.push(DELIVERY_OFF_MARKER);
68
+ out.push(SCOPE_HEADER);
64
69
  if (config.persona) {
65
70
  const lines = personaLines(config.persona);
66
71
  if (lines.length)
@@ -17,6 +17,7 @@ export const RESOURCE_TYPES = ["file", "directory", "symlink"];
17
17
  export function defaultConfig() {
18
18
  return {
19
19
  schema: CONFIG_SCHEMA,
20
+ enabled: true,
20
21
  preset: null,
21
22
  unsaved: false,
22
23
  axes: { ...AXIS_DEFAULT },
@@ -512,7 +513,7 @@ export function parseConfig(text, t = catalogue("en").cli) {
512
513
  // An unknown key is refused by name rather than silently dropped on the next write. This
513
514
  // file is user-editable, and a tool that eats what a person typed has no business asking
514
515
  // them to trust it with someone else's files later.
515
- const KNOWN = ["schema", "preset", "unsaved", "axes", "persona", "safeword", "guards", "fixed_width",
516
+ const KNOWN = ["schema", "enabled", "preset", "unsaved", "axes", "persona", "safeword", "guards", "fixed_width",
516
517
  "speech_level", "language", "seedVersion", "managedSeeds", "wiring"];
517
518
  const unknown = Object.keys(raw).filter((k) => !KNOWN.includes(k));
518
519
  if (unknown.length)
@@ -541,6 +542,7 @@ export function parseConfig(text, t = catalogue("en").cli) {
541
542
  }
542
543
  }
543
544
  const common = {
545
+ enabled: raw.enabled === undefined ? true : requiredBoolean(raw.enabled, "enabled", t),
544
546
  preset: asString(raw.preset, "preset", t),
545
547
  unsaved: raw.unsaved === true,
546
548
  axes: readAxes(raw.axes, t),
@@ -569,6 +571,9 @@ export function serializeConfig(config, t = catalogue("en").cli) {
569
571
  const wiring = readWiringV2(current.wiring, t);
570
572
  const text = JSON.stringify({
571
573
  schema: CONFIG_SCHEMA,
574
+ // On is the historical default. Omit it so ordinary writes retain the released shape;
575
+ // only the meaningful paused state needs a persisted byte.
576
+ ...(current.enabled ? {} : { enabled: false }),
572
577
  preset: current.preset,
573
578
  unsaved: current.unsaved,
574
579
  axes,
@@ -599,6 +604,7 @@ export function prepareApprovedMigration(legacy, t = catalogue("en").cli) {
599
604
  return {
600
605
  config: {
601
606
  schema: CONFIG_SCHEMA,
607
+ enabled: legacy.enabled,
602
608
  preset: legacy.preset,
603
609
  unsaved: legacy.unsaved,
604
610
  axes: { ...legacy.axes },
@@ -617,7 +623,7 @@ export function prepareApprovedMigration(legacy, t = catalogue("en").cli) {
617
623
  },
618
624
  };
619
625
  }
620
- export const USER_SETTING_KEYS = ["preset", "unsaved", "axes", "persona", "safeword", "speech_level", "language"];
626
+ export const USER_SETTING_KEYS = ["enabled", "preset", "unsaved", "axes", "persona", "safeword", "speech_level", "language"];
621
627
  /** Recover supported user values only. Opaque delivery metadata never becomes new authority. */
622
628
  export function recoverSettings(text, t = catalogue("en").cli) {
623
629
  let raw;
package/dist/core/i18n.js CHANGED
@@ -37,6 +37,14 @@ const enMessages = {
37
37
  launchFailed: (provider) => `Could not start ${provider}. Check its installation and try again.`,
38
38
  selectionCancelled: "No provider selected.",
39
39
  },
40
+ delivery: {
41
+ on: "ToneDial on · delivery marker cleared",
42
+ off: "ToneDial off · delivery marker saved · settings and TONE.md kept",
43
+ alreadyOn: "ToneDial already on · no changes",
44
+ alreadyOff: "ToneDial already off · no changes",
45
+ openConversation: "Open conversations may retain an earlier tone. Use a new conversation for a clean no-ToneDial comparison.",
46
+ status: (enabled) => `ToneDial ${enabled ? "on" : "off"}`,
47
+ },
40
48
  axis: {
41
49
  range: (axis, given) => `${axis}: expected 1–5 (got ${given})`,
42
50
  unknown: (name, nearest) => `${name}: not an axis${nearest ? `. Did you mean ${nearest}?` : ""}`,
@@ -112,7 +120,7 @@ const enMessages = {
112
120
  choosePreset: "Choose a preset for values with no valid saved source:",
113
121
  sourceRequired: "Saved values are incomplete. Run tonedial doctor --fix interactively, or select --from-preset SLUG or --from-operation ID before unattended recovery.",
114
122
  sourceInvalid: "The selected source cannot supply valid settings. The original was kept.",
115
- invalidOptions: "Recovery source and tool options require doctor --fix. --restore-preset requires --from-operation; --rollback cannot be combined with source, preset, or tool choices.",
123
+ invalidOptions: "Recovery source, quarantine and tool options require doctor --fix. --restore-preset requires --from-operation; --rollback cannot be combined with quarantine, source, preset, or tool choices.",
116
124
  keptSettings: "Saved settings and custom presets kept.",
117
125
  obsoleteWidthDiscarded: "Discard invalid retired fixed_width input. Output follows terminal width.",
118
126
  obsoleteGuardsDiscarded: "Discard invalid retired guards input. ToneDial no longer adds guard requests.",
@@ -125,6 +133,13 @@ const enMessages = {
125
133
  rollback: (id) => `Restore the previous files:\ntonedial doctor --fix --rollback ${id}`,
126
134
  rollbackOrder: "Restore connections first, then application files:",
127
135
  noSnapshot: (id) => `${id}: no valid settings snapshot for this state root`,
136
+ quarantineCandidate: (id) => `Operation ID:\n${id}\nQuarantine it before rebuilding; the original bytes remain available through rollback.`,
137
+ quarantineQuestion: "Type the full operation ID above to quarantine it; Enter cancels:",
138
+ quarantineRequired: (id, command) => `${id}: this unreadable operation record needs explicit quarantine before recovery. No files were changed.\n${command}`,
139
+ quarantineUnavailable: (id) => `${id}: no matching unreadable operation record can be quarantined. No files were changed.`,
140
+ quarantine: (id) => `Quarantine unreadable operation ${id}; keep its original bytes in rollback history.`,
141
+ quarantineComplete: (id) => `Unreadable operation ${id} quarantined.`,
142
+ quarantineRemaining: (count) => `${count} unreadable operation ${count === 1 ? "record remains" : "records remain"}. Quarantine the next record before rebuilding.`,
128
143
  retire: (id) => `Retire stopped operation ${id} as history before a new application rebuild. Its external changes are not certified or undone.`,
129
144
  resumeOperation: (id) => `Stopped operation:\n${id}`,
130
145
  resumeQuestion: "Type the full operation ID above to continue; Enter cancels:",
@@ -280,6 +295,7 @@ const enMessages = {
280
295
  seedsNotRun: "no bundled files · init has not run here",
281
296
  seedsAttention: "bundled files need attention",
282
297
  recoveryPending: "An operation is active or needs recovery. No files were changed by this command.",
298
+ invalidRecoveryPending: "Unreadable operation history is blocking recovery. No files were changed by this command.",
283
299
  resumeOperation: "Continue application repair:",
284
300
  contextCurrent: "AGENTS.md and TONE.md layout is current.",
285
301
  contextRepair: "Managed context files need repair. Check the guide and tone-file diagnostics.",
@@ -302,7 +318,7 @@ const enMessages = {
302
318
  changed: "Reader or config changed during inspection. Retry after changes stop.",
303
319
  probe: "The controlled probe failed. Check Node and temporary-directory access, then rerun doctor.",
304
320
  },
305
- operationState: { pending: "pending", complete: "complete", "rolled-back": "restored", "retired-for-app-rebuild": "retired for rebuild", invalid: "invalid record" },
321
+ operationState: { pending: "pending", complete: "complete", "rolled-back": "restored", "retired-for-app-rebuild": "retired for rebuild", quarantined: "quarantined", invalid: "invalid record" },
306
322
  rollbackStatus: {
307
323
  ready: "Approval will restore the selected operation.",
308
324
  busy: "Operation still active. Wait for its owner to finish.",
@@ -435,6 +451,8 @@ const enMessages = {
435
451
  firstSteps: "Set up your tone, then inspect it:",
436
452
  examples: "Examples",
437
453
  commands: {
454
+ on: "resume saved tone delivery",
455
+ off: "pause tone delivery · keep settings and TONE.md",
438
456
  init: "set up and refresh bundled files",
439
457
  set: "change axes, role or settings · bare opens dial",
440
458
  get: "read current setting values · key adds its meaning and choices",
@@ -470,6 +488,7 @@ const enMessages = {
470
488
  tools: "agents to connect · init: none = state only",
471
489
  fix: "doctor: repair fixable findings",
472
490
  rollback: "doctor --fix: restore one operation",
491
+ quarantineOperation: "doctor --fix: quarantine one unreadable operation record",
473
492
  fromOperation: "doctor --fix: select saved settings from an operation",
474
493
  fromPreset: "doctor --fix: preset for unavailable settings",
475
494
  restorePreset: "doctor --fix: restore one missing or damaged user preset",
@@ -492,10 +511,9 @@ const enMessages = {
492
511
  errors: {
493
512
  notACommand: (name, known, nearest) => `${name} is not a command.${nearest ? ` Did you mean ${nearest}?` : ""} ` +
494
513
  `Available: ${known.join(", ")}`,
495
- noOffSwitch: "There is no off switch. If set, a safeword requests one plain answer; tonedial show reports " +
496
- "whether one exists. tonedial reset restores axis defaults. tonedial eject --global or " +
497
- "--project <path> disconnects that scope. Once all connections are gone, " +
498
- "npm uninstall -g tonedial removes the command.",
514
+ stoppingHelp: "Use tonedial off to pause tone delivery without deleting settings; tonedial on resumes it. " +
515
+ "tonedial reset restores axis defaults. tonedial eject --global or --project <path> " +
516
+ "disconnects that scope. Once all connections are gone, npm uninstall -g tonedial removes the command.",
499
517
  notAnOption: (flag) => `${flag}: not an option. List: tonedial --help`,
500
518
  flagNotAccepted: (command, flag) => `${command}: does not accept --${flag}`,
501
519
  tooManyArguments: (command, max, got) => `${command}: maximum ${max} argument${max === 1 ? "" : "s"}, got ${got}`,
@@ -691,6 +709,14 @@ const koMessages = {
691
709
  launchFailed: (provider) => `${provider}를 시작하지 못했습니다. 설치 상태를 확인하고 다시 시도해 주세요.`,
692
710
  selectionCancelled: "도구를 선택하지 않았습니다.",
693
711
  },
712
+ delivery: {
713
+ on: "ToneDial 켜짐 · 전달 중지 마커 제거",
714
+ off: "ToneDial 꺼짐 · 전달 중지 마커 저장 · 설정과 TONE.md 유지",
715
+ alreadyOn: "ToneDial이 이미 켜져 있음 · 변경 없음",
716
+ alreadyOff: "ToneDial이 이미 꺼져 있음 · 변경 없음",
717
+ openConversation: "열린 대화에는 이전 말투가 남을 수 있습니다. ToneDial이 없는 상태를 제대로 비교하려면 새 대화를 여세요.",
718
+ status: (enabled) => `ToneDial ${enabled ? "켜짐" : "꺼짐"}`,
719
+ },
694
720
  axis: {
695
721
  range: (axis, given) => `${axis}: 1~5만 쓸 수 있습니다(입력: ${given})`,
696
722
  unknown: (name, nearest) => `${name}: 축이 아닙니다${nearest ? `. ${nearest} 말씀이신가요?` : ""}`,
@@ -766,7 +792,7 @@ const koMessages = {
766
792
  choosePreset: "저장된 값이 없는 항목에 사용할 프리셋을 선택하세요:",
767
793
  sourceRequired: "저장된 값이 부족합니다. 터미널에서 tonedial doctor --fix를 실행하거나 자동 복구 전에 --from-preset SLUG 또는 --from-operation ID를 선택하세요.",
768
794
  sourceInvalid: "선택한 원본에서 올바른 설정을 읽지 못했습니다. 기존 파일은 유지했습니다.",
769
- invalidOptions: "복구 원본과 도구 옵션은 doctor --fix에서 쓰세요. --restore-preset에는 --from-operation이 필요하며, --rollback은 원본·프리셋·도구 선택과 함께 쓸 수 없습니다.",
795
+ invalidOptions: "복구 원본·격리·도구 옵션은 doctor --fix에서 쓰세요. --restore-preset에는 --from-operation이 필요하며, --rollback은 격리·원본·프리셋·도구 선택과 함께 쓸 수 없습니다.",
770
796
  keptSettings: "저장된 설정·사용자 프리셋 유지",
771
797
  obsoleteWidthDiscarded: "더 이상 쓰지 않는 fixed_width의 잘못된 값을 버립니다. 출력은 터미널 폭에 맞춥니다.",
772
798
  obsoleteGuardsDiscarded: "더 이상 쓰지 않는 guards의 잘못된 값을 버립니다. ToneDial은 가드 요청을 넣지 않습니다.",
@@ -779,6 +805,13 @@ const koMessages = {
779
805
  rollback: (id) => `이전 파일로 돌아가기:\ntonedial doctor --fix --rollback ${id}`,
780
806
  rollbackOrder: "연결 파일을 먼저 되돌린 뒤 앱 파일을 되돌리세요:",
781
807
  noSnapshot: (id) => `${id}: 이 상태 경로에서 쓸 수 있는 설정 기록이 없습니다`,
808
+ quarantineCandidate: (id) => `작업 ID:\n${id}\n앱을 복구하려면 이 기록을 먼저 격리해야 합니다. 원본 바이트는 롤백 기록에 보존됩니다.`,
809
+ quarantineQuestion: "격리하려면 위 작업 ID 전체를 입력하세요. Enter를 누르면 취소합니다:",
810
+ quarantineRequired: (id, command) => `${id}: 복구 전에 읽을 수 없는 작업 기록을 명시적으로 격리해야 합니다. 파일은 바꾸지 않았습니다.\n${command}`,
811
+ quarantineUnavailable: (id) => `${id}: 격리할 수 있는 읽기 불가 작업 기록을 찾지 못했습니다. 파일은 바꾸지 않았습니다.`,
812
+ quarantine: (id) => `읽을 수 없는 작업 ${id}를 격리합니다. 원본 바이트는 롤백 기록에 보존합니다.`,
813
+ quarantineComplete: (id) => `읽을 수 없는 작업 ${id}를 격리했습니다.`,
814
+ quarantineRemaining: (count) => `읽을 수 없는 작업 기록 ${count}개가 남았습니다. 다음 기록을 격리한 뒤 복구를 계속하세요.`,
782
815
  retire: (id) => `중단된 작업 ${id}를 기록으로 보관한 뒤 새 앱 복구를 시작합니다. 그 작업의 외부 파일 변경은 확인하거나 되돌리지 않습니다.`,
783
816
  resumeOperation: (id) => `중단된 작업:\n${id}`,
784
817
  resumeQuestion: "계속하려면 위 작업 ID 전체를 입력하세요. Enter를 누르면 취소합니다:",
@@ -937,6 +970,7 @@ const koMessages = {
937
970
  seedsNotRun: "기본 파일 없음 · init으로 준비하세요",
938
971
  seedsAttention: "기본 파일 확인 필요",
939
972
  recoveryPending: "진행 중이거나 복구가 필요한 작업이 있습니다. 이번 실행에서는 파일을 바꾸지 않았습니다.",
973
+ invalidRecoveryPending: "읽을 수 없는 작업 기록 때문에 복구를 진행할 수 없습니다. 이번 실행에서는 파일을 바꾸지 않았습니다.",
940
974
  resumeOperation: "앱 복구를 이어가려면:",
941
975
  contextCurrent: "AGENTS.md와 TONE.md 구성이 최신입니다.",
942
976
  contextRepair: "관리 중인 컨텍스트 파일을 복구해야 합니다. 안내 파일과 톤 파일의 진단을 확인해 주세요.",
@@ -959,7 +993,7 @@ const koMessages = {
959
993
  changed: "검사 중 전달 스크립트나 설정이 바뀌었습니다. 변경이 끝난 뒤 다시 시도해 주세요.",
960
994
  probe: "제어된 검사에 실패했습니다. Node와 임시 디렉터리 접근 권한을 확인한 뒤 doctor를 다시 실행해 주세요.",
961
995
  },
962
- operationState: { pending: "미완료", complete: "완료", "rolled-back": "복구됨", "retired-for-app-rebuild": "앱 복구를 위해 보관됨", invalid: "잘못된 기록" },
996
+ operationState: { pending: "미완료", complete: "완료", "rolled-back": "복구됨", "retired-for-app-rebuild": "앱 복구를 위해 보관됨", quarantined: "격리됨", invalid: "잘못된 기록" },
963
997
  rollbackStatus: {
964
998
  ready: "승인하면 선택한 작업을 복구합니다.",
965
999
  busy: "아직 진행 중인 작업입니다. 작업이 끝날 때까지 기다려 주세요.",
@@ -1092,6 +1126,8 @@ const koMessages = {
1092
1126
  firstSteps: "말투를 설정한 뒤 확인하세요:",
1093
1127
  examples: "사용 예",
1094
1128
  commands: {
1129
+ on: "저장한 말투 전달 재개",
1130
+ off: "말투 전달 일시정지 · 설정·TONE.md 유지",
1095
1131
  init: "초기 설정과 기본 파일 갱신",
1096
1132
  set: "축·역할·설정 변경 · 인자 없으면 다이얼",
1097
1133
  get: "현재 설정값 조회 · 키를 지정하면 의미와 선택지도 표시",
@@ -1127,6 +1163,7 @@ const koMessages = {
1127
1163
  tools: "연결할 에이전트 · init에서 none은 상태만 준비",
1128
1164
  fix: "doctor: 고칠 수 있는 문제 복구",
1129
1165
  rollback: "doctor --fix: 작업 하나 복구",
1166
+ quarantineOperation: "doctor --fix: 읽을 수 없는 작업 기록 하나 격리",
1130
1167
  fromOperation: "doctor --fix: 작업 기록에서 설정 선택",
1131
1168
  fromPreset: "doctor --fix: 없는 설정에 사용할 프리셋",
1132
1169
  restorePreset: "doctor --fix: 없거나 손상된 사용자 프리셋 하나 복원",
@@ -1149,10 +1186,9 @@ const koMessages = {
1149
1186
  errors: {
1150
1187
  notACommand: (name, known, nearest) => `${name}: 명령이 아닙니다.${nearest ? ` ${nearest} 말씀이신가요?` : ""} ` +
1151
1188
  `사용 가능: ${known.join(", ")}`,
1152
- noOffSwitch: "끄는 스위치는 없습니다. 세이프워드를 정했다면 한 메시지만 담백하게 답해 달라고 요청할 수 " +
1153
- "있습니다. 설정 여부는 tonedial show에서 확인하세요. tonedial reset은 축을 기본값으로 " +
1154
- "돌립니다. tonedial eject --global이나 --project <path>는 그 범위의 연결을 해제합니다. " +
1155
- "연결을 모두 해제한 뒤 npm uninstall -g tonedial로 명령을 삭제할 수 있습니다.",
1189
+ stoppingHelp: "설정을 지우지 않고 말투 전달만 멈추려면 tonedial off를 실행하세요. tonedial on으로 다시 켤 수 있습니다. " +
1190
+ "tonedial reset은 축을 기본값으로 돌립니다. tonedial eject --global이나 --project <path>는 " +
1191
+ "해당 범위의 연결을 해제합니다. 연결을 모두 해제한 뒤 npm uninstall -g tonedial로 명령을 삭제할 수 있습니다.",
1156
1192
  notAnOption: (flag) => `${flag}: 옵션이 아닙니다. 목록: tonedial --help`,
1157
1193
  flagNotAccepted: (command, flag) => `${command}: --${flag} 옵션을 받지 않습니다`,
1158
1194
  tooManyArguments: (command, max, got) => `${command}: 인자는 최대 ${max}개지만 ${got}개를 받았습니다`,
@@ -1,5 +1,5 @@
1
1
  // Decode known current and historical English artifact bodies. Unknown prose is never a data source.
2
- import { ANCHORS, AXIS_DEFAULT, AXIS_ORDER, CONTROLS, FORMAT_COMPOSITION, PERSONA, REGISTER_COMPOSITION, SCOPE_HEADER, SPEECH_LEVEL, } from "./anchors.js";
2
+ import { ANCHORS, AXIS_DEFAULT, AXIS_ORDER, CONTROLS, DELIVERY_OFF_MARKER, FORMAT_COMPOSITION, PERSONA, REGISTER_COMPOSITION, SCOPE_HEADER, SPEECH_LEVEL, } from "./anchors.js";
3
3
  import { AGE_BANDS, GENDERS, defaultConfig, recoverSettings, safewordRejection, } from "./config.js";
4
4
  import { composites, intensityConfirmation } from "./combos.js";
5
5
  // Historical producer text is decoder evidence only, never a current tone instruction.
@@ -355,11 +355,16 @@ export function recoverLegacyTone(text, t) {
355
355
  const styleMarker = /^<!-- tonedial-style:1 verbosity=([1-5]) care=([1-5]) -->\n/.exec(afterGeneration);
356
356
  if (afterGeneration.startsWith("<!-- tonedial-style:") && styleMarker === null)
357
357
  return null;
358
- const prefix = `${marker[0]}${styleMarker?.[0] ?? ""}${SCOPE_HEADER}\n`;
358
+ const afterStyle = afterGeneration.slice(styleMarker?.[0].length ?? 0);
359
+ const deliveryMarker = afterStyle.startsWith(`${DELIVERY_OFF_MARKER}\n`) ? `${DELIVERY_OFF_MARKER}\n` : "";
360
+ if (afterStyle.startsWith("<!-- tonedial-delivery:") && deliveryMarker === "")
361
+ return null;
362
+ const prefix = `${marker[0]}${styleMarker?.[0] ?? ""}${deliveryMarker}${SCOPE_HEADER}\n`;
359
363
  if (!source.startsWith(prefix))
360
364
  return null;
361
365
  let body = source.slice(prefix.length);
362
366
  const config = defaultConfig();
367
+ config.enabled = deliveryMarker !== "" ? false : true;
363
368
  const unknown = [];
364
369
  if (body.startsWith("### Persona\n")) {
365
370
  const end = body.indexOf("\n### Rules\n");
@@ -26,6 +26,14 @@ const messages = {
26
26
  launchFailed: (provider) => `No se pudo iniciar ${provider}. Comprueba su instalación y vuelve a intentarlo.`,
27
27
  selectionCancelled: "No se eligió ningún proveedor.",
28
28
  },
29
+ delivery: {
30
+ on: "ToneDial activado · marcador de pausa eliminado",
31
+ off: "ToneDial desactivado · marcador de pausa guardado · se conservan los ajustes y TONE.md",
32
+ alreadyOn: "La entrega del tono ya está activada · sin cambios",
33
+ alreadyOff: "La entrega del tono ya está desactivada · sin cambios",
34
+ openConversation: "Las conversaciones abiertas pueden conservar el tono anterior. Usa una conversación nueva para comparar el estado sin ToneDial.",
35
+ status: (enabled) => `Entrega del tono ${enabled ? "activada" : "desactivada"}`,
36
+ },
29
37
  axis: {
30
38
  range: (axis, given) => `${axis}: se esperaba 1–5 (se recibió ${given})`,
31
39
  unknown: (name, nearest) => `${name}: no es un eje${nearest ? `. ¿Querías decir ${nearest}?` : ""}`,
@@ -101,7 +109,7 @@ const messages = {
101
109
  choosePreset: "Elige un preajuste para los valores sin un origen guardado válido:",
102
110
  sourceRequired: "Los valores guardados están incompletos. Ejecuta tonedial doctor --fix de forma interactiva o selecciona --from-preset SLUG o --from-operation ID antes de recuperar sin interacción.",
103
111
  sourceInvalid: "El origen elegido no proporciona ajustes válidos. Se conservó el original.",
104
- invalidOptions: "El origen de recuperación y las herramientas requieren doctor --fix. --restore-preset requiere --from-operation; --rollback no se combina con origen, preajuste ni herramientas.",
112
+ invalidOptions: "El origen, la cuarentena y las herramientas requieren doctor --fix. --restore-preset requiere --from-operation; --rollback no se combina con cuarentena, origen, preajuste ni herramientas.",
105
113
  keptSettings: "Se conservaron los ajustes y preajustes personalizados.",
106
114
  obsoleteWidthDiscarded: "Se descarta el valor no válido del antiguo fixed_width. La salida se adapta al ancho del terminal.",
107
115
  obsoleteGuardsDiscarded: "Se descarta el valor no válido del antiguo guards. ToneDial ya no añade solicitudes de protección.",
@@ -114,6 +122,13 @@ const messages = {
114
122
  rollback: (id) => `Restaura los archivos anteriores:\ntonedial doctor --fix --rollback ${id}`,
115
123
  rollbackOrder: "Restaura primero las conexiones y después los archivos de la aplicación:",
116
124
  noSnapshot: (id) => `${id}: no hay una instantánea válida para este directorio de estado`,
125
+ quarantineCandidate: (id) => `ID de operación:\n${id}\nPara reconstruir, primero debes poner este registro en cuarentena; los bytes originales quedarán en el historial de reversión.`,
126
+ quarantineQuestion: "Escribe el ID completo de arriba para ponerlo en cuarentena; Enter cancela:",
127
+ quarantineRequired: (id, command) => `${id}: debes poner en cuarentena este registro ilegible antes de recuperar. No se cambió ningún archivo.\n${command}`,
128
+ quarantineUnavailable: (id) => `${id}: no hay un registro de operación ilegible que se pueda poner en cuarentena. No se cambió ningún archivo.`,
129
+ quarantine: (id) => `Pon en cuarentena la operación ilegible ${id}; conserva sus bytes originales en el historial de reversión.`,
130
+ quarantineComplete: (id) => `La operación ilegible ${id} quedó en cuarentena.`,
131
+ quarantineRemaining: (count) => `${count === 1 ? "Queda 1 registro de operación ilegible" : `Quedan ${count} registros de operación ilegibles`}. Pon el siguiente en cuarentena antes de continuar la recuperación.`,
117
132
  retire: (id) => `Archiva la operación detenida ${id} antes de reconstruir. Sus cambios externos no se certifican ni se deshacen.`,
118
133
  resumeOperation: (id) => `Operación detenida:\n${id}`,
119
134
  resumeQuestion: "Para continuar, escribe el ID completo de arriba; Enter cancela:",
@@ -269,6 +284,7 @@ const messages = {
269
284
  seedsNotRun: "sin archivos incluidos · aquí no se ha ejecutado init",
270
285
  seedsAttention: "los archivos incluidos requieren atención",
271
286
  recoveryPending: "Hay una operación en curso o que necesita recuperación. Esta ejecución no cambió archivos.",
287
+ invalidRecoveryPending: "Un historial de operaciones ilegible está bloqueando la recuperación. Esta ejecución no cambió archivos.",
272
288
  resumeOperation: "Continuar la reparación de la aplicación:",
273
289
  contextCurrent: "La estructura de AGENTS.md y TONE.md está al día.",
274
290
  contextRepair: "Los archivos de contexto gestionados necesitan reparación. Revisa el diagnóstico de la guía y del tono.",
@@ -291,7 +307,7 @@ const messages = {
291
307
  changed: "El lector o los ajustes cambiaron durante la inspección. Reintenta cuando terminen los cambios.",
292
308
  probe: "Falló la comprobación controlada. Revisa Node y el acceso al directorio temporal; después repite doctor.",
293
309
  },
294
- operationState: { pending: "pendiente", complete: "completa", "rolled-back": "restaurada", "retired-for-app-rebuild": "archivada para reconstruir", invalid: "registro no válido" },
310
+ operationState: { pending: "pendiente", complete: "completa", "rolled-back": "restaurada", "retired-for-app-rebuild": "archivada para reconstruir", quarantined: "en cuarentena", invalid: "registro no válido" },
295
311
  rollbackStatus: {
296
312
  ready: "La aprobación restaurará la operación elegida.",
297
313
  busy: "La operación sigue activa. Espera a que termine su propietario.",
@@ -420,6 +436,8 @@ const messages = {
420
436
  firstSteps: "Configura el tono y revísalo:",
421
437
  examples: "Ejemplos",
422
438
  commands: {
439
+ on: "reanudar la entrega del tono guardado",
440
+ off: "pausar la entrega · conservar ajustes y TONE.md",
423
441
  init: "configurar y actualizar archivos incluidos",
424
442
  set: "cambiar ejes, rol o ajustes · sin argumentos abre el dial",
425
443
  get: "consultar valores actuales · una clave muestra su significado y opciones",
@@ -455,6 +473,7 @@ const messages = {
455
473
  tools: "agentes que conectar · init: none solo crea el estado",
456
474
  fix: "doctor: reparar los problemas que se puedan",
457
475
  rollback: "doctor --fix: restaurar una operación",
476
+ quarantineOperation: "doctor --fix: poner en cuarentena un registro ilegible",
458
477
  fromOperation: "doctor --fix: elegir ajustes guardados de una operación",
459
478
  fromPreset: "doctor --fix: preajuste para ajustes no disponibles",
460
479
  restorePreset: "doctor --fix: restaurar un preajuste ausente o dañado",
@@ -477,10 +496,9 @@ const messages = {
477
496
  errors: {
478
497
  notACommand: (name, known, nearest) => `${name} no es un comando.${nearest ? ` ¿Querías decir ${nearest}?` : ""} ` +
479
498
  `Disponibles: ${known.join(", ")}`,
480
- noOffSwitch: "No hay interruptor de apagado. Si está definida, la palabra clave pide una respuesta sencilla; tonedial show indica " +
481
- "si existe. tonedial reset restablece los ejes. tonedial eject --global o " +
482
- "--project <path> desconecta ese ámbito. Tras quitar todas las conexiones, " +
483
- "npm uninstall -g tonedial elimina el comando.",
499
+ stoppingHelp: "Usa tonedial off para pausar la entrega sin borrar los ajustes y tonedial on para reanudarla. " +
500
+ "tonedial reset restablece los ejes. tonedial eject --global o --project <path> desconecta ese ámbito. " +
501
+ "Tras quitar todas las conexiones, npm uninstall -g tonedial elimina el comando.",
484
502
  notAnOption: (flag) => `${flag}: no es una opción. Lista: tonedial --help`,
485
503
  flagNotAccepted: (command, flag) => `${command}: no admite --${flag}`,
486
504
  tooManyArguments: (command, max, got) => `${command}: máximo ${max} argumento${max === 1 ? "" : "s"}; se recibieron ${got}`,
@@ -26,6 +26,14 @@ const messages = {
26
26
  launchFailed: (provider) => `${provider} を起動できません。インストール状況を確認して再試行してください。`,
27
27
  selectionCancelled: "プロバイダーが選択されていません。",
28
28
  },
29
+ delivery: {
30
+ on: "ToneDialをオンにしました · 配信停止マーカーを削除",
31
+ off: "ToneDialをオフにしました · 配信停止マーカーを保存 · 設定とTONE.mdは保持",
32
+ alreadyOn: "口調の配信はすでにオンです · 変更なし",
33
+ alreadyOff: "口調の配信はすでにオフです · 変更なし",
34
+ openConversation: "開いている会話には以前の口調が残る場合があります。ToneDialなしの状態と正確に比較するには新しい会話を使ってください。",
35
+ status: (enabled) => `口調の配信:${enabled ? "オン" : "オフ"}`,
36
+ },
29
37
  axis: {
30
38
  range: (axis, given) => `${axis}: 1–5を指定してください(入力:${given})`,
31
39
  unknown: (name, nearest) => `${name}: 軸ではありません${nearest ? `。${nearest} のことですか?` : ""}`,
@@ -101,7 +109,7 @@ const messages = {
101
109
  choosePreset: "有効な保存元がない値の復元用プリセットを選んでください:",
102
110
  sourceRequired: "保存値が不完全です。tonedial doctor --fix を対話的に実行するか、無人復旧の前に --from-preset SLUG または --from-operation ID を選んでください。",
103
111
  sourceInvalid: "選択元から有効な設定を取得できません。元のファイルは保持しました。",
104
- invalidOptions: "復旧元とツールの指定には doctor --fix が必要です。--restore-preset には --from-operation が必要です。--rollback は復旧元、プリセット、ツール指定と併用できません。",
112
+ invalidOptions: "復旧元、隔離、ツールの指定には doctor --fix が必要です。--restore-preset には --from-operation が必要です。--rollback は隔離、復旧元、プリセット、ツール指定と併用できません。",
105
113
  keptSettings: "保存済み設定とカスタムプリセットを保持しました。",
106
114
  obsoleteWidthDiscarded: "廃止済み fixed_width の無効な値を破棄します。出力はターミナル幅に従います。",
107
115
  obsoleteGuardsDiscarded: "廃止済み guards の無効な値を破棄します。ToneDialはガード要求を追加しません。",
@@ -114,6 +122,13 @@ const messages = {
114
122
  rollback: (id) => `以前のファイルに戻す:\ntonedial doctor --fix --rollback ${id}`,
115
123
  rollbackOrder: "先に接続を復元し、その後アプリケーションのファイルを復元してください:",
116
124
  noSnapshot: (id) => `${id}: この状態ルート用の有効な設定スナップショットがありません`,
125
+ quarantineCandidate: (id) => `操作ID:\n${id}\n再構築するには、先にこの記録を隔離する必要があります。元のバイト列はロールバック履歴に保持されます。`,
126
+ quarantineQuestion: "隔離するには上の操作ID全体を入力してください。Enterで中止:",
127
+ quarantineRequired: (id, command) => `${id}: 復旧前に、読み取れない操作記録を明示的に隔離する必要があります。ファイルは変更していません。\n${command}`,
128
+ quarantineUnavailable: (id) => `${id}: 隔離できる読み取り不能な操作記録が見つかりません。ファイルは変更していません。`,
129
+ quarantine: (id) => `読み取れない操作 ${id} を隔離し、元のバイト列をロールバック履歴に保持します。`,
130
+ quarantineComplete: (id) => `読み取れない操作 ${id} を隔離しました。`,
131
+ quarantineRemaining: (count) => `読み取れない操作記録が ${count} 件残っています。次の記録を隔離してから復旧を続けてください。`,
117
132
  retire: (id) => `停止した操作 ${id} を履歴に移してから再構築してください。外部への変更は検証も取り消しもされません。`,
118
133
  resumeOperation: (id) => `停止した操作:\n${id}`,
119
134
  resumeQuestion: "続行するには上の操作ID全体を入力してください。Enterで中止:",
@@ -269,6 +284,7 @@ const messages = {
269
284
  seedsNotRun: "同梱ファイルなし · ここでは init 未実行",
270
285
  seedsAttention: "同梱ファイルの確認が必要",
271
286
  recoveryPending: "実行中または復旧が必要な操作があります。この実行ではファイルを変更していません。",
287
+ invalidRecoveryPending: "読み取れない操作履歴が復旧を妨げています。この実行ではファイルを変更していません。",
272
288
  resumeOperation: "アプリ復旧を続けるには:",
273
289
  contextCurrent: "AGENTS.md と TONE.md の構成は最新です。",
274
290
  contextRepair: "管理対象のコンテキストファイルに修復が必要です。ガイドと口調ファイルの診断を確認してください。",
@@ -291,7 +307,7 @@ const messages = {
291
307
  changed: "確認中にリーダーか設定が変更されました。変更が終わってから再試行してください。",
292
308
  probe: "制御下の確認に失敗しました。Nodeと一時ディレクトリへのアクセスを確認し、doctor を再実行してください。",
293
309
  },
294
- operationState: { pending: "保留中", complete: "完了", "rolled-back": "復元済み", "retired-for-app-rebuild": "再構築用に履歴へ移行", invalid: "無効な記録" },
310
+ operationState: { pending: "保留中", complete: "完了", "rolled-back": "復元済み", "retired-for-app-rebuild": "再構築用に履歴へ移行", quarantined: "隔離済み", invalid: "無効な記録" },
295
311
  rollbackStatus: {
296
312
  ready: "承認すると選択した操作を復元します。",
297
313
  busy: "まだ実行中です。所有者の完了を待ってください。",
@@ -420,6 +436,8 @@ const messages = {
420
436
  firstSteps: "口調を設定し、確認します:",
421
437
  examples: "使用例",
422
438
  commands: {
439
+ on: "保存した口調の配信を再開",
440
+ off: "口調の配信を一時停止 · 設定とTONE.mdは保持",
423
441
  init: "初期設定と同梱ファイルの更新",
424
442
  set: "軸・役割・設定を変更 · 引数なしでダイヤル",
425
443
  get: "現在の設定値を確認 · キー指定で意味と選択肢も表示",
@@ -455,6 +473,7 @@ const messages = {
455
473
  tools: "接続するエージェント · init: none は状態のみ",
456
474
  fix: "doctor: 修復可能な問題を修復",
457
475
  rollback: "doctor --fix: 操作を1つ復元",
476
+ quarantineOperation: "doctor --fix: 読み取れない操作記録を1つ隔離",
458
477
  fromOperation: "doctor --fix: 操作から保存済み設定を選択",
459
478
  fromPreset: "doctor --fix: 不足した設定用のプリセット",
460
479
  restorePreset: "doctor --fix: 不足・破損したユーザープリセットを復元",
@@ -477,10 +496,9 @@ const messages = {
477
496
  errors: {
478
497
  notACommand: (name, known, nearest) => `${name} はコマンドではありません。${nearest ? `${nearest} のことですか?` : ""} ` +
479
498
  `選択肢:${known.join(", ")}`,
480
- noOffSwitch: "オフスイッチはありません。合言葉は一度だけ平易な回答を求めます。設定の有無は tonedial show で確認できます。" +
481
- "tonedial reset は軸の既定値を復元します。tonedial eject --global または " +
482
- "--project <path> でその範囲を解除します。全接続を解除した後、" +
483
- "npm uninstall -g tonedial でコマンドを削除できます。",
499
+ stoppingHelp: "設定を削除せずに口調の配信を止めるには tonedial off を実行し、再開するには tonedial on を実行してください。" +
500
+ "tonedial reset は軸の既定値を復元します。tonedial eject --global または --project <path> でその範囲を解除します。" +
501
+ "全接続を解除した後、npm uninstall -g tonedial でコマンドを削除できます。",
484
502
  notAnOption: (flag) => `${flag}: オプションではありません。一覧:tonedial --help`,
485
503
  flagNotAccepted: (command, flag) => `${command}: --${flag} は使えません`,
486
504
  tooManyArguments: (command, max, got) => `${command}: 引数は最大 ${max}個、入力は ${got}個です`,
@@ -26,6 +26,14 @@ const messages = {
26
26
  launchFailed: (provider) => `无法启动 ${provider}。请检查安装情况后重试。`,
27
27
  selectionCancelled: "未选择服务商。",
28
28
  },
29
+ delivery: {
30
+ on: "ToneDial 已开启 · 已清除暂停投递标记",
31
+ off: "ToneDial 已关闭 · 已保存暂停投递标记 · 保留设置和 TONE.md",
32
+ alreadyOn: "语气投递已处于开启状态 · 无变更",
33
+ alreadyOff: "语气投递已处于关闭状态 · 无变更",
34
+ openConversation: "已打开的对话可能保留之前的语气。请用新对话准确比较未使用 ToneDial 的状态。",
35
+ status: (enabled) => `语气投递${enabled ? "已开启" : "已关闭"}`,
36
+ },
29
37
  axis: {
30
38
  range: (axis, given) => `${axis}:应为 1–5(收到 ${given})`,
31
39
  unknown: (name, nearest) => `${name}:不是语气轴${nearest ? `。是否想输入 ${nearest}?` : ""}`,
@@ -101,7 +109,7 @@ const messages = {
101
109
  choosePreset: "请为缺少有效保存来源的值选择预设:",
102
110
  sourceRequired: "保存值不完整。请交互运行 tonedial doctor --fix,或在无人值守恢复前选择 --from-preset SLUG 或 --from-operation ID。",
103
111
  sourceInvalid: "所选来源无法提供有效设置,已保留原文件。",
104
- invalidOptions: "恢复来源和工具选项需要 doctor --fix。--restore-preset 需要 --from-operation;--rollback 不能与来源、预设或工具选项同时使用。",
112
+ invalidOptions: "恢复来源、隔离和工具选项需要 doctor --fix。--restore-preset 需要 --from-operation;--rollback 不能与隔离、来源、预设或工具选项同时使用。",
105
113
  keptSettings: "已保留保存的设置和自定义预设。",
106
114
  obsoleteWidthDiscarded: "丢弃已停用的无效 fixed_width 值。输出将跟随终端宽度。",
107
115
  obsoleteGuardsDiscarded: "丢弃已停用的无效 guards 值。ToneDial 不再添加防护请求。",
@@ -114,6 +122,13 @@ const messages = {
114
122
  rollback: (id) => `恢复之前的文件:\ntonedial doctor --fix --rollback ${id}`,
115
123
  rollbackOrder: "请先恢复连接,再恢复应用文件:",
116
124
  noSnapshot: (id) => `${id}:没有此状态根目录的有效设置快照`,
125
+ quarantineCandidate: (id) => `操作 ID:\n${id}\n要重建应用,必须先隔离这条记录;原始字节仍保存在回滚历史中。`,
126
+ quarantineQuestion: "请输入上方完整的操作 ID 以隔离该记录;按 Enter 取消:",
127
+ quarantineRequired: (id, command) => `${id}:恢复前必须明确隔离这条无法读取的操作记录。未更改文件。\n${command}`,
128
+ quarantineUnavailable: (id) => `${id}:找不到可隔离的无法读取操作记录。未更改文件。`,
129
+ quarantine: (id) => `隔离无法读取的操作 ${id};原始字节保留在回滚历史中。`,
130
+ quarantineComplete: (id) => `已隔离无法读取的操作 ${id}。`,
131
+ quarantineRemaining: (count) => `仍有 ${count} 条无法读取的操作记录。请隔离下一条记录后继续恢复。`,
117
132
  retire: (id) => `重建应用前,请将已停止的操作 ${id} 归档为历史。其外部变更不会被认证或撤销。`,
118
133
  resumeOperation: (id) => `已停止的操作:\n${id}`,
119
134
  resumeQuestion: "要继续,请输入上方完整的操作 ID;按 Enter 取消:",
@@ -269,6 +284,7 @@ const messages = {
269
284
  seedsNotRun: "无内置文件 · 尚未在此运行 init",
270
285
  seedsAttention: "内置文件需要处理",
271
286
  recoveryPending: "有操作正在运行或需要恢复。本次执行未更改文件。",
287
+ invalidRecoveryPending: "无法读取的操作历史阻止了恢复。本次执行未更改文件。",
272
288
  resumeOperation: "继续应用恢复:",
273
289
  contextCurrent: "AGENTS.md 与 TONE.md 布局已是最新。",
274
290
  contextRepair: "受管理的上下文文件需要修复,请检查指南和语气文件诊断。",
@@ -291,7 +307,7 @@ const messages = {
291
307
  changed: "检查时读取程序或设置发生变化,请等修改结束后重试。",
292
308
  probe: "受控探测失败。请检查 Node 和临时目录访问权限,再运行 doctor。",
293
309
  },
294
- operationState: { pending: "待处理", complete: "已完成", "rolled-back": "已恢复", "retired-for-app-rebuild": "已归档以供重建", invalid: "无效记录" },
310
+ operationState: { pending: "待处理", complete: "已完成", "rolled-back": "已恢复", "retired-for-app-rebuild": "已归档以供重建", quarantined: "已隔离", invalid: "无效记录" },
295
311
  rollbackStatus: {
296
312
  ready: "批准后将恢复所选操作。",
297
313
  busy: "操作仍在运行,请等待其所有者完成。",
@@ -420,6 +436,8 @@ const messages = {
420
436
  firstSteps: "设置语气后检查:",
421
437
  examples: "示例",
422
438
  commands: {
439
+ on: "恢复投递已保存的语气",
440
+ off: "暂停语气投递 · 保留设置和 TONE.md",
423
441
  init: "初始化并刷新内置文件",
424
442
  set: "修改语气轴、角色或设置 · 无参数打开面板",
425
443
  get: "读取当前设置值 · 指定键可查看含义和选项",
@@ -455,6 +473,7 @@ const messages = {
455
473
  tools: "要连接的代理 · init:none 仅初始化状态",
456
474
  fix: "doctor:修复可处理的问题",
457
475
  rollback: "doctor --fix:恢复一次操作",
476
+ quarantineOperation: "doctor --fix:隔离一条无法读取的操作记录",
458
477
  fromOperation: "doctor --fix:从操作中选择保存设置",
459
478
  fromPreset: "doctor --fix:为不可用设置选择预设",
460
479
  restorePreset: "doctor --fix:恢复一个缺失或损坏的用户预设",
@@ -477,10 +496,9 @@ const messages = {
477
496
  errors: {
478
497
  notACommand: (name, known, nearest) => `${name} 不是命令。${nearest ? `是否想输入 ${nearest}?` : ""} ` +
479
498
  `可用命令:${known.join(", ")}`,
480
- noOffSwitch: "没有关闭开关。设置安全词后可请求一次平实回答;用 tonedial show 检查是否已设置。" +
481
- "tonedial reset 恢复语气轴默认值。tonedial eject --global 或 " +
482
- "--project <path> 断开对应范围。移除全部连接后," +
483
- "npm uninstall -g tonedial 可移除命令。",
499
+ stoppingHelp: "要在不删除设置的情况下暂停语气投递,请运行 tonedial off;运行 tonedial on 可恢复投递。" +
500
+ "tonedial reset 恢复语气轴默认值。tonedial eject --global 或 --project <path> 会断开该范围的连接。" +
501
+ "断开所有连接后,可用 npm uninstall -g tonedial 删除命令。",
484
502
  notAnOption: (flag) => `${flag}:不是选项。列表:tonedial --help`,
485
503
  flagNotAccepted: (command, flag) => `${command}:不接受 --${flag}`,
486
504
  tooManyArguments: (command, max, got) => `${command}:最多 ${max} 个参数,收到 ${got} 个`,
@@ -387,6 +387,27 @@ export function readJournal(paths, operationIdValue) {
387
387
  const journal = readJson(path);
388
388
  return validJournal(journal, paths, operationIdValue) ? journal : null;
389
389
  }
390
+ function readOperationQuarantine(paths, operationIdValue) {
391
+ if (!existingOperationsDirectory(paths))
392
+ return null;
393
+ const value = readJson(operationJournalPath(paths, operationIdValue));
394
+ return isPlainObject(value) && hasExactKeys(value, ["kind", "operationId", "originalSha256", "root", "schema"]) &&
395
+ value.schema === 1 && value.kind === "quarantined-operation" && value.operationId === operationIdValue &&
396
+ value.root === paths.root && /^[0-9a-f]{64}$/.test(value.originalSha256)
397
+ ? value : null;
398
+ }
399
+ /** Replace an unreadable journal with a nonblocking marker; the enclosing repair journal keeps its original bytes. */
400
+ export function serializeOperationQuarantine(paths, operationIdValue, original) {
401
+ if (!validOperationId(operationIdValue))
402
+ throw new OperationError("invalid", "operation id is not valid");
403
+ return `${JSON.stringify({
404
+ schema: 1,
405
+ kind: "quarantined-operation",
406
+ operationId: operationIdValue,
407
+ root: paths.root,
408
+ originalSha256: createHash("sha256").update(original).digest("hex"),
409
+ }, null, 2)}\n`;
410
+ }
390
411
  function journalTime(journal) {
391
412
  const updated = Date.parse(journal.updatedAt);
392
413
  if (Number.isFinite(updated))
@@ -889,12 +910,13 @@ export function inspectOperations(paths) {
889
910
  continue;
890
911
  const id = match.groups.id;
891
912
  const journal = readJournal(paths, id);
913
+ const quarantine = journal === null ? readOperationQuarantine(paths, id) : null;
892
914
  journals.push({
893
915
  path: join(paths.operations, entry.name),
894
916
  operationId: id,
895
- state: journal?.state ?? "invalid",
917
+ state: journal?.state ?? (quarantine === null ? "invalid" : "quarantined"),
896
918
  journal,
897
- ...(journal === null ? { message: "mutation journal is invalid" } : {}),
919
+ ...(journal === null ? { message: quarantine === null ? "mutation journal is invalid" : "invalid journal is quarantined in rollback history" } : {}),
898
920
  });
899
921
  }
900
922
  const ownerOnly = ownerOnlyInspection(paths, lock);