@hncode/hncode 0.3.6 → 0.4.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 (148) hide show
  1. package/package.json +5 -3
  2. package/src/agent-task.js +154 -84
  3. package/src/agent.js +533 -145
  4. package/src/clipboard.js +5 -2
  5. package/src/colors.js +242 -238
  6. package/src/config.js +217 -17
  7. package/src/file-history.js +177 -0
  8. package/src/hooks.js +11 -1
  9. package/src/i18n.js +389 -0
  10. package/src/index.js +46 -3
  11. package/src/llm.js +139 -16
  12. package/src/permissions.js +120 -0
  13. package/src/session-hub.js +407 -0
  14. package/src/session.js +31 -0
  15. package/src/term.js +19 -3
  16. package/src/tool-result.js +236 -0
  17. package/src/tools/agent.js +11 -0
  18. package/src/tools/bash.js +84 -17
  19. package/src/tools/edit.js +192 -32
  20. package/src/tools/file-lines.js +5 -0
  21. package/src/tools/index.js +4 -0
  22. package/src/tools/memory.js +88 -0
  23. package/src/tools/read-media-file.js +85 -68
  24. package/src/tools/read.js +15 -1
  25. package/src/tools/utils.js +14 -0
  26. package/src/tools/write.js +35 -5
  27. package/src/tui.js +11665 -9179
  28. package/src/web-auth.js +98 -0
  29. package/src/web-daemon-cli.js +63 -0
  30. package/src/web-daemon-client.js +203 -0
  31. package/src/web-daemon.js +871 -0
  32. package/src/web-public/app.css +1509 -0
  33. package/src/web-public/app.js +2913 -0
  34. package/src/web-public/complete.js +154 -0
  35. package/src/web-public/fonts/JetBrainsMono-Bold.woff2 +0 -0
  36. package/src/web-public/fonts/JetBrainsMono-Regular.woff2 +0 -0
  37. package/src/web-public/fonts/noto-sans-sc-100-400-normal.woff2 +0 -0
  38. package/src/web-public/fonts/noto-sans-sc-101-400-normal.woff2 +0 -0
  39. package/src/web-public/fonts/noto-sans-sc-102-400-normal.woff2 +0 -0
  40. package/src/web-public/fonts/noto-sans-sc-103-400-normal.woff2 +0 -0
  41. package/src/web-public/fonts/noto-sans-sc-104-400-normal.woff2 +0 -0
  42. package/src/web-public/fonts/noto-sans-sc-105-400-normal.woff2 +0 -0
  43. package/src/web-public/fonts/noto-sans-sc-106-400-normal.woff2 +0 -0
  44. package/src/web-public/fonts/noto-sans-sc-107-400-normal.woff2 +0 -0
  45. package/src/web-public/fonts/noto-sans-sc-108-400-normal.woff2 +0 -0
  46. package/src/web-public/fonts/noto-sans-sc-109-400-normal.woff2 +0 -0
  47. package/src/web-public/fonts/noto-sans-sc-110-400-normal.woff2 +0 -0
  48. package/src/web-public/fonts/noto-sans-sc-111-400-normal.woff2 +0 -0
  49. package/src/web-public/fonts/noto-sans-sc-112-400-normal.woff2 +0 -0
  50. package/src/web-public/fonts/noto-sans-sc-113-400-normal.woff2 +0 -0
  51. package/src/web-public/fonts/noto-sans-sc-114-400-normal.woff2 +0 -0
  52. package/src/web-public/fonts/noto-sans-sc-115-400-normal.woff2 +0 -0
  53. package/src/web-public/fonts/noto-sans-sc-116-400-normal.woff2 +0 -0
  54. package/src/web-public/fonts/noto-sans-sc-117-400-normal.woff2 +0 -0
  55. package/src/web-public/fonts/noto-sans-sc-118-400-normal.woff2 +0 -0
  56. package/src/web-public/fonts/noto-sans-sc-119-400-normal.woff2 +0 -0
  57. package/src/web-public/fonts/noto-sans-sc-21-400-normal.woff2 +0 -0
  58. package/src/web-public/fonts/noto-sans-sc-22-400-normal.woff2 +0 -0
  59. package/src/web-public/fonts/noto-sans-sc-23-400-normal.woff2 +0 -0
  60. package/src/web-public/fonts/noto-sans-sc-24-400-normal.woff2 +0 -0
  61. package/src/web-public/fonts/noto-sans-sc-25-400-normal.woff2 +0 -0
  62. package/src/web-public/fonts/noto-sans-sc-26-400-normal.woff2 +0 -0
  63. package/src/web-public/fonts/noto-sans-sc-27-400-normal.woff2 +0 -0
  64. package/src/web-public/fonts/noto-sans-sc-28-400-normal.woff2 +0 -0
  65. package/src/web-public/fonts/noto-sans-sc-29-400-normal.woff2 +0 -0
  66. package/src/web-public/fonts/noto-sans-sc-30-400-normal.woff2 +0 -0
  67. package/src/web-public/fonts/noto-sans-sc-31-400-normal.woff2 +0 -0
  68. package/src/web-public/fonts/noto-sans-sc-32-400-normal.woff2 +0 -0
  69. package/src/web-public/fonts/noto-sans-sc-33-400-normal.woff2 +0 -0
  70. package/src/web-public/fonts/noto-sans-sc-34-400-normal.woff2 +0 -0
  71. package/src/web-public/fonts/noto-sans-sc-35-400-normal.woff2 +0 -0
  72. package/src/web-public/fonts/noto-sans-sc-36-400-normal.woff2 +0 -0
  73. package/src/web-public/fonts/noto-sans-sc-37-400-normal.woff2 +0 -0
  74. package/src/web-public/fonts/noto-sans-sc-38-400-normal.woff2 +0 -0
  75. package/src/web-public/fonts/noto-sans-sc-39-400-normal.woff2 +0 -0
  76. package/src/web-public/fonts/noto-sans-sc-4-400-normal.woff2 +0 -0
  77. package/src/web-public/fonts/noto-sans-sc-40-400-normal.woff2 +0 -0
  78. package/src/web-public/fonts/noto-sans-sc-41-400-normal.woff2 +0 -0
  79. package/src/web-public/fonts/noto-sans-sc-42-400-normal.woff2 +0 -0
  80. package/src/web-public/fonts/noto-sans-sc-43-400-normal.woff2 +0 -0
  81. package/src/web-public/fonts/noto-sans-sc-44-400-normal.woff2 +0 -0
  82. package/src/web-public/fonts/noto-sans-sc-45-400-normal.woff2 +0 -0
  83. package/src/web-public/fonts/noto-sans-sc-46-400-normal.woff2 +0 -0
  84. package/src/web-public/fonts/noto-sans-sc-47-400-normal.woff2 +0 -0
  85. package/src/web-public/fonts/noto-sans-sc-48-400-normal.woff2 +0 -0
  86. package/src/web-public/fonts/noto-sans-sc-49-400-normal.woff2 +0 -0
  87. package/src/web-public/fonts/noto-sans-sc-5-400-normal.woff2 +0 -0
  88. package/src/web-public/fonts/noto-sans-sc-50-400-normal.woff2 +0 -0
  89. package/src/web-public/fonts/noto-sans-sc-51-400-normal.woff2 +0 -0
  90. package/src/web-public/fonts/noto-sans-sc-52-400-normal.woff2 +0 -0
  91. package/src/web-public/fonts/noto-sans-sc-53-400-normal.woff2 +0 -0
  92. package/src/web-public/fonts/noto-sans-sc-54-400-normal.woff2 +0 -0
  93. package/src/web-public/fonts/noto-sans-sc-55-400-normal.woff2 +0 -0
  94. package/src/web-public/fonts/noto-sans-sc-56-400-normal.woff2 +0 -0
  95. package/src/web-public/fonts/noto-sans-sc-57-400-normal.woff2 +0 -0
  96. package/src/web-public/fonts/noto-sans-sc-58-400-normal.woff2 +0 -0
  97. package/src/web-public/fonts/noto-sans-sc-59-400-normal.woff2 +0 -0
  98. package/src/web-public/fonts/noto-sans-sc-6-400-normal.woff2 +0 -0
  99. package/src/web-public/fonts/noto-sans-sc-60-400-normal.woff2 +0 -0
  100. package/src/web-public/fonts/noto-sans-sc-61-400-normal.woff2 +0 -0
  101. package/src/web-public/fonts/noto-sans-sc-62-400-normal.woff2 +0 -0
  102. package/src/web-public/fonts/noto-sans-sc-63-400-normal.woff2 +0 -0
  103. package/src/web-public/fonts/noto-sans-sc-64-400-normal.woff2 +0 -0
  104. package/src/web-public/fonts/noto-sans-sc-65-400-normal.woff2 +0 -0
  105. package/src/web-public/fonts/noto-sans-sc-66-400-normal.woff2 +0 -0
  106. package/src/web-public/fonts/noto-sans-sc-67-400-normal.woff2 +0 -0
  107. package/src/web-public/fonts/noto-sans-sc-68-400-normal.woff2 +0 -0
  108. package/src/web-public/fonts/noto-sans-sc-69-400-normal.woff2 +0 -0
  109. package/src/web-public/fonts/noto-sans-sc-70-400-normal.woff2 +0 -0
  110. package/src/web-public/fonts/noto-sans-sc-71-400-normal.woff2 +0 -0
  111. package/src/web-public/fonts/noto-sans-sc-72-400-normal.woff2 +0 -0
  112. package/src/web-public/fonts/noto-sans-sc-73-400-normal.woff2 +0 -0
  113. package/src/web-public/fonts/noto-sans-sc-74-400-normal.woff2 +0 -0
  114. package/src/web-public/fonts/noto-sans-sc-75-400-normal.woff2 +0 -0
  115. package/src/web-public/fonts/noto-sans-sc-76-400-normal.woff2 +0 -0
  116. package/src/web-public/fonts/noto-sans-sc-77-400-normal.woff2 +0 -0
  117. package/src/web-public/fonts/noto-sans-sc-78-400-normal.woff2 +0 -0
  118. package/src/web-public/fonts/noto-sans-sc-79-400-normal.woff2 +0 -0
  119. package/src/web-public/fonts/noto-sans-sc-80-400-normal.woff2 +0 -0
  120. package/src/web-public/fonts/noto-sans-sc-81-400-normal.woff2 +0 -0
  121. package/src/web-public/fonts/noto-sans-sc-82-400-normal.woff2 +0 -0
  122. package/src/web-public/fonts/noto-sans-sc-83-400-normal.woff2 +0 -0
  123. package/src/web-public/fonts/noto-sans-sc-84-400-normal.woff2 +0 -0
  124. package/src/web-public/fonts/noto-sans-sc-85-400-normal.woff2 +0 -0
  125. package/src/web-public/fonts/noto-sans-sc-86-400-normal.woff2 +0 -0
  126. package/src/web-public/fonts/noto-sans-sc-87-400-normal.woff2 +0 -0
  127. package/src/web-public/fonts/noto-sans-sc-88-400-normal.woff2 +0 -0
  128. package/src/web-public/fonts/noto-sans-sc-89-400-normal.woff2 +0 -0
  129. package/src/web-public/fonts/noto-sans-sc-90-400-normal.woff2 +0 -0
  130. package/src/web-public/fonts/noto-sans-sc-91-400-normal.woff2 +0 -0
  131. package/src/web-public/fonts/noto-sans-sc-97-400-normal.woff2 +0 -0
  132. package/src/web-public/fonts/noto-sans-sc-98-400-normal.woff2 +0 -0
  133. package/src/web-public/fonts/noto-sans-sc-99-400-normal.woff2 +0 -0
  134. package/src/web-public/fonts/noto-sans-sc.css +775 -0
  135. package/src/web-public/fonts.css +31 -0
  136. package/src/web-public/home.css +222 -0
  137. package/src/web-public/home.html +33 -0
  138. package/src/web-public/home.js +160 -0
  139. package/src/web-public/i18n.js +983 -0
  140. package/src/web-public/index.html +196 -0
  141. package/src/web-public/login.css +123 -0
  142. package/src/web-public/login.html +25 -0
  143. package/src/web-public/login.js +46 -0
  144. package/src/web-public/settings.js +290 -0
  145. package/src/web-ws.js +226 -0
  146. package/src/web.js +380 -0
  147. /package/bin/{install.js → install.cjs} +0 -0
  148. /package/bin/{preinstall.js → preinstall.cjs} +0 -0
package/src/tools/read.js CHANGED
@@ -25,6 +25,14 @@ function hashLines(lines, start, end) {
25
25
  // Public alias so edit.js (and the line-range path) share ONE implementation.
26
26
  export const hashRegion = hashLines;
27
27
 
28
+ // Reset the Read snapshot pool after files were rewound on disk. The pool holds
29
+ // per-file content hashes plus the line regions the model has "seen"; once /undo
30
+ // has put an older version back, every hash in it describes content that is no
31
+ // longer there. Keeping them made the NEXT Edit fail with a staleness rejection
32
+ // against a file the model had just watched being restored.
33
+ export function forgetReadPool(ctx) {
34
+ if (ctx && ctx.readPool && typeof ctx.readPool.clear === 'function') ctx.readPool.clear();
35
+ }
28
36
  export const spec = {
29
37
  name: 'Read',
30
38
  description: 'Read a text file. Returns the whole file by default; use line_offset / n_lines for a range.',
@@ -42,6 +50,13 @@ export const spec = {
42
50
  required: ['path'],
43
51
  },
44
52
  async execute(args, ctx) {
53
+ // A missing `path` used to reach resolvePath(undefined), which resolves to the
54
+ // workspace ROOT — so the failure surfaced as an EISDIR from fs.readFileSync
55
+ // instead of "you forgot the path". Cheap to check, and it tells the model
56
+ // exactly what to fix.
57
+ if (typeof args.path !== 'string' || !args.path.trim()) {
58
+ return 'Error: `path` is required and must be a non-empty string.';
59
+ }
45
60
  let p;
46
61
  try { p = resolvePath(args.path, ctx); } catch (e) { return e.message; }
47
62
  let buf;
@@ -50,7 +65,6 @@ export const spec = {
50
65
  return `Cannot read: ${args.path} appears to be a binary file (use a tool suited for binary).`;
51
66
  }
52
67
  let st;
53
- try { st = fs.statSync(p); } catch { st = null; }
54
68
  // NO truncation: the caller asked for the file, so the whole file is returned
55
69
  // (line ranges still work via line_offset / n_lines). The old 1000-line and
56
70
  // 128 KB caps silently cut the content, which also corrupted the
@@ -3,6 +3,7 @@
3
3
  import path from 'node:path';
4
4
  import os from 'node:os';
5
5
  import fs from 'node:fs';
6
+ import { recordBeforeWrite } from '../file-history.js';
6
7
 
7
8
  export const MAX_OUTPUT_BYTES = 128 * 1024; // cap tool results to avoid token blowup
8
9
 
@@ -54,6 +55,19 @@ export function ensureDir(p) {
54
55
  fs.mkdirSync(path.dirname(p), { recursive: true });
55
56
  }
56
57
 
58
+ // Checkpoint `p` before a write tool modifies it, so /undo can put it back (see
59
+ // file-history.js). The record is taken ONCE per turn, so what is captured is the
60
+ // file as it stood before ANY of this turn's changes — the state the user would
61
+ // expect "Undid 1 prompt" to restore.
62
+ //
63
+ // ctx.sessionId identifies the checkpoint store. A caller without one (a
64
+ // subagent's own context, a test) simply gets no checkpoint rather than an error:
65
+ // losing the safety net must never fail the actual write.
66
+ export function checkpointBeforeWrite(p, ctx) {
67
+ if (!ctx || !ctx.sessionId) return false;
68
+ try { return recordBeforeWrite(ctx.sessionId, p); } catch { return false; }
69
+ }
70
+
57
71
  // Read a file fully, returning its string. Throws if not a file.
58
72
  export function readFileBytes(p) {
59
73
  const st = fs.statSync(p);
@@ -1,7 +1,7 @@
1
1
  // Write tool — mirrors the hncode Write tool schema & behavior.
2
2
 
3
3
  import fs from 'node:fs';
4
- import { resolvePath, ensureDir, truncateBuf } from './utils.js';
4
+ import { resolvePath, ensureDir, truncateBuf, checkpointBeforeWrite } from './utils.js';
5
5
 
6
6
  export const spec = {
7
7
  name: 'Write',
@@ -16,14 +16,44 @@ export const spec = {
16
16
  required: ['path', 'content'],
17
17
  },
18
18
  async execute(args, ctx) {
19
+ // Validate BEFORE touching the filesystem. A truncated or malformed tool call
20
+ // arrives as `{ raw, _rawLen }` (agent.js falls back to that shape when the
21
+ // streamed JSON will not parse) or with `content` simply missing — and
22
+ // `fs.writeFileSync(p, undefined)` then threw Node's own
23
+ // `The "data" argument must be of type string…`, which told the model nothing
24
+ // about what to do or why. An unvalidated path is worse: `resolvePath(undefined)`
25
+ // resolves to the workspace root, so a truncated call could target the wrong
26
+ // file while the model believed it was writing a new one.
27
+ const truncated = args && args.raw
28
+ ? ` The arguments did not parse as JSON${args._rawLen ? ` (${args._rawLen} characters received)` : ''} — the call was most likely truncated. Retry, and split a large file across several Write calls (first part with mode "overwrite", the rest appended with mode "append").`
29
+ : '';
30
+ const path_ = args && args.path;
31
+ if (typeof path_ !== 'string' || !path_.trim()) {
32
+ return `Error: \`path\` is required and must be a non-empty string.${truncated}`;
33
+ }
34
+ if (typeof args.content !== 'string') {
35
+ const got = args.content === null ? 'null' : typeof args.content;
36
+ return `Error: \`content\` is required and must be a string (got ${got}).${truncated}`;
37
+ }
19
38
  let p;
20
- try { p = resolvePath(args.path, ctx); } catch (e) { return e.message; }
39
+ try { p = resolvePath(path_, ctx); } catch (e) { return e.message; }
40
+ // `mode` is an enum in the schema, but the schema is not enforced locally: an
41
+ // unexpected value silently meant "overwrite" before. Say so instead.
42
+ const mode = args.mode == null ? 'overwrite' : args.mode;
43
+ if (mode !== 'overwrite' && mode !== 'append') {
44
+ return `Error: invalid mode ${JSON.stringify(mode)}; use "overwrite" or "append".`;
45
+ }
46
+ // Checkpoint the pre-write content so /undo can put it back (file-history.js).
47
+ // Taken BEFORE the write, once per turn, so the recorded version is the state
48
+ // before any of this turn's changes.
49
+ checkpointBeforeWrite(p, ctx);
21
50
  try {
22
51
  ensureDir(p);
23
- const flag = args.mode === 'append' ? 'a' : 'w';
52
+ const flag = mode === 'append' ? 'a' : 'w';
24
53
  fs.writeFileSync(p, args.content, { flag });
25
- } catch (e) { return `Error writing ${args.path}: ${e.message}`; }
54
+ } catch (e) { return `Error writing ${path_}: ${e.message}`; }
26
55
  const size = fs.statSync(p).size;
27
- return `File written: ${args.path} (${size} bytes)`;
56
+ const verb = mode === 'append' ? 'appended to' : 'written';
57
+ return `File ${verb}: ${path_} (${size} bytes)`;
28
58
  },
29
59
  };