@nanhara/hara 0.122.3 → 0.122.4

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 (60) hide show
  1. package/CHANGELOG.md +98 -0
  2. package/README.md +15 -2
  3. package/dist/agent/limits.js +51 -0
  4. package/dist/agent/loop.js +367 -112
  5. package/dist/agent/repeat-guard.js +49 -12
  6. package/dist/checkpoints.js +9 -0
  7. package/dist/cli.js +2 -1
  8. package/dist/config.js +10 -2
  9. package/dist/context/agents-md.js +21 -2
  10. package/dist/context/mentions.js +84 -1
  11. package/dist/context/workspace-scope.js +49 -0
  12. package/dist/cron/deliver.js +61 -38
  13. package/dist/cron/runner.js +423 -65
  14. package/dist/cron/schedule.js +23 -7
  15. package/dist/cron/store.js +709 -43
  16. package/dist/fs-walk.js +279 -7
  17. package/dist/fs-write.js +9 -0
  18. package/dist/gateway/feishu.js +351 -64
  19. package/dist/gateway/flows-pending.js +28 -14
  20. package/dist/gateway/flows.js +306 -31
  21. package/dist/gateway/outbound-files.js +20 -6
  22. package/dist/gateway/runtime-state.js +1485 -0
  23. package/dist/gateway/serve.js +550 -242
  24. package/dist/gateway/telegram.js +279 -29
  25. package/dist/gateway/tts.js +182 -38
  26. package/dist/hooks.js +22 -22
  27. package/dist/index.js +466 -158
  28. package/dist/memory/store.js +8 -5
  29. package/dist/org/planner.js +11 -6
  30. package/dist/process-identity.js +52 -0
  31. package/dist/providers/bounded-turn.js +42 -0
  32. package/dist/recall.js +69 -1
  33. package/dist/runtime.js +24 -0
  34. package/dist/sandbox.js +54 -4
  35. package/dist/search/embed.js +13 -2
  36. package/dist/search/hybrid.js +6 -3
  37. package/dist/search/semindex.js +87 -5
  38. package/dist/security/guardian.js +3 -15
  39. package/dist/security/permissions.js +11 -0
  40. package/dist/serve/server.js +70 -42
  41. package/dist/serve/sessions.js +4 -3
  42. package/dist/tools/agent.js +1 -1
  43. package/dist/tools/ask_user.js +5 -1
  44. package/dist/tools/builtin.js +5 -2
  45. package/dist/tools/codebase.js +67 -18
  46. package/dist/tools/computer.js +149 -68
  47. package/dist/tools/cron.js +72 -18
  48. package/dist/tools/edit.js +3 -2
  49. package/dist/tools/external_agent.js +66 -12
  50. package/dist/tools/memory.js +25 -7
  51. package/dist/tools/patch.js +11 -2
  52. package/dist/tools/registry.js +16 -1
  53. package/dist/tools/search.js +68 -9
  54. package/dist/tools/send.js +1 -1
  55. package/dist/tools/skill.js +1 -1
  56. package/dist/tools/web.js +43 -13
  57. package/dist/tui/App.js +93 -25
  58. package/dist/vision.js +5 -6
  59. package/package.json +2 -2
  60. package/runtime-bootstrap.cjs +22 -0
package/dist/hooks.js CHANGED
@@ -2,12 +2,11 @@
2
2
  // PreToolUse runs BEFORE a tool: non-zero, timeout, signal, or launch failure BLOCKS the call.
3
3
  // PostToolUse runs AFTER: observe-only (format, log, notify). Configured in config.json `hooks` + contributed
4
4
  // by plugins. The command receives {tool, payload} as JSON on stdin + HARA_TOOL_NAME in the env.
5
- import { spawnSync } from "node:child_process";
6
5
  import { resolve } from "node:path";
7
6
  import { loadConfig } from "./config.js";
8
7
  import { pluginHooks } from "./plugins/plugins.js";
9
- import { redactToolSubprocessOutput, toolSubprocessEnv } from "./security/subprocess-env.js";
10
- import { shellCommand } from "./sandbox.js";
8
+ import { redactToolSubprocessOutput } from "./security/subprocess-env.js";
9
+ import { runShell } from "./sandbox.js";
11
10
  const cache = new Map();
12
11
  export function resetHooksCache() {
13
12
  cache.clear();
@@ -42,41 +41,42 @@ export function hasHooks(cwd = process.cwd()) {
42
41
  return !!(h.PreToolUse?.length || h.PostToolUse?.length);
43
42
  }
44
43
  /** Run hooks for an event matching `toolName`. Any abnormal PreToolUse termination fails closed;
45
- * PostToolUse remains observe-only. Sync (hooks are short, opt-in); 30s timeout each. */
46
- export function runHooks(event, toolName, payload, cwd, timeoutMs = 30_000) {
44
+ * PostToolUse remains observe-only. Every child owns a cancellable process group; no hook may hold the
45
+ * event loop past the parent run deadline, and an aborted batch never starts its next command. */
46
+ export async function runHooks(event, toolName, payload, cwd, timeoutMs = 30_000, signal) {
47
47
  for (const h of merged(cwd)[event] ?? []) {
48
+ if (signal?.aborted) {
49
+ return event === "PreToolUse"
50
+ ? { block: true, message: "⛔ blocked because the agent run was cancelled before the PreToolUse hook completed" }
51
+ : { block: false, message: "" };
52
+ }
48
53
  if (!matches(h.matcher, toolName))
49
54
  continue;
50
- let r;
51
55
  try {
52
56
  // Hooks are user/plugin-configured external code. Route them through the same command preflight and
53
57
  // macOS protected-read mask as Bash, even though hooks remain observe-only after a tool has run.
54
- const shell = shellCommand(h.command, cwd, "off");
55
- r = spawnSync(shell.cmd, shell.args, {
56
- cwd,
57
- input: JSON.stringify({ tool: toolName, payload }),
58
- encoding: "utf8",
58
+ await runShell(h.command, cwd, "off", {
59
+ signal,
59
60
  timeout: timeoutMs,
60
- env: toolSubprocessEnv(process.env, { HARA_TOOL_NAME: toolName }),
61
+ maxBuffer: 256 * 1024,
62
+ input: JSON.stringify({ tool: toolName, payload }),
63
+ env: { HARA_TOOL_NAME: toolName },
61
64
  });
62
65
  }
63
66
  catch (error) {
64
67
  if (event === "PreToolUse") {
65
- const detail = redactToolSubprocessOutput(error instanceof Error ? error.message : String(error));
66
- return { block: true, message: `⛔ blocked because a PreToolUse hook could not start${detail ? `: ${detail}` : ""}` };
68
+ const failure = error;
69
+ const output = redactToolSubprocessOutput(`${failure.stdout ?? ""}${failure.stderr ?? ""}`.trim());
70
+ const detail = redactToolSubprocessOutput(failure.message || String(error));
71
+ return {
72
+ block: true,
73
+ message: `⛔ blocked by a PreToolUse hook${output ? `: ${output}` : detail ? ` (${detail})` : ""}`,
74
+ };
67
75
  }
68
76
  // PostToolUse is best-effort: a policy-blocked or broken observer must never rewrite the result of a
69
77
  // tool that already completed. In particular, do not retry it through an unsandboxed fallback shell.
70
78
  continue;
71
79
  }
72
- // A hook is allowed to ignore stdin. On Linux, a command that exits successfully before Node finishes
73
- // writing `input` can report EPIPE in r.error *alongside status=0*. The wait status is authoritative in
74
- // that case; true launch/write failures still have status=null, while timeouts/signals remain blocked.
75
- if (event === "PreToolUse" && (r.status !== 0 || !!r.signal)) {
76
- const output = redactToolSubprocessOutput((String(r.stdout ?? "") + String(r.stderr ?? "")).trim());
77
- const failure = redactToolSubprocessOutput(r.error?.message || (r.signal ? `terminated by ${r.signal}` : `exit ${r.status ?? "unknown"}`));
78
- return { block: true, message: `⛔ blocked by a PreToolUse hook${output ? `: ${output}` : ` (${failure})`}` };
79
- }
80
80
  }
81
81
  return { block: false, message: "" };
82
82
  }