@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.
- package/CHANGELOG.md +108 -0
- package/README.md +15 -2
- package/dist/agent/limits.js +51 -0
- package/dist/agent/loop.js +367 -112
- package/dist/agent/repeat-guard.js +49 -12
- package/dist/checkpoints.js +9 -0
- package/dist/cli.js +2 -1
- package/dist/config.js +10 -2
- package/dist/context/agents-md.js +21 -2
- package/dist/context/mentions.js +84 -1
- package/dist/context/workspace-scope.js +49 -0
- package/dist/cron/deliver.js +61 -38
- package/dist/cron/runner.js +423 -65
- package/dist/cron/schedule.js +23 -7
- package/dist/cron/store.js +709 -43
- package/dist/fs-walk.js +279 -7
- package/dist/fs-write.js +9 -0
- package/dist/gateway/feishu.js +351 -64
- package/dist/gateway/flows-pending.js +31 -17
- package/dist/gateway/flows.js +306 -31
- package/dist/gateway/outbound-files.js +20 -6
- package/dist/gateway/runtime-state.js +1485 -0
- package/dist/gateway/serve.js +550 -242
- package/dist/gateway/sessions.js +3 -3
- package/dist/gateway/telegram.js +279 -29
- package/dist/gateway/tts.js +182 -38
- package/dist/hooks.js +22 -22
- package/dist/index.js +466 -158
- package/dist/memory/store.js +8 -5
- package/dist/org/planner.js +11 -6
- package/dist/org/projects.js +3 -3
- package/dist/process-identity.js +52 -0
- package/dist/providers/bounded-turn.js +42 -0
- package/dist/recall.js +69 -1
- package/dist/runtime.js +24 -0
- package/dist/sandbox.js +54 -4
- package/dist/search/embed.js +13 -2
- package/dist/search/hybrid.js +6 -3
- package/dist/search/semindex.js +87 -5
- package/dist/security/guardian.js +3 -15
- package/dist/security/permissions.js +11 -0
- package/dist/serve/server.js +70 -42
- package/dist/serve/sessions.js +4 -3
- package/dist/sync-sleep.js +46 -0
- package/dist/tools/agent.js +1 -1
- package/dist/tools/ask_user.js +5 -1
- package/dist/tools/builtin.js +5 -2
- package/dist/tools/codebase.js +67 -18
- package/dist/tools/computer.js +149 -68
- package/dist/tools/cron.js +72 -18
- package/dist/tools/edit.js +3 -2
- package/dist/tools/external_agent.js +66 -12
- package/dist/tools/memory.js +25 -7
- package/dist/tools/patch.js +11 -2
- package/dist/tools/registry.js +16 -1
- package/dist/tools/search.js +68 -9
- package/dist/tools/send.js +1 -1
- package/dist/tools/skill.js +1 -1
- package/dist/tools/task.js +3 -3
- package/dist/tools/web.js +43 -13
- package/dist/tui/App.js +93 -25
- package/dist/vision.js +5 -6
- package/package.json +2 -2
- 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
|
+
}
|