agent-accelerator 0.5.7 → 0.5.8

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.
@@ -4,7 +4,7 @@ import { telemetryBus } from "../telemetry.ts";
4
4
  /** Listener invoked for a matching stream event type or `*`. */
5
5
  export type StreamEventListener = (event: StreamEvent) => void;
6
6
 
7
- /** Options for AssistantMessageEventStream (Q-29). */
7
+ /** Options for AssistantMessageEventStream. */
8
8
  export interface EventStreamOptions {
9
9
  /**
10
10
  * When false, `partialText`/`partialThinking` cumulative fields are stripped
@@ -25,9 +25,9 @@ type Resolver = {
25
25
  reject: (err: Error) => void;
26
26
  };
27
27
 
28
- /** Default max buffered events (Q-29). */
28
+ /** Default max buffered events. */
29
29
  export const MAX_BUFFERED_EVENTS = 1000;
30
- /** Default max approx bytes per delta event (Q-29). */
30
+ /** Default max approx bytes per delta event. */
31
31
  export const MAX_EVENT_BYTES = 1_048_576;
32
32
 
33
33
  // Approximate size of an event's text fields; in: push's oversize guard, out: a character count.
@@ -43,7 +43,7 @@ function approxEventBytes(event: StreamEvent): number {
43
43
  // Reports a dropped or overflowing event on the bus and the legacy hook, never throwing; in: push/enqueueBounded, out: telemetryBus.
44
44
  function reportStreamAnomaly(detail: { type: string; [k: string]: unknown }): void {
45
45
  // why: bus + hook only, never console — streams are hot paths and logging
46
- // per-drop would spam and break no-console policy (Q-29/Q-41).
46
+ // per-drop would spam and break no-console policy.
47
47
  telemetryBus.emitWarning(detail.type, `[Agent Accelerator] stream anomaly: ${detail.type}`, detail);
48
48
  try {
49
49
  (globalThis as unknown as { __agentAccelAnomaly?: (d: unknown) => void }).__agentAccelAnomaly?.(detail);
@@ -68,7 +68,7 @@ export class AssistantMessageEventStream implements AsyncIterable<StreamEvent> {
68
68
  private readonly buffered: boolean;
69
69
  private readonly maxBufferedEvents: number;
70
70
  private readonly maxEventBytes: number;
71
- /** First listener exception captured for inspection (Q-29/Q-41). */
71
+ /** First listener exception captured for inspection. */
72
72
  private listenerError: Error | null = null;
73
73
  /** Count of events dropped by bounds (buffer cap / oversize / unbuffered). */
74
74
  private droppedEvents = 0;
@@ -107,7 +107,7 @@ export class AssistantMessageEventStream implements AsyncIterable<StreamEvent> {
107
107
  return this.listenerError;
108
108
  }
109
109
 
110
- /** Number of events dropped by Q-29 bounds. */
110
+ /** Number of events dropped by bounds. */
111
111
  getDroppedCount(): number {
112
112
  return this.droppedEvents;
113
113
  }
@@ -116,7 +116,7 @@ export class AssistantMessageEventStream implements AsyncIterable<StreamEvent> {
116
116
  push(event: StreamEvent): void {
117
117
  if (this.finished) return;
118
118
 
119
- // Q-29: 1MB oversize guard — drop oversized deltas (no console).
119
+ // 1MB oversize guard — drop oversized deltas (no console).
120
120
  if (approxEventBytes(event) > this.maxEventBytes) {
121
121
  this.droppedEvents++;
122
122
  reportStreamAnomaly({ type: "stream_event_oversize", eventType: event.type });
@@ -132,7 +132,7 @@ export class AssistantMessageEventStream implements AsyncIterable<StreamEvent> {
132
132
 
133
133
  if (out.type === "error") {
134
134
  this.error = (out.error instanceof Error ? out.error : new Error(String(out.error))) ?? new Error("Unknown error in stream");
135
- // Single error semantic (Q-29): deliver the error event to listeners,
135
+ // Single error semantic: deliver the error event to listeners,
136
136
  // reject result(), mark finished, reject pending iterators, and queue
137
137
  // the error event so a late iterator observes it before failing.
138
138
  this.deliverToListeners(out);
@@ -156,7 +156,7 @@ export class AssistantMessageEventStream implements AsyncIterable<StreamEvent> {
156
156
  resolver.resolve({ value: out, done: false });
157
157
  } else {
158
158
  // No waiter right now (typical between `next()` polls of a burst):
159
- // buffer for the iterator with a hard cap (Q-29). Overflow drops the
159
+ // buffer for the iterator with a hard cap. Overflow drops the
160
160
  // oldest with an anomaly report — see `enqueueBounded`. A stream built
161
161
  // with `buffer: false` has no iterator to feed and keeps nothing.
162
162
  if (this.buffered) this.enqueueBounded(out);
@@ -194,7 +194,7 @@ export class AssistantMessageEventStream implements AsyncIterable<StreamEvent> {
194
194
  private enqueueBounded(event: StreamEvent): void {
195
195
  this.queue.push(event);
196
196
  while (this.queue.length > this.maxBufferedEvents) {
197
- // Drop oldest buffered event to bound memory (Q-29). Error events are
197
+ // Drop oldest buffered event to bound memory. Error events are
198
198
  // never dropped: if the oldest is an error, drop the next-oldest
199
199
  // non-error instead; if the queue is all errors, keep them.
200
200
  if (this.queue[0]?.type === "error") {
@@ -213,7 +213,7 @@ export class AssistantMessageEventStream implements AsyncIterable<StreamEvent> {
213
213
  end(finalResponse?: AgentResponse): void {
214
214
  if (this.finished) return;
215
215
  if (!finalResponse) {
216
- // Q-29: never hang result() — ending without a response is a
216
+ // Never hang result() — ending without a response is a
217
217
  // programming error, so fail loudly instead of leaving consumers
218
218
  // waiting forever.
219
219
  this.fail(new Error("end() requires response"));
@@ -1,34 +1,84 @@
1
1
  /**
2
- * Opt-in disk sink for the telemetry bus (`AgentConfig.telemetry.persist`).
3
- * Appends one JSON line per event to `<home>/telemetry/<YYYY-MM-DD>.ndjson`.
4
- * Each event is a single synchronous append, so a process crash keeps every
5
- * event already emitted: a `tool.call_started` with no ending event was in
6
- * flight, and the last `tool.progress` shows how far it got. Power loss is not
7
- * covered (no fsync, by design: this is observability, not state). `turn.thinking`
8
- * (full reasoning text) is never written. Takes the home's one-process lock.
2
+ * Opt-in disk sink for the telemetry bus (`AgentConfig.telemetry.persist`). Appends one JSON line per event.
3
+ * An event that carries the id of a persisted session (`data.sessionId`) goes to that session's own file,
4
+ * `<session folder>/telemetry.ndjson`, so a session's trace sits next to its journal and tool logs; every other event
5
+ * goes to `<home>/telemetry/<YYYY-MM-DD>.ndjson`. Nothing is ever written outside a home folder: a session folder
6
+ * that is not inside a home that enabled telemetry (a `persist.dir` pointing elsewhere) falls back to the daily file.
7
+ * Each event is a single synchronous append, so a process crash keeps every event already emitted: a
8
+ * `tool.call_started` with no ending event was in flight, and the last `tool.progress` shows how far it got. Power loss
9
+ * is not covered (no fsync, by design: this is observability, not state). `turn.thinking` (full reasoning text) is never
10
+ * written. Several processes may enable the same home: each event is one append, so lines never mix.
9
11
  */
10
12
  import * as fs from "node:fs";
11
13
  import * as path from "node:path";
12
14
  import { telemetryBus } from "./telemetry.ts";
15
+ import type { TelemetryEvent } from "./telemetry.ts";
13
16
  import { safeStringify } from "./utils/serialization.ts";
14
- import { lockFolder } from "./utils/durable.ts";
15
17
 
16
- const sinks = new Set<string>();
18
+ /** Homes (resolved paths) that asked for telemetry. */
19
+ const homes = new Set<string>();
20
+ /** Where each persisted session lives, by session id. */
21
+ const sessionFolders = new Map<string, string>();
22
+ let unsubscribe: (() => void) | undefined;
17
23
 
18
- /** Starts appending bus events under `home` (idempotent per folder). */
19
- export function persistTelemetry(home: string): void {
20
- const dir = path.join(home, "telemetry");
21
- if (sinks.has(dir)) return;
22
- lockFolder(home);
23
- sinks.add(dir);
24
- telemetryBus.onEvent((event) => {
25
- if (event.code === "turn.thinking") return;
26
- try {
27
- fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
28
- const file = path.join(dir, `${new Date(event.timestamp).toISOString().slice(0, 10)}.ndjson`);
29
- fs.appendFileSync(file, safeStringify(event) + "\n", { mode: 0o600 });
30
- } catch {
31
- // why: a full disk must never fail a run, and reporting it would re-enter this listener.
24
+ // True when `folder` is `home` itself or below it; in: the routing decision, out: a boolean.
25
+ function inside(home: string, folder: string): boolean {
26
+ const rel = path.relative(home, folder);
27
+ return rel === "" || (!rel.startsWith("..") && !path.isAbsolute(rel));
28
+ }
29
+
30
+ // Appends one line to a file, creating its folder; a full disk must never fail a run, and reporting it would re-enter this listener; in: the listener, out: the file.
31
+ function append(file: string, line: string): void {
32
+ try {
33
+ fs.mkdirSync(path.dirname(file), { recursive: true, mode: 0o700 });
34
+ fs.appendFileSync(file, line, { mode: 0o600 });
35
+ } catch {
36
+ // why: observability must never fail a run.
37
+ }
38
+ }
39
+
40
+ // The bus listener: skips reasoning text, writes a session's event to its own folder when that folder is inside an enabled home, else to the daily file of every enabled home; in: each bus event, out: NDJSON files.
41
+ function onEvent(event: TelemetryEvent): void {
42
+ if (event.code === "turn.thinking") return;
43
+ const line = safeStringify(event) + "\n";
44
+ const id = (event.data as { sessionId?: unknown } | undefined)?.sessionId;
45
+ const folder = typeof id === "string" ? sessionFolders.get(id) : undefined;
46
+ if (folder && [...homes].some((h) => inside(h, folder))) {
47
+ append(path.join(folder, "telemetry.ndjson"), line);
48
+ return;
49
+ }
50
+ const day = new Date(event.timestamp).toISOString().slice(0, 10);
51
+ for (const home of homes) {
52
+ if (!fs.existsSync(home)) {
53
+ homes.delete(home); // a deleted home is not recreated by a stray event
54
+ continue;
32
55
  }
33
- });
56
+ append(path.join(home, "telemetry", `${day}.ndjson`), line);
57
+ }
58
+ }
59
+
60
+ /** Starts appending bus events for `home` (idempotent per folder); one listener serves every enabled home. */
61
+ export function persistTelemetry(home: string): void {
62
+ const resolved = path.resolve(home);
63
+ unsubscribe = telemetryBus.onEvent(onEvent); // listeners are a Set, so this is idempotent, and it restores the sink after `telemetryBus.clear()`
64
+ if (homes.has(resolved)) return;
65
+ homes.add(resolved);
66
+ }
67
+
68
+ /** Remembers where a persisted session lives, so its events are written next to it; in: the Agent constructor and `importSession`, out: the routing map. */
69
+ export function registerSessionFolder(sessionId: string, folder: string): void {
70
+ sessionFolders.set(sessionId, path.resolve(folder));
71
+ }
72
+
73
+ /** Forgets a session's folder (its events fall back to the daily file); in: `disposeSession` and `importSession`, out: the routing map. */
74
+ export function unregisterSessionFolder(sessionId: string): void {
75
+ sessionFolders.delete(sessionId);
76
+ }
77
+
78
+ /** Forgets every enabled home and session and removes the listener. Test hook (not part of the public entry). */
79
+ export function resetTelemetryFile(): void {
80
+ homes.clear();
81
+ sessionFolders.clear();
82
+ unsubscribe?.();
83
+ unsubscribe = undefined;
34
84
  }
package/src/telemetry.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * In-process telemetry bus (Q-41). Library code routes warnings, anomalies and
2
+ * In-process telemetry bus. Library code routes warnings, anomalies and
3
3
  * per-turn traces here instead of `console.*`. The default has zero listeners,
4
4
  * so emission is a no-op; CLIs, tests and observability layers attach with
5
5
  * `telemetryBus.onEvent`. Intentionally dependency-free: every module in the
@@ -7,7 +7,7 @@
7
7
  * it without creating an import cycle.
8
8
  */
9
9
 
10
- /** Telemetry event emitted via the in-process bus (Q-41). */
10
+ /** Telemetry event emitted via the in-process bus. */
11
11
  export interface TelemetryEvent {
12
12
  code: string;
13
13
  message: string;
@@ -539,7 +539,7 @@ export async function executeToolCalls(
539
539
  let attempts = 0;
540
540
  let timedOut = false;
541
541
 
542
- // Truncated streamed arguments (Q-12): the `__truncated_raw` marker means the
542
+ // Truncated streamed arguments: the `__truncated_raw` marker means the
543
543
  // provider's partial JSON never parsed. Never execute or overwrite wire
544
544
  // finishReason here — return a synthetic error so the model retries with
545
545
  // complete JSON.
package/src/tools/tool.ts CHANGED
@@ -10,7 +10,7 @@ import { zodToJsonSchema } from "./schema.ts";
10
10
  import { ConfigError } from "../types/errors.ts";
11
11
 
12
12
  /**
13
- * Valid tool-name pattern (Q-55): 1–64 chars of letters, digits, `_`, `-`.
13
+ * Valid tool-name pattern: 1–64 chars of letters, digits, `_`, `-`.
14
14
  * Names are validated at registration in {@link tool} (record-key fallback
15
15
  * included); invalid names throw `ConfigError` with a fix.
16
16
  */
@@ -31,7 +31,7 @@ export function assertValidToolName(name: string): void {
31
31
  }
32
32
 
33
33
  /**
34
- * Normalizes the Q-52 canonical `thinking` option to a bare level.
34
+ * Normalizes the canonical `thinking` option to a bare level.
35
35
  * Accepts a level string or `{ level, budgetTokens }`; `budgetTokens` is
36
36
  * carried separately by providers, so only the level is returned here.
37
37
  * Resolution honors `thinking ?? thinkingLevel`.
@@ -50,7 +50,7 @@ export function normalizeThinkingV2(
50
50
  }
51
51
 
52
52
  /**
53
- * Resolves the Q-51 canonical busy policy. `whenBusy` wins over `mode`.
53
+ * Resolves the canonical busy policy. `whenBusy` wins over `mode`.
54
54
  *
55
55
  * @example `resolveWhenBusy({ whenBusy: "queue", mode: "steer" }); // "queue"`
56
56
  */
@@ -62,7 +62,7 @@ export function resolveWhenBusy(
62
62
  }
63
63
 
64
64
  /**
65
- * Normalizes the Q-52 `maxAttempts` alias to a `maxTries` value.
65
+ * Normalizes the `maxAttempts` alias to a `maxTries` value.
66
66
  * `maxTries` wins when both are set.
67
67
  *
68
68
  * @example `normalizeMaxAttempts({ maxAttempts: 3 }); // 3`
@@ -90,7 +90,7 @@ export interface CreateToolOptions<TInput = any, TOutput = any> {
90
90
  /** Maximum total attempts for transient failures. `0`/omitted means no configured limit. */
91
91
  maxTries?: number | string;
92
92
  /**
93
- * Canonical alias for `maxTries` (Q-52). When both are set, `maxTries` wins.
93
+ * Canonical alias for `maxTries`. When both are set, `maxTries` wins.
94
94
  * Normalized by `normalizeMaxAttempts`.
95
95
  */
96
96
  maxAttempts?: number | string;
@@ -116,12 +116,12 @@ export interface CreateToolOptions<TInput = any, TOutput = any> {
116
116
  /** Optional hook to redact/replace the error message sent back to the model. */
117
117
  redactError?: (err: unknown) => string;
118
118
  /**
119
- * Canonical alias for `redactError` (Q-55). When both are set,
119
+ * Canonical alias for `redactError`. When both are set,
120
120
  * `redactError` wins.
121
121
  */
122
122
  redact?: (err: unknown) => string;
123
123
  /**
124
- * Explicit retry policy (Q-55). Preferred over bare `maxTries` for new
124
+ * Explicit retry policy. Preferred over bare `maxTries` for new
125
125
  * code; the executor reads via `(toolDef as any).retry`.
126
126
  */
127
127
  retry?: { maxRetries?: number; baseDelayMs?: number; maxDelayMs?: number };
@@ -165,7 +165,7 @@ export interface CreateToolOptions<TInput = any, TOutput = any> {
165
165
  export function tool<TInput = any, TOutput = any>(
166
166
  options: CreateToolOptions<TInput, TOutput>
167
167
  ): ToolDefinition<TInput, TOutput> {
168
- // Q-55: names are validated at registration (record-key fallback included).
168
+ // Names are validated at registration (record-key fallback included).
169
169
  if (options.name !== undefined) assertValidToolName(options.name);
170
170
  // ToolDefinition (src/types/tool.ts) carries the v2 policies as typed
171
171
  // optionals; the executor additionally reads them via `(toolDef as any).*`.
@@ -10,7 +10,7 @@ import type { Agent } from "../agent/agent.ts";
10
10
  import type { z } from "zod";
11
11
 
12
12
  // ---------------------------------------------------------------------------
13
- // Q-52/Q-53/Q-54 canonical option types (all additive; nothing renamed).
13
+ // Canonical option types (all additive; nothing renamed).
14
14
  //
15
15
  // Canonical homes (one place per setting):
16
16
  // - Worker model: `dynamicSubagents.model` only. Top-level `subagentModel`
@@ -27,10 +27,10 @@ import type { z } from "zod";
27
27
  /** Canonical model reference: `provider/model` string, catalog spec, or provider helper result. */
28
28
  export type ModelRef = string | ModelSpec | ModelProviderInstance;
29
29
 
30
- /** Canonical reasoning option (Q-52): bare level, or level + explicit token budget. */
30
+ /** Canonical reasoning option: bare level, or level + explicit token budget. */
31
31
  export type ThinkingOption = ThinkingLevel | { level?: ThinkingLevel; budgetTokens?: number };
32
32
 
33
- /** Canonical retry policy (Q-54). Read by the loop via `(config as any).retry`. */
33
+ /** Canonical retry policy. Read by the loop via `(config as any).retry`. */
34
34
  export interface RetryPolicyConfig {
35
35
  maxRetries?: number;
36
36
  baseDelayMs?: number;
@@ -38,7 +38,7 @@ export interface RetryPolicyConfig {
38
38
  budgetMs?: number;
39
39
  }
40
40
 
41
- /** Minimal logger surface for Agent/loop telemetry (Q-54). */
41
+ /** Minimal logger surface for Agent/loop telemetry. */
42
42
  export interface AgentLogger {
43
43
  debug?: (...args: unknown[]) => void;
44
44
  info?: (...args: unknown[]) => void;
@@ -46,17 +46,17 @@ export interface AgentLogger {
46
46
  error?: (...args: unknown[]) => void;
47
47
  }
48
48
 
49
- /** Canonical tool-selection control (Q-54). String form is the portable subset. */
49
+ /** Canonical tool-selection control. String form is the portable subset. */
50
50
  export type ToolChoiceOption =
51
51
  | "auto"
52
52
  | "none"
53
53
  | "required"
54
54
  | { mode?: "auto" | "none" | "required"; tool?: string };
55
55
 
56
- /** Canonical stop control (Q-54): sequence(s) halting generation. */
56
+ /** Canonical stop control: sequence(s) halting generation. */
57
57
  export type StopOption = string | string[];
58
58
 
59
- /** Canonical structured-output input (Q-61). Zod, JSON Schema, or explicit form. */
59
+ /** Canonical structured-output input. Zod, JSON Schema, or explicit form. */
60
60
  export type StructuredOutputOption =
61
61
  | z.ZodType
62
62
  | Record<string, unknown>
@@ -90,7 +90,7 @@ export interface AgentConfig {
90
90
  /** Requested reasoning level, validated against the model catalog. */
91
91
  thinkingLevel?: ThinkingLevel;
92
92
  /**
93
- * Canonical reasoning option (Q-52, preferred over `thinkingLevel`).
93
+ * Canonical reasoning option (preferred over `thinkingLevel`).
94
94
  * Accepts a bare level or `{ level, budgetTokens }`. When both are set,
95
95
  * `thinking` wins; resolution honors `thinking ?? thinkingLevel`.
96
96
  * See `normalizeThinkingV2` (re-exported from the root entry).
@@ -151,11 +151,11 @@ export interface AgentConfig {
151
151
  resume?: "automatic" | "explicit";
152
152
  /**
153
153
  * Folder for everything Agent Accelerator writes to disk (`sessions/`,
154
- * `scheduled_actions/`, `telemetry/`). Defaults to `setHome()`, then the `AGENT_ACCEL_HOME` env var, then `.agent-accel/` in the working directory.
154
+ * `scheduled/`, `telemetry/`). Defaults to `setHome()`, then the `AGENT_ACCEL_HOME` env var, then `.agent-accel/` in the working directory.
155
155
  * An explicit `persist.dir` / `persist.file` still wins for sessions.
156
156
  */
157
157
  home?: string;
158
- // Q-54 missing knobs (all optional, additive; loop reads via `(config as any).*`).
158
+ // Additional options (all optional, additive; loop reads via `(config as any).*`).
159
159
  /**
160
160
  * Canonical tool-selection control (`"auto"` default). `"none"` applies to
161
161
  * every turn of a run; `"required"` and a pinned tool (`{ tool: "name" }`)
@@ -193,8 +193,10 @@ export interface AgentConfig {
193
193
  /** @deprecated Not implemented yet (accepted for forward compatibility; passing it emits a `config.unsupported_option` warning). */
194
194
  contextManagement?: unknown;
195
195
  /**
196
- * Telemetry options. `persist: true` appends every `telemetryBus` event (except full reasoning
197
- * text) to `<home>/telemetry/<date>.ndjson`. Off by default: the SDK writes nothing unless asked.
196
+ * Telemetry options. `persist: true` appends every `telemetryBus` event (except full reasoning text). An event
197
+ * that belongs to a persisted session (`data.sessionId`: tool events, resume and retry events) goes to
198
+ * `<session folder>/telemetry.ndjson`, next to the session's journal; every other event goes to
199
+ * `<home>/telemetry/<date>.ndjson`. Nothing is written outside the home. Off by default: the SDK writes nothing unless asked.
198
200
  */
199
201
  telemetry?: { persist?: boolean };
200
202
  /**
@@ -226,7 +228,7 @@ export interface AgentConfig {
226
228
  * models with unknown pricing contribute $0, never a guess.
227
229
  */
228
230
  budget?: Budget;
229
- /** Run + network limits (Q-22). Turn/budget caps unlimited by default; network timeouts finite. */
231
+ /** Run + network limits. Turn/budget caps unlimited by default; network timeouts finite. */
230
232
  limits?: AgentRunLimits;
231
233
  /**
232
234
  * The only models this agent may use, as `provider/model` strings compared as written. Unset (default) = any model.
@@ -260,7 +262,7 @@ export interface InterruptedRun {
260
262
  pendingToolCalls: string[];
261
263
  }
262
264
 
263
- /** Per-request + run limits (Q-22). */
265
+ /** Per-request + run limits. */
264
266
  export interface AgentRunLimits {
265
267
  maxTurns?: number;
266
268
  deadlineMs?: number;
@@ -275,7 +277,7 @@ export interface AgentRunLimits {
275
277
  /** Developer policy for mid-session new-query insertion while a run is active. */
276
278
  export type MidSessionMode = "steer" | "queue" | "auto";
277
279
 
278
- /** Q-51 canonical busy-policy alias: prefer `whenBusy`, `mode` kept for compat. */
280
+ /** Canonical busy-policy alias: prefer `whenBusy`, `mode` kept for compat. */
279
281
  export type WhenBusyMode = MidSessionMode;
280
282
 
281
283
  /** Developer-configured policy for LLM-spawned dynamic sub-agents. */
@@ -288,7 +290,7 @@ export interface MidSessionConfig {
288
290
  */
289
291
  mode?: MidSessionMode;
290
292
  /**
291
- * Canonical alias for `mode` (Q-51, preferred). When both are set,
293
+ * Canonical alias for `mode` (preferred). When both are set,
292
294
  * `whenBusy` wins. Accepted anywhere `mode` is read; resolution honors
293
295
  * `whenBusy ?? mode ?? "auto"`.
294
296
  */
@@ -341,7 +343,7 @@ export interface AgentRunOptions {
341
343
  /** Per-run reasoning-level override, validated against the selected model. */
342
344
  thinkingLevel?: ThinkingLevel;
343
345
  /**
344
- * Canonical per-run reasoning option (Q-52, preferred over `thinkingLevel`).
346
+ * Canonical per-run reasoning option (preferred over `thinkingLevel`).
345
347
  * Resolution honors `thinking ?? thinkingLevel`.
346
348
  */
347
349
  thinking?: ThinkingOption;
@@ -375,7 +377,7 @@ export interface AgentRunOptions {
375
377
  wrapThinking?: boolean;
376
378
  /** Called after every model/tool turn with a turn summary (also drives sub-agent step logs + durable persistence). */
377
379
  onTurn?: (turn: AgentTurnEvent) => void;
378
- // Q-54 per-run knobs (all optional, additive; `output` intentionally
380
+ // Per-run knobs (all optional, additive; `output` intentionally
379
381
  // omitted here — it is agent-level only, see AgentConfig.output).
380
382
  /** Per-run tool-selection override. */
381
383
  toolChoice?: ToolChoiceOption;
@@ -409,7 +411,7 @@ export interface AgentTurnEvent {
409
411
  toolResults?: Array<{ id: string; name: string; isError?: boolean }>;
410
412
  }
411
413
 
412
- /** Optional run budgets enforced by delegation helpers (Q-39). All fields default to unlimited. */
414
+ /** Optional run budgets enforced by delegation helpers. All fields default to unlimited. */
413
415
  export interface Budget {
414
416
  /** Maximum total cost in USD across this agent + spawned workers. */
415
417
  maxCostUsd?: number;
@@ -433,7 +435,7 @@ export interface BudgetState {
433
435
  startedAtMs: number;
434
436
  }
435
437
 
436
- /** Run/turn lineage attached to sub-agent metadata via `(metadata as any)` (Q-42). */
438
+ /** Run/turn lineage attached to sub-agent metadata via `(metadata as any)`. */
437
439
  export interface SubAgentRunIds {
438
440
  /** Stable id for this worker batch/run (`newRunId`). */
439
441
  runId?: string;
@@ -67,7 +67,7 @@ export type FinishReason =
67
67
  /** Maps a vendor finish reason to canonical FinishReason + raw passthrough.
68
68
  * Known aliases collapse to the canonical set; empty/undefined stays
69
69
  * undefined; any other non-empty string maps to `"error"` with the original
70
- * preserved in `rawFinishReason` (Q-04). */
70
+ * preserved in `rawFinishReason`. */
71
71
  export function normalizeFinishReason(raw?: string): {
72
72
  finishReason?: FinishReason;
73
73
  rawFinishReason?: string;
@@ -118,7 +118,7 @@ export interface AgentResponseJSON {
118
118
  model: string;
119
119
  provider: ProviderId | string;
120
120
  finishReason?: FinishReason;
121
- /** Original vendor finish string when it differs from canonical `finishReason` (Q-04). */
121
+ /** Original vendor finish string when it differs from canonical `finishReason`. */
122
122
  rawFinishReason?: string;
123
123
  durationMs: number;
124
124
  raw: ProviderRawData;
@@ -140,7 +140,7 @@ export class AgentResponse {
140
140
  readonly model: string;
141
141
  readonly provider: ProviderId | string;
142
142
  readonly finishReason?: FinishReason;
143
- /** Original vendor finish string when it differs from canonical `finishReason` (Q-04). */
143
+ /** Original vendor finish string when it differs from canonical `finishReason`. */
144
144
  readonly rawFinishReason?: string;
145
145
  readonly durationMs: number;
146
146
  readonly raw: ProviderRawData;
@@ -247,15 +247,15 @@ export type StreamEventType =
247
247
  /** Payload for one streaming text, thinking, tool, usage, or lifecycle event. */
248
248
  export interface StreamEvent {
249
249
  type: StreamEventType;
250
- /** Schema version for typed consumers (Q-40). Currently always 1 when present. */
250
+ /** Schema version for typed consumers. Currently always 1 when present. */
251
251
  schemaVersion?: 1;
252
252
  delta?: string;
253
253
  thinkingDelta?: string;
254
254
  toolCall?: ToolCallRecord;
255
255
  toolResult?: ToolResultRecord;
256
- /** Batch tool calls for `tool_start` events (Q-40). */
256
+ /** Batch tool calls for `tool_start` events. */
257
257
  toolCalls?: ToolCallRecord[];
258
- /** Batch tool results for `tool_end` events (Q-40). */
258
+ /** Batch tool results for `tool_end` events. */
259
259
  toolResults?: ToolResultRecord[];
260
260
  subagent?: SubAgentExecutionMetadata;
261
261
  /** Tracking id of the worker emitting a `subagent_delta` event. */
@@ -271,7 +271,7 @@ export interface StreamEvent {
271
271
  /** Canonical finish reason when known; vendor strings pass through as-is
272
272
  * for provider compat (normalize with `normalizeFinishReason`). */
273
273
  finishReason?: string;
274
- /** Original vendor finish string when it differs from canonical (Q-04). */
274
+ /** Original vendor finish string when it differs from canonical. */
275
275
  rawFinishReason?: string;
276
276
  error?: Error | unknown;
277
277
  partialText?: string;
@@ -279,7 +279,7 @@ export interface StreamEvent {
279
279
  raw?: ProviderRawData;
280
280
  }
281
281
 
282
- /** Typed (discriminated) stream event schema, version 1 (Q-40).
282
+ /** Typed (discriminated) stream event schema, version 1.
283
283
  * `StreamEvent` stays the permissive compat shape; use this union when you
284
284
  * want exhaustive switching (e.g. `tool_start`/`tool_end` lifecycle). */
285
285
  export type StreamEventV2 =
@@ -1,4 +1,4 @@
1
- /** Lifecycle of a scheduled action as stored in `tasks.json`. */
1
+ /** Lifecycle of a scheduled action as stored in `task.json`. */
2
2
  export type ScheduledState = "waiting" | "cooking" | "missed" | "done";
3
3
 
4
4
  /** Outcome of one run. */
@@ -45,7 +45,7 @@ export type ScheduledActionPatch = Partial<Omit<ScheduledActionInput, "scheduled
45
45
  scheduled_action?: Partial<Omit<ScheduledActionInput["scheduled_action"], "id">>;
46
46
  };
47
47
 
48
- /** One entry of `tasks.json`. All times are local clock strings `YYYY-MM-DDTHH:mm:ss` (no offset). */
48
+ /** The record inside an action's `task.json`. All times are local clock strings `YYYY-MM-DDTHH:mm:ss` (no offset). */
49
49
  export interface ScheduledActionRecord {
50
50
  id: string;
51
51
  name: string;
package/src/types/tool.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import type { z } from "zod";
2
2
 
3
- /** Minimal logger surface accepted by tool contexts (Q-54/Q-55, additive). */
3
+ /** Minimal logger surface accepted by tool contexts (additive). */
4
4
  export interface ToolLogger {
5
5
  debug?: (...args: unknown[]) => void;
6
6
  info?: (...args: unknown[]) => void;
@@ -8,7 +8,7 @@ export interface ToolLogger {
8
8
  error?: (...args: unknown[]) => void;
9
9
  }
10
10
 
11
- /** Live-event emitter handed to tools that stream progress (Q-55, additive). */
11
+ /** Live-event emitter handed to tools that stream progress (additive). */
12
12
  export type ToolEmitFn = (event: { type: string; [k: string]: unknown }) => void;
13
13
 
14
14
  export interface ToolExecutionContext {
@@ -22,7 +22,7 @@ export interface ToolExecutionContext {
22
22
  sessionId?: string;
23
23
  /** Forwards live sub-agent deltas (used by the `subagents` tool for realtime tracking). */
24
24
  onSubagentEvent?: (event: { trackingId: string; delta?: string; thinkingDelta?: string; partialText?: string; partialThinking?: string }) => void;
25
- // Q-55 Tool API v2 context additions (all optional, additive).
25
+ // Tool API v2 context additions (all optional, additive).
26
26
  /** Stable id for this run (`newRunId`). Mirrors SubAgentRunIds.runId. */
27
27
  runId?: string;
28
28
  /** Live-event emitter for tools that stream progress. */
@@ -69,7 +69,7 @@ export interface ToolDefinition<TInput = any, TOutput = any> {
69
69
  maxConcurrency?: number | string;
70
70
  /** Function executed after validation; its result is made JSON-safe for model context. */
71
71
  execute: ToolExecuteFn<TInput, TOutput>;
72
- // Q-55 Tool API v2 additions (all optional, additive; loop reads via `(toolDef as any).*`).
72
+ // Tool API v2 additions (all optional, additive; loop reads via `(toolDef as any).*`).
73
73
  /**
74
74
  * Canonical alias for `maxTries`. When both are set, `maxTries` wins.
75
75
  * Normalized by `normalizeMaxAttempts` (see tools/tool.ts).
@@ -101,7 +101,7 @@ export interface ToolDefinition<TInput = any, TOutput = any> {
101
101
  redactError?: (err: unknown) => string;
102
102
  /**
103
103
  * Canonical alias for `redactError`. When both are set, `redactError` wins.
104
- * Kept short for Q-55 Tool API v2 parity.
104
+ * Kept short for Tool API v2 parity.
105
105
  */
106
106
  redact?: (err: unknown) => string;
107
107
  /** Free-form tags for grouping/filtering tools (e.g. `["web", "readonly"]`). */
@@ -12,7 +12,7 @@ import { refreshModelCatalog } from "./index.ts";
12
12
  import { ConfigError } from "./types/errors.ts";
13
13
 
14
14
  const args = process.argv.slice(2);
15
- // Q-34: force is true only with explicit --force (previously also true when no --ttl).
15
+ // Force is true only with explicit --force (previously also true when no --ttl).
16
16
  const force = args.includes("--force");
17
17
 
18
18
  let customTtlMs: number | undefined;
@@ -26,7 +26,7 @@ for (const arg of args) {
26
26
  } else {
27
27
  customTtlMs = parseFloat(val);
28
28
  }
29
- // Q-34: reject non-numeric TTLs (e.g. --ttl=abc) instead of silently caching NaN.
29
+ // Reject non-numeric TTLs (e.g. --ttl=abc) instead of silently caching NaN.
30
30
  if (!Number.isFinite(customTtlMs)) {
31
31
  throw new ConfigError(`Invalid --ttl value "${val}": expected a number with optional h/m suffix (e.g. --ttl=24h).`);
32
32
  }
@@ -4,7 +4,7 @@ Small shared helpers. Each file has one job.
4
4
 
5
5
  | File | What it does |
6
6
  | :-- | :-- |
7
- | `durable.ts` | The ONE disk writer (`writeFileDurable`: temp, fsync, `.bak`, rename, folder fsync), `readWithBackup`, and `lockFolder` (one process per folder, stale-lock takeover, temp sweep). |
7
+ | `durable.ts` | The ONE disk writer (`writeFileDurable`: temp, fsync, `.bak`, rename, folder fsync), `readWithBackup`, `lockFolder` (one process per folder, used for session folders; stale-lock takeover, temp sweep) and `withFileMutex` (short per-file lock for state several processes change, e.g. one scheduled action). |
8
8
  | `home.ts` | `resolveHome` / `setHome`: the single folder everything is stored in. |
9
9
  | `env.ts` | Process-env lookups and API key resolution. No `globalThis` fallback. |
10
10
  | `headers.ts` | Session-affinity headers, opt-in attribution, browser stripping, package version fallback. |
@@ -11,7 +11,7 @@ export function clampCacheKey(key?: string): string | undefined {
11
11
  }
12
12
 
13
13
  // ---------------------------------------------------------------------------
14
- // Central redaction helpers (Q-11, temporary home to avoid owner conflicts).
14
+ // Central redaction helpers (temporary home to avoid owner conflicts).
15
15
  // Providers must call these instead of maintaining local deny-lists.
16
16
  // ---------------------------------------------------------------------------
17
17
 
@@ -45,7 +45,7 @@ function valueLooksSensitive(value: unknown): boolean {
45
45
  }
46
46
 
47
47
  /**
48
- * Redacts a single value: sensitive strings become "[REDACTED]" (Q-11).
48
+ * Redacts a single value: sensitive strings become "[REDACTED]".
49
49
  */
50
50
  export function redactValue(value: unknown): unknown {
51
51
  if (typeof value !== "string") return value;
@@ -54,7 +54,7 @@ export function redactValue(value: unknown): unknown {
54
54
  }
55
55
 
56
56
  /**
57
- * Redacts sensitive header values (Q-11). Returns a shallow copy; input untouched.
57
+ * Redacts sensitive header values. Returns a shallow copy; input untouched.
58
58
  * Deny-list (name contains): *key*, *token*, *secret*, cookie, authorization.
59
59
  * Value patterns (redacted regardless of name): sk-, AIza, gsk-, xox-.
60
60
  */
@@ -71,7 +71,7 @@ export function redactHeadersCentral(
71
71
  }
72
72
 
73
73
  /**
74
- * Redacts secret-looking substrings inside free text (Q-11).
74
+ * Redacts secret-looking substrings inside free text.
75
75
  * Replaces `sk-...`, `AIza...`, `gsk-...`, `xox-...` tokens with "[REDACTED]".
76
76
  */
77
77
  export function redactText(text: string): string {