@aragon-agent/core 0.1.3 → 0.2.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 CHANGED
@@ -5,6 +5,42 @@ All notable changes to `@aragon-agent/core` are documented here. Entries at
5
5
  — those releases really were published under that name, so they are left as they
6
6
  were written.
7
7
 
8
+ ## 0.3.0
9
+
10
+ ### Added
11
+
12
+ - **`Agent.pauseIdleWatchdog()` / `Agent.resumeIdleWatchdog()`** — suspend and
13
+ restart the idle watchdog around a tool that blocks on a human. Both are
14
+ idempotent, and a paused watchdog also swallows `kick()`, so an event arriving
15
+ mid-wait cannot silently re-arm it. Always pair them in a `try/finally`: a
16
+ throw inside the wait must not leave the watchdog disarmed for the rest of the
17
+ run. The host is the only party that can know a person is being waited on —
18
+ from inside the loop that call is indistinguishable from a wedged network
19
+ request.
20
+
21
+ - **`AgentConfig.timeouts.toolTimeoutOverrides?: Record<string, number>`** —
22
+ per-tool ceilings keyed by tool name, forwarded to `ToolExecutor`'s existing
23
+ `timeoutOverrides`. This is pure wiring of a capability the executor already
24
+ had and that was unreachable through `Agent`.
25
+
26
+ Read this before relying on it: `ToolExecutor` expresses a timeout by aborting
27
+ `context.signal` and then *continuing to await* the tool's promise — there is
28
+ no `Promise.race`. A tool that never observes `context.signal` is therefore
29
+ never timed out, and the number set here is inert for it, with no error
30
+ anywhere. See *Blocking on a human* in `API.md`.
31
+
32
+ ### Fixed
33
+
34
+ - `IdleWatchdog.stop()` now clears the paused flag. Without it a run aborted
35
+ while paused would leave the watchdog deaf for every subsequent run, because
36
+ `start()` goes through `kick()` and `kick()` returns early while paused.
37
+
38
+ ### Notes
39
+
40
+ - **No change to the export surface.** The two new members are instance methods
41
+ and the third is a config field, so `src/index.ts` and the `EXPECTED` list in
42
+ `public-api.test.ts` are untouched.
43
+
8
44
  ## 0.2.0
9
45
 
10
46
  ### Breaking
@@ -37,6 +37,18 @@ export interface AgentConfig {
37
37
  toolTimeout?: number;
38
38
  /** Code execution timeout (ms). Default: 30_000 (30 s). */
39
39
  codeTimeout?: number;
40
+ /**
41
+ * Per-tool ceilings keyed by tool name, overriding `toolTimeout`.
42
+ *
43
+ * READ THIS BEFORE RELYING ON IT: `ToolExecutor` expresses a timeout by
44
+ * calling `controller.abort(...)` on the context signal and then continuing
45
+ * to `await` the tool's promise — there is no `Promise.race`. A tool that
46
+ * never observes `context.signal` is therefore never timed out, and any
47
+ * number set here is inert for it. Tools that must honour a ceiling have to
48
+ * subscribe to `context.signal` explicitly (see `bash-tool.ts` in the CLI
49
+ * package for the house pattern).
50
+ */
51
+ toolTimeoutOverrides?: Record<string, number>;
40
52
  };
41
53
  }
42
54
  export interface AgentState {
@@ -110,6 +122,10 @@ export declare class Agent {
110
122
  replaceMessages(messages: Message[]): void;
111
123
  /** Clear both steering and follow-up message queues. */
112
124
  clearAllQueues(): void;
125
+ /** Suspend the idle watchdog (e.g. while a tool waits on a human). Idempotent. */
126
+ pauseIdleWatchdog(): void;
127
+ /** Resume the idle watchdog and restart its window from now. Idempotent. */
128
+ resumeIdleWatchdog(): void;
113
129
  /**
114
130
  * Emit an event to all listeners and kick the watchdog.
115
131
  * Listener errors are caught and logged to prevent crashing the loop.
@@ -67,6 +67,9 @@ export class Agent {
67
67
  // Tool executor
68
68
  this.toolExecutor = new ToolExecutor(this.toolRegistry, {
69
69
  defaultTimeout: this.toolTimeout,
70
+ ...(config.timeouts?.toolTimeoutOverrides
71
+ ? { timeoutOverrides: config.timeouts.toolTimeoutOverrides }
72
+ : {}),
70
73
  });
71
74
  // Watchdog — on timeout, abort the agent
72
75
  this.watchdog = new IdleWatchdog(this.idleTimeout, () => {
@@ -192,6 +195,22 @@ export class Agent {
192
195
  this.messageQueueManager.clearAll();
193
196
  }
194
197
  // =========================================================================
198
+ // Idle watchdog control
199
+ //
200
+ // Public because only the HOST knows that a tool is blocked on a human. The
201
+ // engine sees an ordinary long-running tool call and would abort the run
202
+ // after `idleTimeout` — which is right for a wedged network call and wrong
203
+ // for a person reading a plan. Always pair these in a `try/finally`.
204
+ // =========================================================================
205
+ /** Suspend the idle watchdog (e.g. while a tool waits on a human). Idempotent. */
206
+ pauseIdleWatchdog() {
207
+ this.watchdog.pause();
208
+ }
209
+ /** Resume the idle watchdog and restart its window from now. Idempotent. */
210
+ resumeIdleWatchdog() {
211
+ this.watchdog.resume();
212
+ }
213
+ // =========================================================================
195
214
  // Internal
196
215
  // =========================================================================
197
216
  /**
@@ -11,12 +11,28 @@ export declare class IdleWatchdog {
11
11
  private readonly onTimeout;
12
12
  private timerId;
13
13
  private running;
14
+ private paused;
14
15
  constructor(timeoutMs: number, onTimeout: () => void);
15
16
  /** Reset the idle timer. Must be called on every Agent event. */
16
17
  kick(): void;
17
18
  /** Start the watchdog. Begins (or restarts) the idle timer. */
18
19
  start(): void;
19
- /** Stop the watchdog and cancel any pending timer. */
20
+ /**
21
+ * Suspend the idle timer — e.g. while a tool blocks on a human answer.
22
+ * Idempotent. A paused watchdog swallows `kick()`, so an event arriving
23
+ * mid-wait cannot silently re-arm it.
24
+ */
25
+ pause(): void;
26
+ /** Resume and restart the idle window from now. Idempotent. */
27
+ resume(): void;
28
+ /**
29
+ * Stop the watchdog and cancel any pending timer.
30
+ *
31
+ * Clearing `paused` here is load-bearing: a run aborted while paused would
32
+ * otherwise leave the watchdog deaf for the NEXT run — `start()` calls
33
+ * `kick()`, and `kick()` returns early while paused — and nothing anywhere
34
+ * would report it.
35
+ */
20
36
  stop(): void;
21
37
  /** Update the timeout duration. Takes effect on the next `kick()`. */
22
38
  setTimeout(ms: number): void;
@@ -11,13 +11,14 @@ export class IdleWatchdog {
11
11
  onTimeout;
12
12
  timerId = null;
13
13
  running = false;
14
+ paused = false;
14
15
  constructor(timeoutMs, onTimeout) {
15
16
  this.timeoutMs = timeoutMs;
16
17
  this.onTimeout = onTimeout;
17
18
  }
18
19
  /** Reset the idle timer. Must be called on every Agent event. */
19
20
  kick() {
20
- if (!this.running)
21
+ if (!this.running || this.paused)
21
22
  return;
22
23
  this.clearTimer();
23
24
  this.timerId = setTimeout(() => {
@@ -29,9 +30,31 @@ export class IdleWatchdog {
29
30
  this.running = true;
30
31
  this.kick();
31
32
  }
32
- /** Stop the watchdog and cancel any pending timer. */
33
+ /**
34
+ * Suspend the idle timer — e.g. while a tool blocks on a human answer.
35
+ * Idempotent. A paused watchdog swallows `kick()`, so an event arriving
36
+ * mid-wait cannot silently re-arm it.
37
+ */
38
+ pause() {
39
+ this.paused = true;
40
+ this.clearTimer();
41
+ }
42
+ /** Resume and restart the idle window from now. Idempotent. */
43
+ resume() {
44
+ this.paused = false;
45
+ this.kick();
46
+ }
47
+ /**
48
+ * Stop the watchdog and cancel any pending timer.
49
+ *
50
+ * Clearing `paused` here is load-bearing: a run aborted while paused would
51
+ * otherwise leave the watchdog deaf for the NEXT run — `start()` calls
52
+ * `kick()`, and `kick()` returns early while paused — and nothing anywhere
53
+ * would report it.
54
+ */
33
55
  stop() {
34
56
  this.running = false;
57
+ this.paused = false;
35
58
  this.clearTimer();
36
59
  }
37
60
  /** Update the timeout duration. Takes effect on the next `kick()`. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aragon-agent/core",
3
- "version": "0.1.3",
3
+ "version": "0.2.1",
4
4
  "description": "AragonAgent core engine — LLM provider adapters, streaming, tool registry/execution, steering, idle watchdog, and an optional isolated-vm sandbox.",
5
5
  "license": "MIT",
6
6
  "author": "AragonAgent contributors",