dsh-mnemon 0.5.38 → 0.5.39

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.
@@ -614,6 +614,89 @@ function encodeFrames(text) {
614
614
  return Buffer.concat([text.slice(0, end), text.slice(end)].filter(Boolean).map(part => zlib.zstdCompressSync(part, options)))
615
615
  }
616
616
 
617
+ async function writeRepairCopy(input, output, result) {
618
+ if (resolve(input) === resolve(output)) throw new Error('Output must be a new copy; the input cannot be overwritten.')
619
+ const temporary = await mkdtemp(join(dirname(resolve(output)), '.mnemon-repair-'))
620
+ try {
621
+ const path = join(temporary, 'session')
622
+ const file = await open(path, 'wx', 0o600)
623
+ try { await file.writeFile(result); await file.sync() } finally { await file.close() }
624
+ // Publish exclusively, including when output is a symlink or hard link.
625
+ await link(path, output)
626
+ } finally { await rm(temporary, { recursive: true, force: true }) }
627
+ }
628
+
629
+ /** Preserve the complete audit payload and all offsets; never truncate history. */
630
+ async function repairIdleProbeSession(entries, original, compressed, input, output) {
631
+ const header = entries[0].row
632
+ assertUniqueJson(entries[0].line, header)
633
+ if (header.type !== 'session' || header.version !== 4 || header.isSeeded !== false || header.delegationDepth !== 0 ||
634
+ header.parentSession !== undefined || header.origin !== undefined) throw new Error('Idle probe recovery requires an unseeded v4 root Session.')
635
+ const uuid = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/u
636
+ const recovered = new Set()
637
+ const pending = new Map()
638
+ const roots = new Set()
639
+ let openTurn
640
+ let completed = false
641
+ const refuse = () => { throw new Error('Unrecognized or incomplete out-of-turn PTC audit; no output written.') }
642
+ for (const entry of entries.slice(1)) {
643
+ const row = entry.row
644
+ if (row.type === 'turn/start' || row.type === 'turn/end') {
645
+ assertUniqueJson(entry.line, row)
646
+ if (pending.size || !count(row.data?.turn)) refuse()
647
+ if (row.type === 'turn/start') {
648
+ if (openTurn !== undefined) refuse()
649
+ openTurn = row.data.turn
650
+ } else {
651
+ if (openTurn !== row.data.turn) refuse()
652
+ openTurn = undefined
653
+ completed = true
654
+ }
655
+ }
656
+ if (!['tool/ptc-dispatch-start', 'tool/ptc-dispatch'].includes(row.type) || openTurn !== undefined) continue
657
+ assertUniqueJson(entry.line, row)
658
+ const data = row.data
659
+ const start = row.type === 'tool/ptc-dispatch-start'
660
+ if (!completed || !exactKeys(row, ['type', 'seq', 'time', 'data']) || !count(row.seq) || !integer(row.time) ||
661
+ !exactKeys(data, ['rootCallId', 'parentCallId', 'subCallId', 'name', 'arguments', ...(start ? [] : ['isError', 'content'])], start ? [] : ['error', 'meta']) ||
662
+ !uuid.test(data.rootCallId) || data.parentCallId !== data.rootCallId || data.subCallId !== `${data.rootCallId}:ptc:1` ||
663
+ data.name !== 'mnemon_subagent_result' || !exactKeys(data.arguments, ['requestId', 'result']) ||
664
+ !uuid.test(data.arguments.requestId) || !exactKeys(data.arguments.result, [])) refuse()
665
+ if (start) {
666
+ if (roots.has(data.rootCallId)) refuse()
667
+ roots.add(data.rootCallId)
668
+ pending.set(data.rootCallId, entry)
669
+ } else {
670
+ const previous = pending.get(data.rootCallId)
671
+ if (!previous || !isDeepStrictEqual(previous.row.data.arguments, data.arguments) || previous.row.seq >= row.seq ||
672
+ typeof data.isError !== 'boolean' || !Array.isArray(data.content) ||
673
+ !data.isError && !isDeepStrictEqual(data.content, [{ type: 'text', text: 'Mnemon subagent result recorded.' }])) refuse()
674
+ pending.delete(data.rootCallId)
675
+ recovered.add(previous)
676
+ recovered.add(entry)
677
+ }
678
+ }
679
+ if (pending.size) refuse()
680
+ const changedSeqs = new Set([...recovered].map(entry => entry.row.seq))
681
+ for (const { row } of entries.slice(1)) {
682
+ for (const range of row.sourceEventSeqs ?? []) {
683
+ if (typeof range === 'number' ? changedSeqs.has(range) : Array.isArray(range) && [...changedSeqs].some(seq => seq >= range[0] && seq <= range[1])) refuse()
684
+ }
685
+ }
686
+ const text = entries.map(entry => recovered.has(entry)
687
+ ? JSON.stringify({ type: 'dsh-mnemon/recovered-idle-probe', seq: entry.row.seq, time: entry.row.time, ignorable: true,
688
+ data: { originalType: entry.row.type, originalData: entry.row.data } }) + '\n'
689
+ : entry.line).join('')
690
+ const plain = Buffer.from(text)
691
+ if (plain.length > maximumBytes) throw new Error('Repaired Session exceeds the 128 MiB repair limit; no output written.')
692
+ const result = recovered.size === 0 ? original : compressed ? encodeFrames(text) : plain
693
+ if (result.length > maximumBytes) throw new Error('Repaired Session exceeds the 128 MiB repair limit; no output written.')
694
+ if (output !== undefined) await writeRepairCopy(input, output, result)
695
+ return { mode: output === undefined ? 'preview' : 'copy', format: 4, encoding: compressed ? 'zstd' : 'jsonl',
696
+ recoveredIdleProbeEvents: recovered.size, inputSha256: sha256(original), outputSha256: sha256(result),
697
+ originalPreserved: true, migrationValidated: false, blockers: [] }
698
+ }
699
+
617
700
  export async function repairLegacySession(input, output) {
618
701
  const original = await readBounded(input)
619
702
  const compressed = original.subarray(0, 4).equals(Buffer.from([0x28, 0xb5, 0x2f, 0xfd]))
@@ -627,6 +710,7 @@ export async function repairLegacySession(input, output) {
627
710
  const entries = lines.map((line, index) => {
628
711
  try { return { line, index, row: JSON.parse(line) } } catch { throw new Error(`Invalid JSON at line ${index + 1}; no output written.`) }
629
712
  })
713
+ if (entries[0]?.row?.version === 4) return repairIdleProbeSession(entries, original, compressed, input, output)
630
714
  const recovery = recoverRecordedToolIdentities(entries)
631
715
  let changes = 0
632
716
  let descriptors = 0
@@ -681,15 +765,7 @@ export async function repairLegacySession(input, output) {
681
765
  if (result.length > maximumBytes) throw new Error('Repaired Session exceeds the 128 MiB repair limit; no output written.')
682
766
  const blockers = diagnostics.report()
683
767
  if (output !== undefined && blockers.length === 0) {
684
- if (resolve(input) === resolve(output)) throw new Error('Output must be a new copy; the input cannot be overwritten.')
685
- const temporary = await mkdtemp(join(dirname(resolve(output)), '.mnemon-repair-'))
686
- try {
687
- const path = join(temporary, 'session')
688
- const file = await open(path, 'wx', 0o600)
689
- try { await file.writeFile(result); await file.sync() } finally { await file.close() }
690
- // Publish exclusively, including when output is a symlink or hard link.
691
- await link(path, output)
692
- } finally { await rm(temporary, { recursive: true, force: true }) }
768
+ await writeRepairCopy(input, output, result)
693
769
  }
694
770
  return {
695
771
  mode: output === undefined ? 'preview' : blockers.length === 0 ? 'copy' : 'refused',
@@ -712,7 +788,7 @@ export async function repairLegacySession(input, output) {
712
788
  }
713
789
 
714
790
  async function main(args) {
715
- const usage = 'Usage: dsh-mnemon-repair-session --input FILE [--output NEW_FILE]\nWithout --output, preview the three known Mnemon v0 summary repairs, exact compatible v2 subagent descriptors, packed deltas with empty IDs/names, property-preserving null-to-empty delta names and proven DeepSeek chains with a previously recorded provider ID. Unsupported descriptor/tool shapes are reported with exit status 1 and prevent output. DSH must still validate the repaired copy. Stop DSH and work on a backup copy. The input and existing output files are never overwritten.'
791
+ const usage = 'Usage: dsh-mnemon-repair-session --input FILE [--output NEW_FILE]\nWithout --output, preview recognized v4 idle-review probe audit recovery or the three known Mnemon v0 summary repairs, exact compatible v2 subagent descriptors, packed deltas with empty IDs/names, property-preserving null-to-empty delta names and proven DeepSeek chains with a previously recorded provider ID. Unsupported descriptor/tool shapes are reported with exit status 1 and prevent output. DSH must still validate the repaired copy. Stop DSH and work on a backup copy. The input and existing output files are never overwritten.'
716
792
  if (args.length === 1 && args[0] === '--help') { console.log(usage); return }
717
793
  const options = new Map()
718
794
  for (let index = 0; index < args.length; index += 2) {
package/lib/bundle.js CHANGED
@@ -1,4 +1,4 @@
1
- import { t as prepareStarterResolution } from "./starter-resolution-XVFzvnV0.js";
1
+ import { t as prepareStarterResolution } from "./starter-resolution-C3yJawlA.js";
2
2
  //#region src/bundle.ts
3
3
  const groupKey = Symbol.for("cordis.group");
4
4
  const init = Symbol.for("cordis.init");
package/lib/client.js CHANGED
@@ -316,6 +316,7 @@ window.__ModuleLoader__.load({
316
316
  evolution: false,
317
317
  allowCreate: true,
318
318
  allowUpdate: true,
319
+ targetScope: "user",
319
320
  autoBacktest: true,
320
321
  hints: true,
321
322
  sidebar: false,
@@ -3054,7 +3055,7 @@ window.__ModuleLoader__.load({
3054
3055
  "config.syncAuthorDefault": "默认",
3055
3056
  "config.syncAuthorMachine": "本机 Git",
3056
3057
  "config.syncAuthorCustom": "自定义",
3057
- "config.syncAuthorDefaultNote": "以 {identity} 提交",
3058
+ "config.syncAuthorDefaultNote": "优先使用本机 Git 身份;没有配置时以 {identity} 提交",
3058
3059
  "config.syncAuthorMachineNote": "使用这台电脑的 Git 身份;没有配置时用默认身份",
3059
3060
  "config.syncAuthorName": "姓名",
3060
3061
  "config.syncAuthorEmail": "邮箱",
@@ -3130,6 +3131,11 @@ window.__ModuleLoader__.load({
3130
3131
  "skills.allowCreateHint": "允许复盘提出新技能;已有建议仍可手动处理",
3131
3132
  "skills.allowUpdate": "改进已有技能",
3132
3133
  "skills.allowUpdateHint": "允许复盘提出已有技能的改进;两类建议均关闭时暂停复盘",
3134
+ "skills.targetScope": "新技能写入位置",
3135
+ "skills.targetScopeHint": "默认写入用户技能;跟随存储范围时,workspace / workspaces 写入项目,其余写入用户。已有建议和草案保留目标,改进写回原技能;采纳时可手动选择。",
3136
+ "skills.targetScope.user": "用户",
3137
+ "skills.targetScope.project": "项目",
3138
+ "skills.targetScope.storage": "跟随存储范围",
3133
3139
  "skills.evolutionHint": "后台复盘并建议改进技能;会创建落盘的任务及子代理会话,Mnemon 不自动删除这些记录",
3134
3140
  "skills.autoBacktest": "草案自动回测",
3135
3141
  "skills.autoBacktestHint": "草案写好后,用测试用例与当前版本对比",
@@ -3864,7 +3870,7 @@ window.__ModuleLoader__.load({
3864
3870
  "config.syncAuthorDefault": "Default",
3865
3871
  "config.syncAuthorMachine": "This computer's Git",
3866
3872
  "config.syncAuthorCustom": "Custom",
3867
- "config.syncAuthorDefaultNote": "Commits as {identity}",
3873
+ "config.syncAuthorDefaultNote": "Uses this computer's Git identity; falls back to {identity} when unconfigured",
3868
3874
  "config.syncAuthorMachineNote": "Uses this computer's Git identity, else the default one",
3869
3875
  "config.syncAuthorName": "Name",
3870
3876
  "config.syncAuthorEmail": "Email",
@@ -3940,6 +3946,11 @@ window.__ModuleLoader__.load({
3940
3946
  "skills.allowCreateHint": "Allow reviews to propose new skills; existing suggestions can still be handled manually",
3941
3947
  "skills.allowUpdate": "Suggest skill improvements",
3942
3948
  "skills.allowUpdateHint": "Allow reviews to improve existing skills; reviews pause when both suggestion types are off",
3949
+ "skills.targetScope": "New skill destination",
3950
+ "skills.targetScopeHint": "Defaults to user skills. Follow storage uses the project for workspace / workspaces, otherwise the user. Existing proposals keep their destination; improvements keep their original file. You can choose again at adoption.",
3951
+ "skills.targetScope.user": "User",
3952
+ "skills.targetScope.project": "Project",
3953
+ "skills.targetScope.storage": "Follow storage",
3943
3954
  "skills.evolutionHint": "Review conversations and suggest skills. Task and subagent sessions are saved locally; Mnemon does not automatically delete them.",
3944
3955
  "skills.autoBacktest": "Backtest drafts",
3945
3956
  "skills.autoBacktestHint": "Compare a new draft with the current version on its test cases",
@@ -8519,8 +8530,8 @@ window.__ModuleLoader__.load({
8519
8530
  /**
8520
8531
  * What this computer is called, where on the branch the memory goes and whose name
8521
8532
  * its commits carry. Each field shows the value in use; the computer's name is the
8522
- * host name or a typed one, and the author an explicit choice between the channel's
8523
- * own identity, this computer's Git identity, and a typed one.
8533
+ * host name or a typed one. The default author follows this computer's Git
8534
+ * identity with a placeholder fallback; a custom author overrides it.
8524
8535
  */
8525
8536
  function AdvancedSettings({ client, status, disabled, t, onSaved }) {
8526
8537
  const [open, setOpen] = (0, react.useState)(false);
@@ -12028,8 +12039,8 @@ window.__ModuleLoader__.load({
12028
12039
  }
12029
12040
  /**
12030
12041
  * 技能管理's settings on its page under Plugins. Switches apply at once; the
12031
- * review parameters wait, folded, for their own Apply. Evolution's own
12032
- * switches show only while it is on.
12042
+ * destination and folded review parameters wait for their own Apply. The
12043
+ * destination also applies to manual drafts while evolution is off.
12033
12044
  */
12034
12045
  function SkillsSettings(props) {
12035
12046
  const { scope, page, t } = props;
@@ -12044,6 +12055,9 @@ window.__ModuleLoader__.load({
12044
12055
  const autoBacktest = useLive(saved.autoBacktest, write("autoBacktest"));
12045
12056
  const hints = useLive(saved.hints, write("hints"));
12046
12057
  const sidebar = useLive(saved.sidebar, write("sidebar"));
12058
+ const destination = useStaged({ targetScope: saved.targetScope }, async (draft) => {
12059
+ await scope.mutate([set("targetScope", draft.targetScope)]);
12060
+ });
12047
12061
  const parameters = useStaged(parametersOf(saved), async (draft) => {
12048
12062
  const changed = PARAMETERS.filter((field) => parameterValue(field, draft[field.key]) !== saved[field.key]);
12049
12063
  if (changed.length > 0) await scope.mutate(changed.map((field) => set(field.key, parameterValue(field, draft[field.key]))));
@@ -12080,6 +12094,35 @@ window.__ModuleLoader__.load({
12080
12094
  disabled,
12081
12095
  onChange: allowUpdate.set
12082
12096
  }),
12097
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SelectRow, {
12098
+ id: "mnemon-skills-target",
12099
+ label: t("skills.targetScope"),
12100
+ hint: t("skills.targetScopeHint"),
12101
+ value: destination.draft.targetScope,
12102
+ options: [
12103
+ "user",
12104
+ "project",
12105
+ "storage"
12106
+ ].map((value) => ({
12107
+ value,
12108
+ label: t(`skills.targetScope.${value}`)
12109
+ })),
12110
+ disabled: disabled || destination.saving,
12111
+ onChange: (targetScope) => destination.edit({ targetScope })
12112
+ }),
12113
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PanelActions, {
12114
+ dirty: destination.dirty,
12115
+ saving: destination.saving,
12116
+ applied: destination.applied,
12117
+ failed: destination.failed,
12118
+ disabled,
12119
+ invalid: null,
12120
+ t,
12121
+ onDiscard: destination.discard,
12122
+ onApply: () => {
12123
+ destination.apply();
12124
+ }
12125
+ }),
12083
12126
  evolution.value && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToggleRow, {
12084
12127
  id: "mnemon-skills-backtest",
12085
12128
  label: t("skills.autoBacktest"),