@nanhara/hara 0.119.0 → 0.119.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.
- package/CHANGELOG.md +16 -0
- package/dist/agent/loop.js +17 -2
- package/dist/sandbox.js +10 -1
- package/dist/tools/builtin.js +9 -1
- package/dist/tools/registry.js +10 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,22 @@ All notable changes to `@nanhara/hara`.
|
|
|
5
5
|
> Versioning (pre-1.0, SemVer-style): the **minor** (middle) number bumps for a **new feature**; the
|
|
6
6
|
> **patch** (last) number bumps for **optimizations/fixes of existing features**.
|
|
7
7
|
|
|
8
|
+
## 0.119.1 — field-feedback robustness: param gate · no-hang git · actionable timeouts · stale-artifact rule
|
|
9
|
+
|
|
10
|
+
- **Required-parameter gate.** A tool call arriving WITHOUT its required parameters (observed:
|
|
11
|
+
qwen3.7-plus dropping write_file's path/content, then retrying the same broken call forever) is
|
|
12
|
+
now rejected before execution with an error naming exactly what's missing; repeat-guard
|
|
13
|
+
escalates if the model loops anyway. Empty strings stay legal.
|
|
14
|
+
- **git can no longer hang to the timeout.** Shell commands run with `GIT_TERMINAL_PROMPT=0` +
|
|
15
|
+
`GCM_INTERACTIVE=never` (user env overrides win): an https op that wants credentials fails in
|
|
16
|
+
seconds with a real auth error instead of sitting silently for 5 minutes.
|
|
17
|
+
- **Timeouts tell you what to do next**: larger `timeout_ms` for long builds, `background:true`
|
|
18
|
+
for servers, and for network ops — diagnose or skip, never blind-retry.
|
|
19
|
+
- **Stale-artifact rule** (prompt): before previewing generated artifacts, verify they're newer
|
|
20
|
+
than their sources; AGENTS.md/README command sequences are authoritative — middle steps (render/
|
|
21
|
+
build) are never skipped. Pairs with hara-design 0.3.6's deterministic staleness tripwire +
|
|
22
|
+
preview idle auto-exit.
|
|
23
|
+
|
|
8
24
|
## 0.119.0 — project panels: the chat ↔ live-preview split
|
|
9
25
|
|
|
10
26
|
- **Plugin panels become project-aware.** A plugin panel can declare `detect` markers (e.g.
|
package/dist/agent/loop.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getTool, toolSpecs } from "../tools/registry.js";
|
|
1
|
+
import { getTool, toolSpecs, missingRequired } from "../tools/registry.js";
|
|
2
2
|
import { stdout } from "node:process";
|
|
3
3
|
import { c, out } from "../ui.js";
|
|
4
4
|
import { activity } from "../activity.js";
|
|
@@ -83,7 +83,12 @@ can't serve private repos), don't switch protocols — hara already fast-fails r
|
|
|
83
83
|
diagnose instead. git ignores the macOS system / Clash proxy unless configured (git config --global
|
|
84
84
|
http.proxy), so a browser that reaches a site doesn't mean the terminal does — verify connectivity yourself
|
|
85
85
|
rather than trusting "the network is fine". If a step's output artifact already exists and is newer than its
|
|
86
|
-
inputs, skip re-running it
|
|
86
|
+
inputs, skip re-running it — and the INVERSE: before serving or previewing GENERATED artifacts (a gallery,
|
|
87
|
+
site, build output), check they are newer than their sources (compare mtimes or the latest commit time); if
|
|
88
|
+
the sources changed since the artifacts were built, run the project's documented build/render steps FIRST.
|
|
89
|
+
When AGENTS.md / README / package.json document a command sequence (e.g. pull → render → build → preview),
|
|
90
|
+
that ordering is authoritative — never skip the middle steps, or you serve stale output and the user sees
|
|
91
|
+
two-day-old work. After completing a task, give a one-line summary.`;
|
|
87
92
|
/** When running inside `hara gateway`, tell the agent it's in a chat — so it delivers files via send_file
|
|
88
93
|
* (the only channel that reaches the peer) and never reaches for the desktop client / computer tool. */
|
|
89
94
|
function gatewayNote() {
|
|
@@ -433,6 +438,16 @@ export async function runAgent(history, opts) {
|
|
|
433
438
|
}
|
|
434
439
|
activity.inc();
|
|
435
440
|
try {
|
|
441
|
+
// Defensive parameter gate — some models drop required tool parameters outright (observed:
|
|
442
|
+
// qwen3.7-plus sending write_file without path/content, then retrying the same broken call
|
|
443
|
+
// forever). Reject precisely and name what's missing; repeat-guard escalates if it loops.
|
|
444
|
+
const missing = missingRequired(p.tool, p.tu.input);
|
|
445
|
+
if (missing.length) {
|
|
446
|
+
const msg = `Error: tool call NOT executed — missing required parameter${missing.length > 1 ? "s" : ""}: ` +
|
|
447
|
+
`${missing.join(", ")}. Send the call again with ALL required parameters (${(p.tool.input_schema.required ?? []).join(", ")}) present and complete.`;
|
|
448
|
+
results[idx] = { id: p.tu.id, name: p.tu.name, content: msg + recordCall(p.tu.name, p.tu.input, msg, true), isError: true };
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
436
451
|
const pre = runHooks("PreToolUse", p.tu.name, p.tu.input, ctx.cwd); // a hook may veto the call
|
|
437
452
|
if (pre.block) {
|
|
438
453
|
results[idx] = { id: p.tu.id, name: p.tu.name, content: pre.message, isError: true };
|
package/dist/sandbox.js
CHANGED
|
@@ -93,7 +93,16 @@ export function maybeWarnUnsandboxed(mode) {
|
|
|
93
93
|
export function runShell(command, cwd, mode, opts) {
|
|
94
94
|
const { cmd, args } = shellCommand(command, cwd, mode);
|
|
95
95
|
return new Promise((resolve, reject) => {
|
|
96
|
-
|
|
96
|
+
// Non-interactive by contract: there is no terminal to answer a credential prompt, so a git
|
|
97
|
+
// https op against a private repo would otherwise sit silently until the timeout (observed as
|
|
98
|
+
// "git hangs 5 minutes"). With prompts disabled it fails in seconds with a real auth error.
|
|
99
|
+
// Users' credential helpers (keychain/GCM store) still work — only interactive PROMPTS are off.
|
|
100
|
+
const env = {
|
|
101
|
+
...process.env,
|
|
102
|
+
GIT_TERMINAL_PROMPT: process.env.GIT_TERMINAL_PROMPT ?? "0",
|
|
103
|
+
GCM_INTERACTIVE: process.env.GCM_INTERACTIVE ?? "never",
|
|
104
|
+
};
|
|
105
|
+
const child = spawn(cmd, args, { cwd, env });
|
|
97
106
|
let stdout = "";
|
|
98
107
|
let stderr = "";
|
|
99
108
|
let timedOut = false;
|
package/dist/tools/builtin.js
CHANGED
|
@@ -179,7 +179,15 @@ registerTool({
|
|
|
179
179
|
return capHeadTail(combined.trim() || "(no output)");
|
|
180
180
|
}
|
|
181
181
|
catch (e) {
|
|
182
|
-
|
|
182
|
+
let base = `Command failed: ${e.message}\n${e.stdout || ""}${e.stderr || ""}`;
|
|
183
|
+
// Timeout gets an ACTIONABLE next step, not just a corpse — the model (and user) should pick a
|
|
184
|
+
// lane instead of blind-retrying into the same wall.
|
|
185
|
+
if (/timed out after \d+ms/.test(String(e.message))) {
|
|
186
|
+
base +=
|
|
187
|
+
`\n⏱ hara: the command hit its ${input.timeout_ms ?? 300_000}ms cap and was killed. Pick ONE: ` +
|
|
188
|
+
`a long build/transform → re-run with a larger timeout_ms; a server/watcher → background:true; ` +
|
|
189
|
+
`a network op (git/curl/npm) → do NOT just retry — check connectivity/proxy or skip this step and tell the user.`;
|
|
190
|
+
}
|
|
183
191
|
// Network fault tolerance — if this was a genuine host-unreachability (connect timeout / DNS, NOT
|
|
184
192
|
// auth / 404 / connection-refused), remember the host so we fast-fail future ops to it this session.
|
|
185
193
|
if (isConnectFailure(base)) {
|
package/dist/tools/registry.js
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
/** Names of required parameters that are ABSENT (undefined/null) in a tool call's input. Defends
|
|
2
|
+
* against models that drop parameters outright (observed: qwen3.7-plus losing write_file's
|
|
3
|
+
* path/content mid-stream) — the loop rejects the call with a precise error instead of executing
|
|
4
|
+
* garbage, and repeat-guard escalates if the model loops on the same broken shape. Empty strings
|
|
5
|
+
* are NOT flagged (writing an empty file is legitimate). */
|
|
6
|
+
export function missingRequired(tool, input) {
|
|
7
|
+
const req = tool.input_schema.required ?? [];
|
|
8
|
+
const obj = (input && typeof input === "object" ? input : {});
|
|
9
|
+
return req.filter((k) => obj[k] === undefined || obj[k] === null);
|
|
10
|
+
}
|
|
1
11
|
const registry = new Map();
|
|
2
12
|
export function registerTool(t) {
|
|
3
13
|
registry.set(t.name, t);
|