@visorcraft/idlehands 0.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (197) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +30 -0
  3. package/dist/agent.js +2604 -0
  4. package/dist/agent.js.map +1 -0
  5. package/dist/anton/controller.js +341 -0
  6. package/dist/anton/controller.js.map +1 -0
  7. package/dist/anton/lock.js +110 -0
  8. package/dist/anton/lock.js.map +1 -0
  9. package/dist/anton/parser.js +303 -0
  10. package/dist/anton/parser.js.map +1 -0
  11. package/dist/anton/prompt.js +203 -0
  12. package/dist/anton/prompt.js.map +1 -0
  13. package/dist/anton/reporter.js +119 -0
  14. package/dist/anton/reporter.js.map +1 -0
  15. package/dist/anton/session.js +51 -0
  16. package/dist/anton/session.js.map +1 -0
  17. package/dist/anton/types.js +7 -0
  18. package/dist/anton/types.js.map +1 -0
  19. package/dist/anton/verifier.js +263 -0
  20. package/dist/anton/verifier.js.map +1 -0
  21. package/dist/bench/compare.js +239 -0
  22. package/dist/bench/compare.js.map +1 -0
  23. package/dist/bench/debug_hooks.js +17 -0
  24. package/dist/bench/debug_hooks.js.map +1 -0
  25. package/dist/bench/json_extract.js +22 -0
  26. package/dist/bench/json_extract.js.map +1 -0
  27. package/dist/bench/openclaw.js +86 -0
  28. package/dist/bench/openclaw.js.map +1 -0
  29. package/dist/bench/report.js +116 -0
  30. package/dist/bench/report.js.map +1 -0
  31. package/dist/bench/runner.js +312 -0
  32. package/dist/bench/runner.js.map +1 -0
  33. package/dist/bench/types.js +2 -0
  34. package/dist/bench/types.js.map +1 -0
  35. package/dist/bot/commands.js +444 -0
  36. package/dist/bot/commands.js.map +1 -0
  37. package/dist/bot/confirm-discord.js +133 -0
  38. package/dist/bot/confirm-discord.js.map +1 -0
  39. package/dist/bot/confirm-telegram.js +290 -0
  40. package/dist/bot/confirm-telegram.js.map +1 -0
  41. package/dist/bot/discord.js +826 -0
  42. package/dist/bot/discord.js.map +1 -0
  43. package/dist/bot/format.js +210 -0
  44. package/dist/bot/format.js.map +1 -0
  45. package/dist/bot/session-manager.js +270 -0
  46. package/dist/bot/session-manager.js.map +1 -0
  47. package/dist/bot/telegram.js +678 -0
  48. package/dist/bot/telegram.js.map +1 -0
  49. package/dist/cli/agent-turn.js +45 -0
  50. package/dist/cli/agent-turn.js.map +1 -0
  51. package/dist/cli/args.js +236 -0
  52. package/dist/cli/args.js.map +1 -0
  53. package/dist/cli/bot.js +252 -0
  54. package/dist/cli/bot.js.map +1 -0
  55. package/dist/cli/build-repl-context.js +365 -0
  56. package/dist/cli/build-repl-context.js.map +1 -0
  57. package/dist/cli/command-registry.js +20 -0
  58. package/dist/cli/command-registry.js.map +1 -0
  59. package/dist/cli/commands/anton.js +271 -0
  60. package/dist/cli/commands/anton.js.map +1 -0
  61. package/dist/cli/commands/editing.js +328 -0
  62. package/dist/cli/commands/editing.js.map +1 -0
  63. package/dist/cli/commands/model.js +274 -0
  64. package/dist/cli/commands/model.js.map +1 -0
  65. package/dist/cli/commands/project.js +255 -0
  66. package/dist/cli/commands/project.js.map +1 -0
  67. package/dist/cli/commands/runtime.js +63 -0
  68. package/dist/cli/commands/runtime.js.map +1 -0
  69. package/dist/cli/commands/session.js +281 -0
  70. package/dist/cli/commands/session.js.map +1 -0
  71. package/dist/cli/commands/tools.js +126 -0
  72. package/dist/cli/commands/tools.js.map +1 -0
  73. package/dist/cli/commands/trifecta.js +221 -0
  74. package/dist/cli/commands/trifecta.js.map +1 -0
  75. package/dist/cli/commands/tui.js +17 -0
  76. package/dist/cli/commands/tui.js.map +1 -0
  77. package/dist/cli/init.js +222 -0
  78. package/dist/cli/init.js.map +1 -0
  79. package/dist/cli/input.js +360 -0
  80. package/dist/cli/input.js.map +1 -0
  81. package/dist/cli/oneshot.js +254 -0
  82. package/dist/cli/oneshot.js.map +1 -0
  83. package/dist/cli/repl-context.js +2 -0
  84. package/dist/cli/repl-context.js.map +1 -0
  85. package/dist/cli/runtime-cmds.js +811 -0
  86. package/dist/cli/runtime-cmds.js.map +1 -0
  87. package/dist/cli/service.js +145 -0
  88. package/dist/cli/service.js.map +1 -0
  89. package/dist/cli/session-state.js +130 -0
  90. package/dist/cli/session-state.js.map +1 -0
  91. package/dist/cli/setup.js +815 -0
  92. package/dist/cli/setup.js.map +1 -0
  93. package/dist/cli/shell.js +79 -0
  94. package/dist/cli/shell.js.map +1 -0
  95. package/dist/cli/status.js +392 -0
  96. package/dist/cli/status.js.map +1 -0
  97. package/dist/cli/watch.js +33 -0
  98. package/dist/cli/watch.js.map +1 -0
  99. package/dist/client.js +676 -0
  100. package/dist/client.js.map +1 -0
  101. package/dist/commands.js +194 -0
  102. package/dist/commands.js.map +1 -0
  103. package/dist/config.js +507 -0
  104. package/dist/config.js.map +1 -0
  105. package/dist/confirm/auto.js +13 -0
  106. package/dist/confirm/auto.js.map +1 -0
  107. package/dist/confirm/headless.js +41 -0
  108. package/dist/confirm/headless.js.map +1 -0
  109. package/dist/confirm/terminal.js +90 -0
  110. package/dist/confirm/terminal.js.map +1 -0
  111. package/dist/context.js +49 -0
  112. package/dist/context.js.map +1 -0
  113. package/dist/git.js +136 -0
  114. package/dist/git.js.map +1 -0
  115. package/dist/harnesses.js +171 -0
  116. package/dist/harnesses.js.map +1 -0
  117. package/dist/history.js +139 -0
  118. package/dist/history.js.map +1 -0
  119. package/dist/index.js +700 -0
  120. package/dist/index.js.map +1 -0
  121. package/dist/indexer.js +374 -0
  122. package/dist/indexer.js.map +1 -0
  123. package/dist/jsonrpc.js +76 -0
  124. package/dist/jsonrpc.js.map +1 -0
  125. package/dist/lens.js +525 -0
  126. package/dist/lens.js.map +1 -0
  127. package/dist/lsp.js +605 -0
  128. package/dist/lsp.js.map +1 -0
  129. package/dist/markdown.js +275 -0
  130. package/dist/markdown.js.map +1 -0
  131. package/dist/mcp.js +554 -0
  132. package/dist/mcp.js.map +1 -0
  133. package/dist/recovery.js +178 -0
  134. package/dist/recovery.js.map +1 -0
  135. package/dist/replay.js +132 -0
  136. package/dist/replay.js.map +1 -0
  137. package/dist/replay_cli.js +24 -0
  138. package/dist/replay_cli.js.map +1 -0
  139. package/dist/runtime/executor.js +418 -0
  140. package/dist/runtime/executor.js.map +1 -0
  141. package/dist/runtime/planner.js +197 -0
  142. package/dist/runtime/planner.js.map +1 -0
  143. package/dist/runtime/store.js +289 -0
  144. package/dist/runtime/store.js.map +1 -0
  145. package/dist/runtime/types.js +2 -0
  146. package/dist/runtime/types.js.map +1 -0
  147. package/dist/safety.js +446 -0
  148. package/dist/safety.js.map +1 -0
  149. package/dist/spinner.js +224 -0
  150. package/dist/spinner.js.map +1 -0
  151. package/dist/sys/context.js +124 -0
  152. package/dist/sys/context.js.map +1 -0
  153. package/dist/sys/snapshot.sh +97 -0
  154. package/dist/term.js +61 -0
  155. package/dist/term.js.map +1 -0
  156. package/dist/themes.js +135 -0
  157. package/dist/themes.js.map +1 -0
  158. package/dist/tools.js +1114 -0
  159. package/dist/tools.js.map +1 -0
  160. package/dist/tui/branch-picker.js +65 -0
  161. package/dist/tui/branch-picker.js.map +1 -0
  162. package/dist/tui/command-handler.js +108 -0
  163. package/dist/tui/command-handler.js.map +1 -0
  164. package/dist/tui/confirm.js +90 -0
  165. package/dist/tui/confirm.js.map +1 -0
  166. package/dist/tui/controller.js +463 -0
  167. package/dist/tui/controller.js.map +1 -0
  168. package/dist/tui/event-bridge.js +44 -0
  169. package/dist/tui/event-bridge.js.map +1 -0
  170. package/dist/tui/events.js +2 -0
  171. package/dist/tui/events.js.map +1 -0
  172. package/dist/tui/keymap.js +144 -0
  173. package/dist/tui/keymap.js.map +1 -0
  174. package/dist/tui/layout.js +11 -0
  175. package/dist/tui/layout.js.map +1 -0
  176. package/dist/tui/render.js +186 -0
  177. package/dist/tui/render.js.map +1 -0
  178. package/dist/tui/screen.js +48 -0
  179. package/dist/tui/screen.js.map +1 -0
  180. package/dist/tui/state.js +167 -0
  181. package/dist/tui/state.js.map +1 -0
  182. package/dist/tui/theme.js +70 -0
  183. package/dist/tui/theme.js.map +1 -0
  184. package/dist/tui/types.js +2 -0
  185. package/dist/tui/types.js.map +1 -0
  186. package/dist/types.js +2 -0
  187. package/dist/types.js.map +1 -0
  188. package/dist/upgrade.js +412 -0
  189. package/dist/upgrade.js.map +1 -0
  190. package/dist/utils.js +87 -0
  191. package/dist/utils.js.map +1 -0
  192. package/dist/vault.js +520 -0
  193. package/dist/vault.js.map +1 -0
  194. package/dist/vim.js +160 -0
  195. package/dist/vim.js.map +1 -0
  196. package/package.json +67 -0
  197. package/src/sys/snapshot.sh +97 -0
@@ -0,0 +1,178 @@
1
+ /**
2
+ * Mid-session crash recovery (§6e)
3
+ *
4
+ * - Autosave: periodically writes session state (messages, turn count, metadata)
5
+ * to ~/.local/state/idlehands/autosave.json every N turns or M seconds.
6
+ * - Lockfile: written on session start, removed on clean exit.
7
+ * If lockfile exists at startup → previous session crashed → offer recovery.
8
+ * - Restore: loads the autosave and resumes from last checkpoint.
9
+ */
10
+ import fs from 'node:fs/promises';
11
+ import path from 'node:path';
12
+ import { stateDir } from './utils.js';
13
+ const STATE_DIR = stateDir();
14
+ const AUTOSAVE_PATH = path.join(STATE_DIR, 'autosave.json');
15
+ const LOCKFILE_PATH = path.join(STATE_DIR, 'session.lock');
16
+ /** Ensure state directory exists */
17
+ async function ensureStateDir() {
18
+ await fs.mkdir(STATE_DIR, { recursive: true });
19
+ }
20
+ // ─── Lockfile ──────────────────────────────────────────────
21
+ /**
22
+ * Write a lockfile with the current PID.
23
+ * Returns true if created, false if one already existed (dirty shutdown detected).
24
+ */
25
+ export async function acquireLock() {
26
+ await ensureStateDir();
27
+ try {
28
+ // O_EXCL: fail if file exists
29
+ const fd = await fs.open(LOCKFILE_PATH, 'wx');
30
+ await fd.write(`${process.pid}\n`);
31
+ await fd.close();
32
+ return { acquired: true };
33
+ }
34
+ catch (e) {
35
+ if (e?.code === 'EEXIST') {
36
+ // Lockfile already exists — dirty shutdown
37
+ let stalePid;
38
+ try {
39
+ const raw = await fs.readFile(LOCKFILE_PATH, 'utf8');
40
+ stalePid = parseInt(raw.trim(), 10) || undefined;
41
+ }
42
+ catch {
43
+ // ignore
44
+ }
45
+ return { acquired: false, stalePid };
46
+ }
47
+ throw e;
48
+ }
49
+ }
50
+ /** Release the lockfile. Best-effort — never throws. */
51
+ export async function releaseLock() {
52
+ try {
53
+ await fs.rm(LOCKFILE_PATH, { force: true });
54
+ }
55
+ catch {
56
+ // ignore
57
+ }
58
+ }
59
+ /**
60
+ * Force-acquire the lock (after user confirms recovery or dismisses).
61
+ * Overwrites the stale lockfile with current PID.
62
+ */
63
+ export async function forceAcquireLock() {
64
+ await ensureStateDir();
65
+ await fs.writeFile(LOCKFILE_PATH, `${process.pid}\n`, 'utf8');
66
+ }
67
+ /** Check whether the PID in the lockfile is still running */
68
+ export function isPidAlive(pid) {
69
+ try {
70
+ process.kill(pid, 0); // signal 0 = existence check
71
+ return true;
72
+ }
73
+ catch {
74
+ return false;
75
+ }
76
+ }
77
+ // ─── Autosave ──────────────────────────────────────────────
78
+ /** Save session state to autosave.json (atomic write) */
79
+ export async function writeAutosave(data) {
80
+ await ensureStateDir();
81
+ const tmp = AUTOSAVE_PATH + '.tmp';
82
+ await fs.writeFile(tmp, JSON.stringify(data), 'utf8');
83
+ await fs.rename(tmp, AUTOSAVE_PATH);
84
+ }
85
+ /** Read the autosave if it exists. Returns null if missing/corrupt. */
86
+ export async function readAutosave() {
87
+ try {
88
+ const raw = await fs.readFile(AUTOSAVE_PATH, 'utf8');
89
+ const data = JSON.parse(raw);
90
+ // Basic validation
91
+ if (!data.messages || !Array.isArray(data.messages) || data.messages.length < 2)
92
+ return null;
93
+ if (data.messages[0].role !== 'system')
94
+ return null;
95
+ return data;
96
+ }
97
+ catch {
98
+ return null;
99
+ }
100
+ }
101
+ /** Remove the autosave file (on clean session end). */
102
+ export async function clearAutosave() {
103
+ try {
104
+ await fs.rm(AUTOSAVE_PATH, { force: true });
105
+ }
106
+ catch {
107
+ // ignore
108
+ }
109
+ }
110
+ /**
111
+ * Format a human-readable recovery prompt.
112
+ */
113
+ export function formatRecoveryPrompt(data) {
114
+ const savedAt = new Date(data.savedAt);
115
+ const agoMs = Date.now() - savedAt.getTime();
116
+ const agoMin = Math.floor(agoMs / 60_000);
117
+ const agoStr = agoMin < 1 ? 'just now' : agoMin === 1 ? '1 min ago' : `${agoMin} min ago`;
118
+ return `Recovered session from crash (${data.turns} turns, ${data.toolCalls} tool calls, ${agoStr}). Resume? [Y/n]`;
119
+ }
120
+ /**
121
+ * Create an autosave controller that saves every `turnInterval` turns
122
+ * or every `intervalMs` milliseconds, whichever comes first.
123
+ */
124
+ export function createAutosaveController(opts) {
125
+ const turnInterval = opts?.turnInterval ?? 5;
126
+ const intervalMs = opts?.intervalMs ?? 60_000;
127
+ let lastSaveTurn = 0;
128
+ let pending = null;
129
+ let timer = null;
130
+ let latestState = null;
131
+ const doSave = async (messages, meta) => {
132
+ const data = {
133
+ messages,
134
+ model: meta.model,
135
+ harness: meta.harness,
136
+ cwd: meta.cwd,
137
+ turns: meta.turns,
138
+ toolCalls: meta.toolCalls,
139
+ savedAt: new Date().toISOString(),
140
+ pid: process.pid
141
+ };
142
+ await writeAutosave(data);
143
+ lastSaveTurn = meta.turns;
144
+ };
145
+ // Periodic timer: save latest state every intervalMs
146
+ timer = setInterval(() => {
147
+ if (!latestState)
148
+ return;
149
+ const { messages, meta } = latestState;
150
+ if (pending)
151
+ return; // already saving
152
+ pending = doSave(messages, meta).catch(() => { }).finally(() => { pending = null; });
153
+ }, intervalMs);
154
+ // Don't keep the process alive just for autosave
155
+ if (timer && typeof timer === 'object' && 'unref' in timer) {
156
+ timer.unref();
157
+ }
158
+ const tick = (messages, meta) => {
159
+ latestState = { messages: [...messages], meta };
160
+ // Save every N turns
161
+ if (meta.turns - lastSaveTurn >= turnInterval) {
162
+ if (!pending) {
163
+ pending = doSave([...messages], meta).catch(() => { }).finally(() => { pending = null; });
164
+ }
165
+ }
166
+ };
167
+ const flush = async (messages, meta) => {
168
+ await doSave([...messages], meta);
169
+ };
170
+ const stop = () => {
171
+ if (timer) {
172
+ clearInterval(timer);
173
+ timer = null;
174
+ }
175
+ };
176
+ return { tick, flush, stop };
177
+ }
178
+ //# sourceMappingURL=recovery.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recovery.js","sourceRoot":"","sources":["../src/recovery.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAClC,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAEtC,MAAM,SAAS,GAAG,QAAQ,EAAE,CAAC;AAC7B,MAAM,aAAa,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,eAAe,CAAC,CAAC;AAC5D,MAAM,aAAa,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,cAAc,CAAC,CAAC;AAa3D,oCAAoC;AACpC,KAAK,UAAU,cAAc;IAC3B,MAAM,EAAE,CAAC,KAAK,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;AACjD,CAAC;AAED,8DAA8D;AAE9D;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW;IAC/B,MAAM,cAAc,EAAE,CAAC;IACvB,IAAI,CAAC;QACH,8BAA8B;QAC9B,MAAM,EAAE,GAAG,MAAM,EAAE,CAAC,IAAI,CAAC,aAAa,EAAE,IAAI,CAAC,CAAC;QAC9C,MAAM,EAAE,CAAC,KAAK,CAAC,GAAG,OAAO,CAAC,GAAG,IAAI,CAAC,CAAC;QACnC,MAAM,EAAE,CAAC,KAAK,EAAE,CAAC;QACjB,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC5B,CAAC;IAAC,OAAO,CAAM,EAAE,CAAC;QAChB,IAAI,CAAC,EAAE,IAAI,KAAK,QAAQ,EAAE,CAAC;YACzB,2CAA2C;YAC3C,IAAI,QAA4B,CAAC;YACjC,IAAI,CAAC;gBACH,MAAM,GAAG,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC;gBACrD,QAAQ,GAAG,QAAQ,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,EAAE,CAAC,IAAI,SAAS,CAAC;YACnD,CAAC;YAAC,MAAM,CAAC;gBACP,SAAS;YACX,CAAC;YACD,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC;QACvC,CAAC;QACD,MAAM,CAAC,CAAC;IACV,CAAC;AACH,CAAC;AAED,wDAAwD;AACxD,MAAM,CAAC,KAAK,UAAU,WAAW;IAC/B,IAAI,CAAC;QACH,MAAM,EAAE,CAAC,EAAE,CAAC,aAAa,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IAC9C,CAAC;IAAC,MAAM,CAAC;QACP,SAAS;IACX,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB;IACpC,MAAM,cAAc,EAAE,CAAC;IACvB,MAAM,EAAE,CAAC,SAAS,CAAC,aAAa,EAAE,GAAG,OAAO,CAAC,GAAG,IAAI,EAAE,MAAM,CAAC,CAAC;AAChE,CAAC;AAED,6DAA6D;AAC7D,MAAM,UAAU,UAAU,CAAC,GAAW;IACpC,IAAI,CAAC;QACH,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,6BAA6B;QACnD,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,8DAA8D;AAE9D,yDAAyD;AACzD,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,IAAkB;IACpD,MAAM,cAAc,EAAE,CAAC;IACvB,MAAM,GAAG,GAAG,aAAa,GAAG,MAAM,CAAC;IACnC,MAAM,EAAE,CAAC,SAAS,CAAC,GAAG,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,CAAC;IACtD,MAAM,EAAE,CAAC,MAAM,CAAC,GAAG,EAAE,aAAa,CAAC,CAAC;AACtC,CAAC;AAED,uEAAuE;AACvE,MAAM,CAAC,KAAK,UAAU,YAAY;IAChC,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC;QACrD,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAiB,CAAC;QAC7C,mBAAmB;QACnB,IAAI,CAAC,IAAI,CAAC,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC;YAAE,OAAO,IAAI,CAAC;QAC7F,IAAI,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,KAAK,QAAQ;YAAE,OAAO,IAAI,CAAC;QACpD,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,uDAAuD;AACvD,MAAM,CAAC,KAAK,UAAU,aAAa;IACjC,IAAI,CAAC;QACH,MAAM,EAAE,CAAC,EAAE,CAAC,aAAa,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IAC9C,CAAC;IAAC,MAAM,CAAC;QACP,SAAS;IACX,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,oBAAoB,CAAC,IAAkB;IACrD,MAAM,OAAO,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACvC,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC;IAC7C,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,MAAM,CAAC,CAAC;IAC1C,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,GAAG,MAAM,UAAU,CAAC;IAE1F,OAAO,iCAAiC,IAAI,CAAC,KAAK,WAAW,IAAI,CAAC,SAAS,gBAAgB,MAAM,kBAAkB,CAAC;AACtH,CAAC;AAaD;;;GAGG;AACH,MAAM,UAAU,wBAAwB,CAAC,IAGxC;IACC,MAAM,YAAY,GAAG,IAAI,EAAE,YAAY,IAAI,CAAC,CAAC;IAC7C,MAAM,UAAU,GAAG,IAAI,EAAE,UAAU,IAAI,MAAM,CAAC;IAE9C,IAAI,YAAY,GAAG,CAAC,CAAC;IACrB,IAAI,OAAO,GAAyB,IAAI,CAAC;IACzC,IAAI,KAAK,GAA0C,IAAI,CAAC;IACxD,IAAI,WAAW,GAAgI,IAAI,CAAC;IAEpJ,MAAM,MAAM,GAAG,KAAK,EAAE,QAAuB,EAAE,IAAuF,EAAE,EAAE;QACxI,MAAM,IAAI,GAAiB;YACzB,QAAQ;YACR,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,OAAO,EAAE,IAAI,CAAC,OAAO;YACrB,GAAG,EAAE,IAAI,CAAC,GAAG;YACb,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,SAAS,EAAE,IAAI,CAAC,SAAS;YACzB,OAAO,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;YACjC,GAAG,EAAE,OAAO,CAAC,GAAG;SACjB,CAAC;QACF,MAAM,aAAa,CAAC,IAAI,CAAC,CAAC;QAC1B,YAAY,GAAG,IAAI,CAAC,KAAK,CAAC;IAC5B,CAAC,CAAC;IAEF,qDAAqD;IACrD,KAAK,GAAG,WAAW,CAAC,GAAG,EAAE;QACvB,IAAI,CAAC,WAAW;YAAE,OAAO;QACzB,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,GAAG,WAAW,CAAC;QACvC,IAAI,OAAO;YAAE,OAAO,CAAC,iBAAiB;QACtC,OAAO,GAAG,MAAM,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE,GAAG,OAAO,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;IACtF,CAAC,EAAE,UAAU,CAAC,CAAC;IAEf,iDAAiD;IACjD,IAAI,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,OAAO,IAAI,KAAK,EAAE,CAAC;QAC3D,KAAK,CAAC,KAAK,EAAE,CAAC;IAChB,CAAC;IAED,MAAM,IAAI,GAAG,CAAC,QAAuB,EAAE,IAAuF,EAAE,EAAE;QAChI,WAAW,GAAG,EAAE,QAAQ,EAAE,CAAC,GAAG,QAAQ,CAAC,EAAE,IAAI,EAAE,CAAC;QAEhD,qBAAqB;QACrB,IAAI,IAAI,CAAC,KAAK,GAAG,YAAY,IAAI,YAAY,EAAE,CAAC;YAC9C,IAAI,CAAC,OAAO,EAAE,CAAC;gBACb,OAAO,GAAG,MAAM,CAAC,CAAC,GAAG,QAAQ,CAAC,EAAE,IAAI,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE,GAAG,OAAO,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;YAC3F,CAAC;QACH,CAAC;IACH,CAAC,CAAC;IAEF,MAAM,KAAK,GAAG,KAAK,EAAE,QAAuB,EAAE,IAAuF,EAAE,EAAE;QACvI,MAAM,MAAM,CAAC,CAAC,GAAG,QAAQ,CAAC,EAAE,IAAI,CAAC,CAAC;IACpC,CAAC,CAAC;IAEF,MAAM,IAAI,GAAG,GAAG,EAAE;QAChB,IAAI,KAAK,EAAE,CAAC;YACV,aAAa,CAAC,KAAK,CAAC,CAAC;YACrB,KAAK,GAAG,IAAI,CAAC;QACf,CAAC;IACH,CAAC,CAAC;IAEF,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;AAC/B,CAAC"}
package/dist/replay.js ADDED
@@ -0,0 +1,132 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import crypto from 'node:crypto';
4
+ import { stateDir } from './utils.js';
5
+ function defaultReplayDir() {
6
+ return path.join(stateDir(), 'replay');
7
+ }
8
+ function sha256Bytes(buf) {
9
+ return crypto.createHash('sha256').update(buf).digest('hex');
10
+ }
11
+ function randId() {
12
+ return crypto.randomBytes(6).toString('hex');
13
+ }
14
+ export class ReplayStore {
15
+ dir;
16
+ max;
17
+ indexPath;
18
+ constructor(opts = {}) {
19
+ this.dir = opts.dir ?? defaultReplayDir();
20
+ this.max = opts.maxCheckpoints ?? 200;
21
+ this.indexPath = path.join(this.dir, 'checkpoints.jsonl');
22
+ }
23
+ async init() {
24
+ await fs.mkdir(this.dir, { recursive: true });
25
+ // touch index
26
+ await fs.appendFile(this.indexPath, '');
27
+ }
28
+ async list(limit = 50) {
29
+ const raw = await fs.readFile(this.indexPath, 'utf8').catch(() => '');
30
+ const rows = [];
31
+ for (const line of raw.split(/\r?\n/)) {
32
+ if (!line.trim())
33
+ continue;
34
+ try {
35
+ rows.push(JSON.parse(line));
36
+ }
37
+ catch {
38
+ // ignore
39
+ }
40
+ }
41
+ return rows.slice(-limit).reverse();
42
+ }
43
+ async checkpoint(args) {
44
+ await this.init();
45
+ const ts = new Date().toISOString();
46
+ const id = `${ts.replace(/[:.]/g, '-')}_${randId()}`;
47
+ const beforeHash = sha256Bytes(args.before);
48
+ const afterHash = args.after ? sha256Bytes(args.after) : undefined;
49
+ const cp = {
50
+ id,
51
+ ts,
52
+ op: args.op,
53
+ filePath: args.filePath,
54
+ sha256_before: beforeHash,
55
+ sha256_after: afterHash,
56
+ note: args.note
57
+ };
58
+ const blobDir = path.join(this.dir, 'blobs');
59
+ await fs.mkdir(blobDir, { recursive: true });
60
+ await fs.writeFile(path.join(blobDir, `${id}.before`), args.before);
61
+ if (args.after)
62
+ await fs.writeFile(path.join(blobDir, `${id}.after`), args.after);
63
+ await fs.appendFile(this.indexPath, JSON.stringify(cp) + '\n', 'utf8');
64
+ await this.rotate();
65
+ return cp;
66
+ }
67
+ async get(id) {
68
+ const cands = await this.list(10000);
69
+ const cp = cands.find((x) => x.id === id);
70
+ if (!cp)
71
+ throw new Error(`checkpoint not found: ${id}`);
72
+ const blobDir = path.join(this.dir, 'blobs');
73
+ const before = await fs.readFile(path.join(blobDir, `${id}.before`));
74
+ let after;
75
+ try {
76
+ after = await fs.readFile(path.join(blobDir, `${id}.after`));
77
+ }
78
+ catch {
79
+ // ignore
80
+ }
81
+ return { cp, before, after };
82
+ }
83
+ async rewind(id, readCurrent, write) {
84
+ const { cp, before } = await this.get(id);
85
+ const cur = await readCurrent().catch((e) => {
86
+ if (e?.code === 'ENOENT') {
87
+ return Buffer.from('');
88
+ }
89
+ throw e;
90
+ });
91
+ let warn = '';
92
+ // best-effort safety: ensure current matches cp.after if we have it
93
+ if (cp.sha256_after) {
94
+ const curHash = sha256Bytes(cur);
95
+ if (curHash !== cp.sha256_after) {
96
+ warn = ` [warn: checksum mismatch for ${cp.filePath} (expected ${cp.sha256_after.slice(0, 8)}..., got ${curHash.slice(0, 8)}...)]`;
97
+ }
98
+ }
99
+ const msg = `rewound ${cp.filePath} to checkpoint ${id}`;
100
+ try {
101
+ await this.checkpoint({ op: 'undo', filePath: cp.filePath, before: cur, after: before, note: `rewind to ${id}` });
102
+ }
103
+ catch (e) {
104
+ throw new Error(`${msg}${warn}: checkpoint failed: ${e instanceof Error ? e.message : String(e)}`);
105
+ }
106
+ await write(before);
107
+ return `${msg}${warn}`;
108
+ }
109
+ async rotate() {
110
+ const cps = await this.list(10000);
111
+ if (cps.length <= this.max)
112
+ return;
113
+ // cps returned newest-first; delete oldest
114
+ const keep = new Set(cps.slice(0, this.max).map((c) => c.id));
115
+ const blobDir = path.join(this.dir, 'blobs');
116
+ // Rewrite index with kept checkpoints in chronological order.
117
+ const kept = cps.slice(0, this.max).reverse();
118
+ await fs.writeFile(this.indexPath, kept.map((c) => JSON.stringify(c)).join('\n') + '\n', 'utf8');
119
+ // best-effort blob cleanup
120
+ const ents = await fs.readdir(blobDir).catch(() => []);
121
+ for (const name of ents) {
122
+ const m = /^(.*)\.(before|after)$/.exec(name);
123
+ if (!m)
124
+ continue;
125
+ const id = m[1];
126
+ if (!keep.has(id)) {
127
+ await fs.rm(path.join(blobDir, name), { force: true }).catch(() => { });
128
+ }
129
+ }
130
+ }
131
+ }
132
+ //# sourceMappingURL=replay.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"replay.js","sourceRoot":"","sources":["../src/replay.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import os from 'node:os';
4
+ import { spawn } from 'node:child_process';
5
+ // Re-export atomicWrite from tools for use by index.ts /rewind command
6
+ export { atomicWrite } from './tools.js';
7
+ export async function unifiedDiffFromBuffers(before, after) {
8
+ const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'idlehands-diff-'));
9
+ const aPath = path.join(tmpDir, 'before');
10
+ const bPath = path.join(tmpDir, 'after');
11
+ await fs.writeFile(aPath, before);
12
+ await fs.writeFile(bPath, after);
13
+ const out = await new Promise((resolve) => {
14
+ const p = spawn('git', ['diff', '--no-index', '--text', '--', aPath, bPath], { stdio: ['ignore', 'pipe', 'pipe'] });
15
+ let s = '';
16
+ p.stdout.on('data', (d) => (s += d.toString('utf8')));
17
+ p.stderr.on('data', () => { }); // discard stderr (git warnings)
18
+ p.on('close', () => resolve(s));
19
+ p.on('error', () => resolve(''));
20
+ });
21
+ await fs.rm(tmpDir, { recursive: true, force: true }).catch(() => { });
22
+ return out.trimEnd();
23
+ }
24
+ //# sourceMappingURL=replay_cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"replay_cli.js","sourceRoot":"","sources":["../src/replay_cli.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAClC,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAE3C,uEAAuE;AACvE,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAEzC,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAAC,MAAc,EAAE,KAAa;IACxE,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE,iBAAiB,CAAC,CAAC,CAAC;IAC3E,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IAC1C,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACzC,MAAM,EAAE,CAAC,SAAS,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;IAClC,MAAM,EAAE,CAAC,SAAS,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;IAEjC,MAAM,GAAG,GAAG,MAAM,IAAI,OAAO,CAAS,CAAC,OAAO,EAAE,EAAE;QAChD,MAAM,CAAC,GAAG,KAAK,CAAC,KAAK,EAAE,CAAC,MAAM,EAAE,YAAY,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,CAAC,EAAE,EAAE,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC;QACpH,IAAI,CAAC,GAAG,EAAE,CAAC;QACX,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;QACtD,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC,CAAE,gCAAgC;QAChE,CAAC,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;QAChC,CAAC,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC;IACnC,CAAC,CAAC,CAAC;IAEH,MAAM,EAAE,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;IACtE,OAAO,GAAG,CAAC,OAAO,EAAE,CAAC;AACvB,CAAC"}