@hncode/hncode 0.3.6 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +4 -2
- package/src/agent-task.js +154 -84
- package/src/agent.js +533 -145
- package/src/clipboard.js +5 -2
- package/src/colors.js +242 -238
- package/src/config.js +217 -17
- package/src/file-history.js +177 -0
- package/src/hooks.js +11 -1
- package/src/i18n.js +389 -0
- package/src/index.js +46 -3
- package/src/llm.js +139 -16
- package/src/permissions.js +120 -0
- package/src/session-hub.js +407 -0
- package/src/session.js +31 -0
- package/src/term.js +19 -3
- package/src/tool-result.js +236 -0
- package/src/tools/agent.js +11 -0
- package/src/tools/bash.js +84 -17
- package/src/tools/edit.js +192 -32
- package/src/tools/file-lines.js +5 -0
- package/src/tools/index.js +4 -0
- package/src/tools/memory.js +88 -0
- package/src/tools/read-media-file.js +85 -68
- package/src/tools/read.js +15 -1
- package/src/tools/utils.js +14 -0
- package/src/tools/write.js +35 -5
- package/src/tui.js +11665 -9179
- package/src/web-auth.js +98 -0
- package/src/web-daemon-cli.js +63 -0
- package/src/web-daemon-client.js +203 -0
- package/src/web-daemon.js +871 -0
- package/src/web-public/app.css +1509 -0
- package/src/web-public/app.js +2913 -0
- package/src/web-public/complete.js +154 -0
- package/src/web-public/fonts/JetBrainsMono-Bold.woff2 +0 -0
- package/src/web-public/fonts/JetBrainsMono-Regular.woff2 +0 -0
- package/src/web-public/fonts/noto-sans-sc-100-400-normal.woff2 +0 -0
- package/src/web-public/fonts/noto-sans-sc-101-400-normal.woff2 +0 -0
- package/src/web-public/fonts/noto-sans-sc-102-400-normal.woff2 +0 -0
- package/src/web-public/fonts/noto-sans-sc-103-400-normal.woff2 +0 -0
- package/src/web-public/fonts/noto-sans-sc-104-400-normal.woff2 +0 -0
- package/src/web-public/fonts/noto-sans-sc-105-400-normal.woff2 +0 -0
- package/src/web-public/fonts/noto-sans-sc-106-400-normal.woff2 +0 -0
- package/src/web-public/fonts/noto-sans-sc-107-400-normal.woff2 +0 -0
- package/src/web-public/fonts/noto-sans-sc-108-400-normal.woff2 +0 -0
- package/src/web-public/fonts/noto-sans-sc-109-400-normal.woff2 +0 -0
- package/src/web-public/fonts/noto-sans-sc-110-400-normal.woff2 +0 -0
- package/src/web-public/fonts/noto-sans-sc-111-400-normal.woff2 +0 -0
- package/src/web-public/fonts/noto-sans-sc-112-400-normal.woff2 +0 -0
- package/src/web-public/fonts/noto-sans-sc-113-400-normal.woff2 +0 -0
- package/src/web-public/fonts/noto-sans-sc-114-400-normal.woff2 +0 -0
- package/src/web-public/fonts/noto-sans-sc-115-400-normal.woff2 +0 -0
- package/src/web-public/fonts/noto-sans-sc-116-400-normal.woff2 +0 -0
- package/src/web-public/fonts/noto-sans-sc-117-400-normal.woff2 +0 -0
- package/src/web-public/fonts/noto-sans-sc-118-400-normal.woff2 +0 -0
- package/src/web-public/fonts/noto-sans-sc-119-400-normal.woff2 +0 -0
- package/src/web-public/fonts/noto-sans-sc-21-400-normal.woff2 +0 -0
- package/src/web-public/fonts/noto-sans-sc-22-400-normal.woff2 +0 -0
- package/src/web-public/fonts/noto-sans-sc-23-400-normal.woff2 +0 -0
- package/src/web-public/fonts/noto-sans-sc-24-400-normal.woff2 +0 -0
- package/src/web-public/fonts/noto-sans-sc-25-400-normal.woff2 +0 -0
- package/src/web-public/fonts/noto-sans-sc-26-400-normal.woff2 +0 -0
- package/src/web-public/fonts/noto-sans-sc-27-400-normal.woff2 +0 -0
- package/src/web-public/fonts/noto-sans-sc-28-400-normal.woff2 +0 -0
- package/src/web-public/fonts/noto-sans-sc-29-400-normal.woff2 +0 -0
- package/src/web-public/fonts/noto-sans-sc-30-400-normal.woff2 +0 -0
- package/src/web-public/fonts/noto-sans-sc-31-400-normal.woff2 +0 -0
- package/src/web-public/fonts/noto-sans-sc-32-400-normal.woff2 +0 -0
- package/src/web-public/fonts/noto-sans-sc-33-400-normal.woff2 +0 -0
- package/src/web-public/fonts/noto-sans-sc-34-400-normal.woff2 +0 -0
- package/src/web-public/fonts/noto-sans-sc-35-400-normal.woff2 +0 -0
- package/src/web-public/fonts/noto-sans-sc-36-400-normal.woff2 +0 -0
- package/src/web-public/fonts/noto-sans-sc-37-400-normal.woff2 +0 -0
- package/src/web-public/fonts/noto-sans-sc-38-400-normal.woff2 +0 -0
- package/src/web-public/fonts/noto-sans-sc-39-400-normal.woff2 +0 -0
- package/src/web-public/fonts/noto-sans-sc-4-400-normal.woff2 +0 -0
- package/src/web-public/fonts/noto-sans-sc-40-400-normal.woff2 +0 -0
- package/src/web-public/fonts/noto-sans-sc-41-400-normal.woff2 +0 -0
- package/src/web-public/fonts/noto-sans-sc-42-400-normal.woff2 +0 -0
- package/src/web-public/fonts/noto-sans-sc-43-400-normal.woff2 +0 -0
- package/src/web-public/fonts/noto-sans-sc-44-400-normal.woff2 +0 -0
- package/src/web-public/fonts/noto-sans-sc-45-400-normal.woff2 +0 -0
- package/src/web-public/fonts/noto-sans-sc-46-400-normal.woff2 +0 -0
- package/src/web-public/fonts/noto-sans-sc-47-400-normal.woff2 +0 -0
- package/src/web-public/fonts/noto-sans-sc-48-400-normal.woff2 +0 -0
- package/src/web-public/fonts/noto-sans-sc-49-400-normal.woff2 +0 -0
- package/src/web-public/fonts/noto-sans-sc-5-400-normal.woff2 +0 -0
- package/src/web-public/fonts/noto-sans-sc-50-400-normal.woff2 +0 -0
- package/src/web-public/fonts/noto-sans-sc-51-400-normal.woff2 +0 -0
- package/src/web-public/fonts/noto-sans-sc-52-400-normal.woff2 +0 -0
- package/src/web-public/fonts/noto-sans-sc-53-400-normal.woff2 +0 -0
- package/src/web-public/fonts/noto-sans-sc-54-400-normal.woff2 +0 -0
- package/src/web-public/fonts/noto-sans-sc-55-400-normal.woff2 +0 -0
- package/src/web-public/fonts/noto-sans-sc-56-400-normal.woff2 +0 -0
- package/src/web-public/fonts/noto-sans-sc-57-400-normal.woff2 +0 -0
- package/src/web-public/fonts/noto-sans-sc-58-400-normal.woff2 +0 -0
- package/src/web-public/fonts/noto-sans-sc-59-400-normal.woff2 +0 -0
- package/src/web-public/fonts/noto-sans-sc-6-400-normal.woff2 +0 -0
- package/src/web-public/fonts/noto-sans-sc-60-400-normal.woff2 +0 -0
- package/src/web-public/fonts/noto-sans-sc-61-400-normal.woff2 +0 -0
- package/src/web-public/fonts/noto-sans-sc-62-400-normal.woff2 +0 -0
- package/src/web-public/fonts/noto-sans-sc-63-400-normal.woff2 +0 -0
- package/src/web-public/fonts/noto-sans-sc-64-400-normal.woff2 +0 -0
- package/src/web-public/fonts/noto-sans-sc-65-400-normal.woff2 +0 -0
- package/src/web-public/fonts/noto-sans-sc-66-400-normal.woff2 +0 -0
- package/src/web-public/fonts/noto-sans-sc-67-400-normal.woff2 +0 -0
- package/src/web-public/fonts/noto-sans-sc-68-400-normal.woff2 +0 -0
- package/src/web-public/fonts/noto-sans-sc-69-400-normal.woff2 +0 -0
- package/src/web-public/fonts/noto-sans-sc-70-400-normal.woff2 +0 -0
- package/src/web-public/fonts/noto-sans-sc-71-400-normal.woff2 +0 -0
- package/src/web-public/fonts/noto-sans-sc-72-400-normal.woff2 +0 -0
- package/src/web-public/fonts/noto-sans-sc-73-400-normal.woff2 +0 -0
- package/src/web-public/fonts/noto-sans-sc-74-400-normal.woff2 +0 -0
- package/src/web-public/fonts/noto-sans-sc-75-400-normal.woff2 +0 -0
- package/src/web-public/fonts/noto-sans-sc-76-400-normal.woff2 +0 -0
- package/src/web-public/fonts/noto-sans-sc-77-400-normal.woff2 +0 -0
- package/src/web-public/fonts/noto-sans-sc-78-400-normal.woff2 +0 -0
- package/src/web-public/fonts/noto-sans-sc-79-400-normal.woff2 +0 -0
- package/src/web-public/fonts/noto-sans-sc-80-400-normal.woff2 +0 -0
- package/src/web-public/fonts/noto-sans-sc-81-400-normal.woff2 +0 -0
- package/src/web-public/fonts/noto-sans-sc-82-400-normal.woff2 +0 -0
- package/src/web-public/fonts/noto-sans-sc-83-400-normal.woff2 +0 -0
- package/src/web-public/fonts/noto-sans-sc-84-400-normal.woff2 +0 -0
- package/src/web-public/fonts/noto-sans-sc-85-400-normal.woff2 +0 -0
- package/src/web-public/fonts/noto-sans-sc-86-400-normal.woff2 +0 -0
- package/src/web-public/fonts/noto-sans-sc-87-400-normal.woff2 +0 -0
- package/src/web-public/fonts/noto-sans-sc-88-400-normal.woff2 +0 -0
- package/src/web-public/fonts/noto-sans-sc-89-400-normal.woff2 +0 -0
- package/src/web-public/fonts/noto-sans-sc-90-400-normal.woff2 +0 -0
- package/src/web-public/fonts/noto-sans-sc-91-400-normal.woff2 +0 -0
- package/src/web-public/fonts/noto-sans-sc-97-400-normal.woff2 +0 -0
- package/src/web-public/fonts/noto-sans-sc-98-400-normal.woff2 +0 -0
- package/src/web-public/fonts/noto-sans-sc-99-400-normal.woff2 +0 -0
- package/src/web-public/fonts/noto-sans-sc.css +775 -0
- package/src/web-public/fonts.css +31 -0
- package/src/web-public/home.css +222 -0
- package/src/web-public/home.html +33 -0
- package/src/web-public/home.js +160 -0
- package/src/web-public/i18n.js +983 -0
- package/src/web-public/index.html +196 -0
- package/src/web-public/login.css +123 -0
- package/src/web-public/login.html +25 -0
- package/src/web-public/login.js +46 -0
- package/src/web-public/settings.js +290 -0
- package/src/web-ws.js +226 -0
- package/src/web.js +380 -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
|
package/src/tools/utils.js
CHANGED
|
@@ -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);
|
package/src/tools/write.js
CHANGED
|
@@ -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(
|
|
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 =
|
|
52
|
+
const flag = mode === 'append' ? 'a' : 'w';
|
|
24
53
|
fs.writeFileSync(p, args.content, { flag });
|
|
25
|
-
} catch (e) { return `Error writing ${
|
|
54
|
+
} catch (e) { return `Error writing ${path_}: ${e.message}`; }
|
|
26
55
|
const size = fs.statSync(p).size;
|
|
27
|
-
|
|
56
|
+
const verb = mode === 'append' ? 'appended to' : 'written';
|
|
57
|
+
return `File ${verb}: ${path_} (${size} bytes)`;
|
|
28
58
|
},
|
|
29
59
|
};
|