@nanhara/hara 0.122.2 → 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 (64) hide show
  1. package/CHANGELOG.md +108 -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 +31 -17
  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/sessions.js +3 -3
  25. package/dist/gateway/telegram.js +279 -29
  26. package/dist/gateway/tts.js +182 -38
  27. package/dist/hooks.js +22 -22
  28. package/dist/index.js +466 -158
  29. package/dist/memory/store.js +8 -5
  30. package/dist/org/planner.js +11 -6
  31. package/dist/org/projects.js +3 -3
  32. package/dist/process-identity.js +52 -0
  33. package/dist/providers/bounded-turn.js +42 -0
  34. package/dist/recall.js +69 -1
  35. package/dist/runtime.js +24 -0
  36. package/dist/sandbox.js +54 -4
  37. package/dist/search/embed.js +13 -2
  38. package/dist/search/hybrid.js +6 -3
  39. package/dist/search/semindex.js +87 -5
  40. package/dist/security/guardian.js +3 -15
  41. package/dist/security/permissions.js +11 -0
  42. package/dist/serve/server.js +70 -42
  43. package/dist/serve/sessions.js +4 -3
  44. package/dist/sync-sleep.js +46 -0
  45. package/dist/tools/agent.js +1 -1
  46. package/dist/tools/ask_user.js +5 -1
  47. package/dist/tools/builtin.js +5 -2
  48. package/dist/tools/codebase.js +67 -18
  49. package/dist/tools/computer.js +149 -68
  50. package/dist/tools/cron.js +72 -18
  51. package/dist/tools/edit.js +3 -2
  52. package/dist/tools/external_agent.js +66 -12
  53. package/dist/tools/memory.js +25 -7
  54. package/dist/tools/patch.js +11 -2
  55. package/dist/tools/registry.js +16 -1
  56. package/dist/tools/search.js +68 -9
  57. package/dist/tools/send.js +1 -1
  58. package/dist/tools/skill.js +1 -1
  59. package/dist/tools/task.js +3 -3
  60. package/dist/tools/web.js +43 -13
  61. package/dist/tui/App.js +93 -25
  62. package/dist/vision.js +5 -6
  63. package/package.json +2 -2
  64. package/runtime-bootstrap.cjs +22 -0
package/CHANGELOG.md CHANGED
@@ -5,6 +5,114 @@ 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.122.4 — 2026-07-14 — bounded agent lifecycle and chat delivery hardening
9
+
10
+ - **An active agent can no longer renew itself forever.** Every CLI, headless, org, and Desktop/serve turn
11
+ now has a 30-minute total wall-clock deadline and a 64 model/tool-round ceiling, independent of streaming
12
+ activity and per-tool timeouts. A visible warning fires after five minutes or at 75% of the round budget;
13
+ reaching either hard boundary returns an actionable `halted` outcome to the terminal, Desktop protocol, or
14
+ gateway reply. `runTimeoutMs` (friendly `30m`/`90s` values; hard max 2h) and `maxAgentRounds` (hard max 256)
15
+ can raise/lower the bounds but cannot disable them.
16
+ - **Repeated failures now trip a real circuit-breaker.** The third identical failing tool call closes its
17
+ protocol round and stops the run instead of merely asking the model not to repeat itself. A final round cap
18
+ still catches alternating or superficially changing loops.
19
+ - **Cancellation reaches owned work.** Tools receive the combined user/deadline signal; foreground shell and
20
+ external-agent process trees terminate on it, and read-only sub-agents inherit the parent's cancellation
21
+ with tighter 8-minute/24-round limits. Providers/tools that ignore `AbortSignal` are raced at the core, so
22
+ Hara itself still settles on time and never executes a late next tool call. File, memory, skill, and gateway
23
+ outbox writes re-check cancellation immediately before commit, while session ownership remains held until an
24
+ uncooperative tool physically settles, preventing a timed-out write from racing the next turn.
25
+ - **One-shot model helpers are hard-bounded too.** Setup checks, role routing, plan decomposition and
26
+ verification, commit-message generation, session naming, conversation compaction, vision analysis, and the
27
+ security guardian now combine cooperative abort with a hard Promise deadline. A custom provider that ignores
28
+ cancellation can no longer strand the CLI before or after the main agent loop.
29
+ - **Cron can no longer hold its global tick lock forever.** Scheduled jobs keep their 30-minute per-job
30
+ deadline and the whole tick now has a non-renewable 60-minute wall-clock watchdog. Timeout cancellation
31
+ force-stops the owned process tree, always releases the tick lock, records `running`/`timed_out`, duration and
32
+ last error durably, counts timeouts toward `alertAfter`, and lets later due jobs run after an individual job
33
+ timeout (a total-tick timeout stops the remainder). `HARA_CRON_JOB_TIMEOUT_MS` and
34
+ `HARA_CRON_TICK_TIMEOUT_MS` tune the millisecond bounds, with hard maxima of 24 hours and 5 hours; a scheduled
35
+ job is additionally capped by its tick. `hara cron list` now makes active and timed-out work explicit.
36
+ - **Cron timeout notifications survive transport outages and restarts.** Each outcome/threshold alarm is
37
+ committed with terminal state before delivery, keeps one stable idempotency key across bounded per-tick
38
+ retries, and is removed (with alert cooldown recorded) only after confirmed transport success. Each job's
39
+ queue is capped at 64: launches reserve room for outcome + alert, disable before overflow, and recovery may
40
+ compact only an old outcome while marking that loss explicitly; jobs.json also has schema/count/byte limits.
41
+ Configure the failure threshold with `--alert-after 1..1000` or the cron tool's `alertAfter` field.
42
+ - **Cron recovery no longer mistakes PID reuse for a live owner forever.** The tick and synchronous jobs-store
43
+ locks use Linux/macOS process-birth identity; store commits additionally use a conservative renewable lease,
44
+ snapshot CAS, and a token fence.
45
+ Unknown identity formats and live legacy identity-less commit guards fail closed instead of being age-stolen.
46
+ A `running` record beyond the 24-hour hard job maximum plus grace is marked interrupted and disabled even if
47
+ its PID is alive. Recovery never kills or replays the possibly orphaned task; it persists the failure
48
+ notification for operator verification. Manual runs also refuse every unresolved `running` marker; if its
49
+ parent is dead, Hara records an interrupted/disabled state and requires operator inspection before retrying.
50
+ - **Creation-minute cron jobs now use an explicit one-shot due marker.** A job added just after that minute's
51
+ OS tick runs once on the next tick, while disabling it clears the marker so a later enable cannot replay a
52
+ days- or months-old occurrence. Absolute one-shot schedules already in the past are rejected instead of being
53
+ displayed forever as a misleading next run.
54
+ - **`hara resume <id>` now re-enters the correct runtime without blocking terminal input.** The launcher uses
55
+ the same runtime-aware self command as cron/gateway and waits asynchronously with inherited stdio: npm/Node
56
+ keeps its script entry, while a Bun-compiled standalone re-executes only the binary instead of treating its
57
+ virtual/user `argv[1]` as JavaScript. This removes the standalone resume hang/high-CPU/dead-input failure.
58
+ - **The home directory is no longer an implicit repository.** Canonical/real-path checks suppress first-run
59
+ AGENTS generation and reject `hara init`, repo indexing, codebase search, and recursive grep/glob/inventory at
60
+ the Home root (including symlink aliases). Hara shows a `cd /path/to/project` hint; non-recursive `ls`, explicit
61
+ file reads, and explicitly selected child directories remain usable.
62
+ - **Recursive project discovery is streaming and interruptible.** Glob/grep fallback, codebase search, semantic
63
+ indexing, recall, Desktop file lookup, did-you-mean, and `@dir` expansion share file, directory, Dirent, and
64
+ wall-clock bounds from the start of discovery. Cached empty-directory forests and a single extremely wide
65
+ directory can no longer block the event loop past the agent deadline; truncated indexes are never published.
66
+ - **Windows shell and portable-home discovery are deterministic.** Hara probes the conventional Git Bash
67
+ installation paths before falling back to `cmd.exe`, understands MSYS drive paths and UNC paths, and honors an
68
+ explicit `HOME` ahead of `USERPROFILE` so portable/Git-Bash sessions do not silently load another profile.
69
+
70
+ - **Long chat replies now split on natural line/word boundaries without tearing Unicode.** Feishu and
71
+ Telegram bubbles preserve the exact reply while keeping emoji ZWJ sequences, flags, combining marks, CRLF,
72
+ and surrogate pairs intact whenever the platform limit permits.
73
+ - **Only one Hara gateway process may own a configured bot connection at a time.** A credential-scoped,
74
+ crash-safe private lease refuses a second live Feishu/Telegram/etc. connection and reclaims only a
75
+ proven-dead owner, preventing multiple WebSockets from replying to the same event.
76
+ - **Stable inbound message ids are deduplicated across restarts.** Successfully handled Feishu and Telegram
77
+ ids are persisted for one hour in a byte- and count-bounded owner-only store; failures remain retryable,
78
+ concurrent duplicates and startup replays older than 30 seconds are ignored, and different bot credentials
79
+ use isolated stores. State contains ids/timestamps and a one-way connection namespace only—never message
80
+ text, user data, or credentials.
81
+ - **Outbound chat transport is bounded and ordered across adapter instances.** Telegram and Feishu text/file
82
+ requests use real `AbortSignal` cancellation plus a hard 30s/120s ceiling; a credential-scoped, process-wide
83
+ per-chat lane prevents long multipart replies from interleaving with one-shot or flow delivery. A failed or
84
+ timed-out call settles for its caller without freezing shutdown or the inbound retry claim; an ambiguous
85
+ underlying transfer remains quarantined in that live process's lane until it actually settles.
86
+ - **Flow side effects resume from durable receipts.** Each reply, notification target, and pending approval has
87
+ a stable, credential-scoped receipt. Redelivery skips locally completed receipts, pending actions reuse their
88
+ stable opaque id, and idempotency keys are forwarded where supported. A crash after remote acceptance but
89
+ before the local receipt commit can still cause at-least-once delivery on transports without server-side
90
+ deduplication. No-tool model decisions, bounded failure attempts, and partial target/chunk progress also
91
+ survive restarts, so a retry cannot mix a newly generated answer into an older partially delivered flow.
92
+ - **Stable-id gateway events do not re-execute coding or stateful commands on delivery retry.** A private
93
+ started marker is durable before coding, tmux injection, `/new`, `/voice`, `/say`, or `/send`; completed
94
+ reply/file/default-voice bytes are durable before transport. Default voice retries therefore reuse the same
95
+ synthesized bytes instead of rerunning coding or TTS. Speech now has a 60-second default/120-second hard
96
+ deadline, propagates shutdown cancellation to remote requests, and terminates the full local
97
+ `say`/custom-command process tree.
98
+ - **Interrupted gateway outcome markers have an explicit fail-closed recovery path.** A credential-scoped,
99
+ single-instance `hara gateway --recover-outcome ... --confirm-recovery terminalize:<id>` command releases one
100
+ abandoned active slot without rerunning its side effect; a separate exact confirmation can later delete only
101
+ an already-terminal tombstone. Running or unacknowledged completed records cannot be silently erased.
102
+ - Feishu outbound REST now uses native abortable requests with an in-memory, early-refresh tenant-token cache;
103
+ the official SDK remains responsible for the inbound WebSocket/resource surface. Hara resolves mentions
104
+ from event metadata and drains tracked callbacks before releasing the single-instance lease on shutdown.
105
+
106
+ ## 0.122.3 — 2026-07-14 — standalone runtime recovery
107
+
108
+ - **Bun-compiled standalone binaries no longer assume `SharedArrayBuffer` exists at module startup.**
109
+ The short synchronous waits used by cross-process stores now prefer `Bun.sleepSync`, lazily use
110
+ `Atomics.wait` only on Node when its primitives exist, and retain a bounded last-resort delay. This
111
+ fixes the macOS arm64 `ReferenceError` that could make the 0.122.2 standalone binary exit before
112
+ even printing `--version`; npm/Node and GHCR users were unaffected. CI now compiles and executes a
113
+ native standalone binary on Linux and macOS, and the release job executes its native Linux asset
114
+ before publishing, so a merely successful cross-compile is no longer accepted as runtime proof.
115
+
8
116
  ## 0.122.2 — 2026-07-14 — reproducible container release
9
117
 
10
118
  - **The Docker build now includes the guarded runtime entry before normalizing package modes.** This fixes the
package/README.md CHANGED
@@ -175,6 +175,11 @@ hara --profile work # use a named profile from ~/.hara/config.json
175
175
  hara -m glm-5 # pick a model
176
176
  ```
177
177
 
178
+ Run Hara from a project directory. When the current directory resolves to your Home root, Hara does not treat
179
+ the whole Home tree as a repository: project init/index and default recursive grep/glob/codebase inventory are
180
+ disabled with a `cd /path/to/project` hint. Non-recursive `ls`, explicitly named files, and explicitly selected
181
+ child directories still work.
182
+
178
183
  For automation, `--schema` accepts inline JSON Schema or a schema file. The model must return through the
179
184
  validated `structured_output` tool; on success stdout contains only the JSON value, while diagnostics go to
180
185
  stderr and missing/invalid output exits non-zero. `--role reviewer` resolves locally, `--role global:reviewer`
@@ -234,16 +239,24 @@ only changed files re-embed (a full repo rebuild that takes ~a minute re-runs in
234
239
  `hara config set computerUse read|click|full` and allowlist apps with `hara config set computerApps "App, …"`. Guarded
235
240
  by the tier, the frontmost-app allowlist, a dangerous-key blocklist, and a once-per-session grant. Screenshots are read via your
236
241
  vision model into **actionable** output — interactive elements + positions (pass `focus` to target what you're after) — so even a text-only main model can click.
237
- **Sessions**: conversations are saved automatically — `-c` / `--resume <id>` to continue, `hara sessions` to list, `hara export [id] [--out file]` to render one as a Markdown transcript.
242
+ **Sessions**: conversations are saved automatically — `-c` / `--resume <id>` or `hara resume <id>` to continue, `hara sessions` to list, `hara export [id] [--out file]` to render one as a Markdown transcript. The `hara resume` launcher preserves terminal input in both npm/Node and standalone-binary installs.
238
243
  **MCP**: add an `mcpServers` map to global config (a reviewed project config additionally needs `HARA_TRUST_PROJECT_CONFIG=1` at launch); their tools appear to the agent as `mcp__<server>__<tool>`. Configured MCP servers, like `external_agent`, are trusted host extensions outside Hara's protected-file boundary. Every interactive tool call requires confirmation (even in `full-auto`), and non-interactive runs disable them by default; reviewed automation can explicitly opt in before launch with `HARA_ALLOW_TRUSTED_EXTENSIONS=1`. hara can also **be** an MCP server — `hara mcp` exposes its read/search tools (esp. **`codebase_search`**) over stdio so other clients (Claude Desktop, Cursor, another hara) can use them; read-only by default (`HARA_MCP_TOOLS` to override).
239
244
  **Vim mode**: `hara config set vimMode true` makes the prompt modal — Esc → normal, `i/a/A/I` insert, `h l 0 $ w b e` motions, `x D C dd cw p` edits. Off by default.
240
- **Scheduled tasks**: `hara cron add "0 9 * * 1-5" "<task>"` (or `"every 30m"`, `"in 2h"`) runs a task on a schedule — each run is a fresh hara session. `hara cron install` wires a per-minute tick into launchd/crontab (no daemon); `--org` routes through the role org. Manage with `hara cron list/run/enable/disable/remove/logs`.
245
+ **Scheduled tasks**: `hara cron add "0 9 * * 1-5" "<task>"` (or `"every 30m"`, `"in 2h"`) runs a task on a schedule — each run is a fresh hara session. `hara cron install` wires a per-minute tick into launchd/crontab (no daemon); `--org` routes through the role org. Manage with `hara cron list/run/enable/disable/remove/logs`. Every job has a 30-minute deadline and the whole sequential tick has a non-renewable 60-minute watchdog: a job timeout kills its process tree, records `timed out` + duration/error, then continues with the next due job; a tick timeout stops the remainder and releases the global lock. Add `--deliver feishu:<chatId>` (or Telegram/webhook) for outcomes and `--alert-after N` for the consecutive-failure 🚨 threshold (default 3). Delivery intent is durable before transport, uses a stable idempotency key, and retries with bounded backoff on later ticks until confirmed. A failed channel cannot grow `jobs.json` forever: each job keeps at most 64 pending effects, reserves outcome/alert room before launch, and disables itself with a visible backlog error when full; restore delivery, let the queue drain, then re-enable it. Tune milliseconds with `HARA_CRON_JOB_TIMEOUT_MS` (hard max 24h) and `HARA_CRON_TICK_TIMEOUT_MS` (hard max 5h); scheduled jobs are also capped by the tick. After upgrading from a version whose tick is already stuck, terminate that specific legacy `hara cron tick` process tree once (or reboot); the next scheduler minute marks over-age state interrupted/disabled and recovers the lock without replaying a possibly orphaned task.
241
246
  **Work coordination**: `todo_write` is the agent's short, session-scoped checklist; it persists with that
242
247
  session and is isolated between simultaneous sub-agents and serve sessions. `task` is the durable project pool
243
248
  for work that outlives a conversation: add/update/list/remove items with `pending|in_progress|done`, an optional
244
249
  owner, and `blockedBy` dependencies. The private, atomic store is shared by concurrent hara processes for the
245
250
  same project and rejects missing/self/cyclic dependencies.
246
251
  **Notifications**: `hara config set notify bell` (terminal bell) or `notify system` (OS notification) pings you when a turn finishes — handy for long runs you've stepped away from. Gated on elapsed time so quick turns stay quiet; off by default.
252
+ **Run limits and loop alarms**: every agent turn has a non-renewable 30-minute total deadline and a 64-round
253
+ model/tool cap. Hara warns after five minutes or at 75% of the round budget, stops the third identical failing
254
+ tool call, and surfaces the final reason in CLI, Desktop, or gateway output. Tune intentional long work with
255
+ `hara config set runTimeoutMs 45m` (1s..2h) and `hara config set maxAgentRounds 96` (1..256), or
256
+ `HARA_RUN_TIMEOUT_MS` / `HARA_MAX_AGENT_ROUNDS`; neither boundary can be disabled. Sub-agents are capped at
257
+ 8 minutes/24 rounds and inherit the parent's cancellation. Auxiliary model work (planning, verification,
258
+ compaction, naming, commit messages, the guardian, and vision) has its own short hard deadline as well, so a
259
+ provider that ignores cancellation cannot strand the CLI outside the main loop.
247
260
  **Hooks**: run your own shell commands around tool calls via a `"hooks"` map in global config; hooks from a reviewed project config require the launch-time `HARA_TRUST_PROJECT_CONFIG=1` opt-in. A **`PreToolUse`** hook can **veto** a call (non-zero exit blocks it; its output becomes the reason the model sees) — gate `bash`, forbid edits outside a path, require a clean tree. A **`PostToolUse`** hook observes (format/lint a file the agent just wrote, log, notify). Each has a `matcher` (regex/literal on the tool name, `*` = all) and gets `{tool, payload}` on stdin + `HARA_TOOL_NAME` in env. Plugins can contribute hooks too.
248
261
  Reviewer/read-only/plan runs and parallel read-only sub-agents suppress both hook phases: PreToolUse and
249
262
  PostToolUse commands are arbitrary shell, so either could otherwise bypass their read-only contract indirectly.
@@ -0,0 +1,51 @@
1
+ /** Hard lifecycle bounds for one agent run. These are deliberately independent of the provider's
2
+ * stream-idle watchdog and each tool's own timeout: activity must not be able to renew a run forever. */
3
+ export const DEFAULT_AGENT_RUN_TIMEOUT_MS = 30 * 60_000;
4
+ export const MAX_AGENT_RUN_TIMEOUT_MS = 2 * 60 * 60_000;
5
+ export const MIN_AGENT_RUN_TIMEOUT_MS = 1_000;
6
+ export const DEFAULT_AGENT_MAX_ROUNDS = 64;
7
+ export const MAX_AGENT_MAX_ROUNDS = 256;
8
+ export function parseAgentRunTimeoutMs(value) {
9
+ if (typeof value === "number")
10
+ return Number.isFinite(value) ? value : undefined;
11
+ if (typeof value !== "string")
12
+ return undefined;
13
+ const match = /^\s*(\d+(?:\.\d+)?)\s*(ms|s|m|h)?\s*$/i.exec(value);
14
+ if (!match)
15
+ return undefined;
16
+ const amount = Number(match[1]);
17
+ const unit = (match[2] ?? "ms").toLowerCase();
18
+ const multiplier = unit === "h" ? 3_600_000 : unit === "m" ? 60_000 : unit === "s" ? 1_000 : 1;
19
+ const parsed = amount * multiplier;
20
+ return Number.isFinite(parsed) ? parsed : undefined;
21
+ }
22
+ /** Parse a total run deadline. Invalid/zero values cannot disable the safety boundary. Human values such
23
+ * as `30m`, `90s`, and `1h` are accepted in config; HARA_RUN_TIMEOUT_MS also accepts plain milliseconds. */
24
+ export function agentRunTimeoutMs(value = process.env.HARA_RUN_TIMEOUT_MS) {
25
+ const parsed = parseAgentRunTimeoutMs(value);
26
+ if (parsed === undefined || parsed <= 0)
27
+ return DEFAULT_AGENT_RUN_TIMEOUT_MS;
28
+ return Math.max(MIN_AGENT_RUN_TIMEOUT_MS, Math.min(MAX_AGENT_RUN_TIMEOUT_MS, Math.trunc(parsed)));
29
+ }
30
+ /** Maximum provider/tool rounds in one run. This catches active loops that never go silent. */
31
+ export function agentMaxRounds(value = process.env.HARA_MAX_AGENT_ROUNDS) {
32
+ const parsed = typeof value === "number" ? value : Number(value);
33
+ if (!Number.isFinite(parsed) || parsed <= 0)
34
+ return DEFAULT_AGENT_MAX_ROUNDS;
35
+ return Math.max(1, Math.min(MAX_AGENT_MAX_ROUNDS, Math.trunc(parsed)));
36
+ }
37
+ export function formatAgentDuration(ms) {
38
+ if (ms >= 3_600_000 && ms % 3_600_000 === 0)
39
+ return `${ms / 3_600_000}h`;
40
+ if (ms >= 3_600_000)
41
+ return `${Math.round(ms / 360_000) / 10}h`;
42
+ if (ms >= 60_000 && ms % 60_000 === 0)
43
+ return `${ms / 60_000}m`;
44
+ if (ms >= 60_000)
45
+ return `${Math.round(ms / 6_000) / 10}m`;
46
+ if (ms >= 1_000 && ms % 1_000 === 0)
47
+ return `${ms / 1_000}s`;
48
+ if (ms >= 1_000)
49
+ return `${Math.round(ms / 1_000)}s`;
50
+ return `${ms}ms`;
51
+ }