nexus-agentd 0.2.6 → 0.3.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.
Files changed (56) hide show
  1. package/README.md +103 -14
  2. package/dist/a2a/runtime.d.ts +4 -1
  3. package/dist/a2a/runtime.js +213 -10
  4. package/dist/acp/runtime.js +13 -6
  5. package/dist/artifact-store.d.ts +168 -0
  6. package/dist/artifact-store.js +1111 -0
  7. package/dist/cli.js +13 -14
  8. package/dist/config.js +45 -5
  9. package/dist/control-plane-validation.d.ts +54 -0
  10. package/dist/control-plane-validation.js +152 -0
  11. package/dist/control-plane.d.ts +9 -32
  12. package/dist/control-plane.js +41 -157
  13. package/dist/drivers/index.js +12 -0
  14. package/dist/drivers/pi.js +1 -1
  15. package/dist/drivers/stdio.d.ts +4 -1
  16. package/dist/drivers/stdio.js +135 -19
  17. package/dist/events.d.ts +9 -0
  18. package/dist/events.js +17 -4
  19. package/dist/index.js +10 -1
  20. package/dist/process-tree.js +15 -6
  21. package/dist/run-store.d.ts +90 -7
  22. package/dist/run-store.js +254 -22
  23. package/dist/server/auth-policy.d.ts +27 -0
  24. package/dist/server/auth-policy.js +143 -0
  25. package/dist/server/connection.d.ts +8 -0
  26. package/dist/server/connection.js +67 -0
  27. package/dist/server/diagnostics.d.ts +40 -0
  28. package/dist/server/diagnostics.js +130 -0
  29. package/dist/server/metrics.d.ts +51 -0
  30. package/dist/server/metrics.js +56 -0
  31. package/dist/server/quota.d.ts +59 -0
  32. package/dist/server/quota.js +176 -0
  33. package/dist/server/validation.d.ts +29 -0
  34. package/dist/server/validation.js +221 -0
  35. package/dist/server.d.ts +3 -0
  36. package/dist/server.js +737 -419
  37. package/dist/session/admin-runs.d.ts +73 -0
  38. package/dist/session/admin-runs.js +298 -0
  39. package/dist/session/admission.d.ts +32 -0
  40. package/dist/session/admission.js +62 -0
  41. package/dist/session/readiness.d.ts +33 -0
  42. package/dist/session/readiness.js +174 -0
  43. package/dist/session.d.ts +65 -7
  44. package/dist/session.js +256 -109
  45. package/dist/sse-writer.d.ts +7 -0
  46. package/dist/sse-writer.js +60 -0
  47. package/dist/types.d.ts +46 -2
  48. package/dist/types.js +1 -0
  49. package/dist/webui/app/sources.js +18 -9
  50. package/dist/webui/icons.d.ts +1 -0
  51. package/dist/webui/icons.js +1 -0
  52. package/dist/webui/markup.js +3 -0
  53. package/dist/webui/styles.js +105 -15
  54. package/examples/client.mjs +102 -0
  55. package/nexus-agentd.example.json +19 -0
  56. package/package.json +3 -2
@@ -4,6 +4,7 @@ import { createHermesDriver } from './hermes.js';
4
4
  import { createOpenCodeDriver } from './opencode.js';
5
5
  import { createOpenClawDriver } from './openclaw.js';
6
6
  import { createPiDriver } from './pi.js';
7
+ import { createStdioAcpDriver } from './stdio.js';
7
8
  export function createDriverRegistry(config) {
8
9
  const drivers = new Map();
9
10
  for (const [id, driver] of Object.entries(config.agents)) {
@@ -12,6 +13,17 @@ export function createDriverRegistry(config) {
12
13
  if (driver.enabled === false)
13
14
  continue;
14
15
  switch (driver.driver) {
16
+ case 'stdio':
17
+ if (!driver.command)
18
+ break;
19
+ drivers.set(id, createStdioAcpDriver(id, driver, {
20
+ name: driver.name || id,
21
+ description: driver.description || 'Custom ACP agent over stdio',
22
+ command: driver.command,
23
+ args: driver.args || [],
24
+ probeArgs: driver.probeArgs
25
+ }));
26
+ break;
15
27
  case 'opencode':
16
28
  drivers.set(id, createOpenCodeDriver(id, driver));
17
29
  break;
@@ -15,7 +15,7 @@ export function createPiDriver(id, config) {
15
15
  return adapter;
16
16
  const command = driver.env.PI_ACP_PI_COMMAND || 'pi';
17
17
  try {
18
- const version = await probeCommand(command, ['--version'], driver.env, 'Pi runtime');
18
+ const version = await probeCommand(command, ['--version'], driver.env, 'Pi runtime', { ownsProcessGroup: process.platform !== 'win32' });
19
19
  return { ...adapter, version };
20
20
  }
21
21
  catch (error) {
@@ -10,4 +10,7 @@ export interface StdioAcpDriverDefaults {
10
10
  }
11
11
  export declare function createStdioAcpDriver(id: string, config: AgentdDriverConfig, defaults: StdioAcpDriverDefaults): AgentDriver;
12
12
  export declare function buildEnvironment(config: AgentdDriverConfig, defaults?: Record<string, string>): NodeJS.ProcessEnv;
13
- export declare function probeCommand(command: string, args: string[], env: NodeJS.ProcessEnv, label: string): Promise<string>;
13
+ export declare function probeCommand(command: string, args: string[], env: NodeJS.ProcessEnv, label: string, options?: {
14
+ ownsProcessGroup?: boolean;
15
+ timeoutMs?: number;
16
+ }): Promise<string>;
@@ -1,5 +1,9 @@
1
1
  import { spawn } from 'node:child_process';
2
+ import { existsSync, statSync } from 'node:fs';
3
+ import path from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
2
5
  import { resolveSecret } from '../config.js';
6
+ import { terminateProcessTree } from '../process-tree.js';
3
7
  const BASE_ENV_KEYS = [
4
8
  'PATH',
5
9
  'HOME',
@@ -11,7 +15,17 @@ const BASE_ENV_KEYS = [
11
15
  'TMPDIR',
12
16
  'TEMP',
13
17
  'LANG',
14
- 'LC_ALL'
18
+ 'LC_ALL',
19
+ // Node's Windows process creation and a number of ACP launchers require
20
+ // these values even when the command itself is an absolute path.
21
+ 'SystemRoot',
22
+ 'ComSpec',
23
+ 'USERPROFILE',
24
+ 'APPDATA',
25
+ 'LOCALAPPDATA',
26
+ 'ProgramFiles',
27
+ 'ProgramFiles(x86)',
28
+ 'PATHEXT'
15
29
  ];
16
30
  export function createStdioAcpDriver(id, config, defaults) {
17
31
  const command = config.command || defaults.command;
@@ -31,7 +45,7 @@ export function createStdioAcpDriver(id, config, defaults) {
31
45
  ownsProcessGroup: process.platform !== 'win32',
32
46
  async probe() {
33
47
  try {
34
- const version = await probeCommand(command, defaults.probeArgs || ['--version'], env, name);
48
+ const version = await probeCommand(command, config.probeArgs || defaults.probeArgs || ['--version'], env, name, { ownsProcessGroup: process.platform !== 'win32' });
35
49
  return {
36
50
  id,
37
51
  name,
@@ -53,7 +67,7 @@ export function createStdioAcpDriver(id, config, defaults) {
53
67
  }
54
68
  },
55
69
  spawn(workspace) {
56
- return spawn(command, args, {
70
+ return spawnCommand(command, args, {
57
71
  cwd: workspace,
58
72
  env,
59
73
  detached: process.platform !== 'win32',
@@ -75,42 +89,144 @@ export function buildEnvironment(config, defaults = {}) {
75
89
  }
76
90
  return result;
77
91
  }
78
- export function probeCommand(command, args, env, label) {
92
+ export function probeCommand(command, args, env, label, options = {}) {
79
93
  return new Promise((resolve, reject) => {
80
- const child = spawn(command, args, {
94
+ const child = spawnCommand(command, args, {
81
95
  env,
82
- stdio: ['ignore', 'pipe', 'pipe']
96
+ stdio: ['ignore', 'pipe', 'pipe'],
97
+ detached: options.ownsProcessGroup ?? process.platform !== 'win32',
98
+ windowsHide: true
83
99
  });
84
100
  let output = '';
85
101
  let settled = false;
102
+ let finishing = false;
103
+ let timedOut = false;
104
+ let timer;
86
105
  const finish = (error) => {
87
106
  if (settled)
88
107
  return;
89
108
  settled = true;
90
- clearTimeout(timer);
109
+ if (timer)
110
+ clearTimeout(timer);
91
111
  if (error)
92
112
  reject(error);
93
113
  else
94
114
  resolve(output.trim().split(/\r?\n/)[0] || 'unknown');
95
115
  };
96
- const timer = setTimeout(() => {
97
- child.kill();
98
- finish(new Error(`${label} probe timed out`));
99
- }, 5000);
116
+ const ownsProcessGroup = options.ownsProcessGroup ?? process.platform !== 'win32';
117
+ const cleanup = () => terminateProcessTree(child, {
118
+ ownsProcessGroup,
119
+ graceMs: 250
120
+ });
121
+ const cleanupFailure = (error) => error instanceof Error ? error.message : String(error);
122
+ const settleExit = (code, signal) => {
123
+ if (settled || finishing || timedOut)
124
+ return;
125
+ finishing = true;
126
+ if (timer)
127
+ clearTimeout(timer);
128
+ // A successful wrapper can leave a child in its process group.
129
+ // Clean the group before exposing probe success/failure.
130
+ void cleanup().then(() => {
131
+ if (timedOut || settled)
132
+ return;
133
+ finishing = false;
134
+ if (code === 0)
135
+ finish();
136
+ else {
137
+ finish(new Error(output.trim() ||
138
+ `${label} probe exited with ${signal || code || 'unknown'}`));
139
+ }
140
+ }, (error) => {
141
+ if (settled)
142
+ return;
143
+ finishing = false;
144
+ finish(new Error(`${label} probe cleanup failed: ${cleanupFailure(error)}`));
145
+ });
146
+ };
147
+ timer = setTimeout(() => {
148
+ if (settled || finishing || timedOut)
149
+ return;
150
+ timedOut = true;
151
+ finishing = true;
152
+ if (timer)
153
+ clearTimeout(timer);
154
+ // Probe commands are allowed to be wrappers that launch another
155
+ // process. Wait for tree termination before rejecting so a timed
156
+ // out health check cannot leave an agent running in the background.
157
+ void cleanup().then(() => finish(new Error(`${label} probe timed out`)), (error) => finish(new Error(`${label} probe timed out; cleanup failed: ${cleanupFailure(error)}`)));
158
+ }, options.timeoutMs ?? 5000);
159
+ timer.unref?.();
100
160
  child.stdout.on('data', (chunk) => {
101
161
  output = `${output}${String(chunk)}`.slice(-8192);
102
162
  });
103
163
  child.stderr.on('data', (chunk) => {
104
164
  output = `${output}${String(chunk)}`.slice(-8192);
105
165
  });
106
- child.once('error', (error) => finish(error));
107
- child.once('exit', (code, signal) => {
108
- if (code === 0)
109
- finish();
110
- else {
111
- finish(new Error(output.trim() ||
112
- `${label} probe exited with ${signal || code || 'unknown'}`));
113
- }
166
+ child.once('error', (error) => {
167
+ if (settled || finishing || timedOut)
168
+ return;
169
+ finishing = true;
170
+ if (timer)
171
+ clearTimeout(timer);
172
+ void cleanup().then(() => finish(error), (cleanupError) => finish(new Error(`${label} probe failed (${error.message}); cleanup failed: ${cleanupFailure(cleanupError)}`)));
114
173
  });
174
+ child.once('exit', (code, signal) => settleExit(code, signal));
115
175
  });
116
176
  }
177
+ function needsWindowsShell(command) {
178
+ return process.platform === 'win32' && /\.(?:cmd|bat)$/i.test(command);
179
+ }
180
+ function spawnCommand(command, args, options) {
181
+ const resolvedCommand = resolveWindowsCommand(command, options.env, options.cwd);
182
+ if (!needsWindowsShell(resolvedCommand)) {
183
+ return spawn(resolvedCommand, args, options);
184
+ }
185
+ // cmd.exe's /S /C parser needs an outer pair around the complete command
186
+ // when the batch path itself is quoted; otherwise it treats the first
187
+ // argument as part of the executable path.
188
+ const commandLine = `"${[quoteWindowsArg(resolvedCommand), ...args.map(quoteWindowsArg)].join(' ')}"`;
189
+ return spawn(process.env.ComSpec || 'cmd.exe', ['/d', '/s', '/c', commandLine], {
190
+ ...options,
191
+ windowsVerbatimArguments: true,
192
+ shell: false
193
+ });
194
+ }
195
+ function resolveWindowsCommand(command, env, cwd) {
196
+ if (process.platform !== 'win32')
197
+ return command;
198
+ const pathValue = env?.PATH || process.env.PATH || '';
199
+ const directory = cwd instanceof URL ? fileURLToPath(cwd) : cwd || process.cwd();
200
+ const pathEntries = pathValue.split(path.delimiter).filter(Boolean).map((entry) => entry.replace(/^"|"$/g, ''));
201
+ const pathExtValue = env?.PATHEXT || process.env.PATHEXT || '.COM;.EXE;.BAT;.CMD';
202
+ const extensions = pathExtValue
203
+ .split(';')
204
+ .map((extension) => extension.trim())
205
+ .filter((extension) => /^\.(?:com|exe|bat|cmd)$/i.test(extension));
206
+ // npm installs a POSIX shim without an extension beside its .cmd shim.
207
+ // That file is not a Windows executable; selecting it makes real npm
208
+ // commands fail even though a runnable .cmd exists in the same directory.
209
+ const names = /\.(?:com|exe|bat|cmd)$/i.test(command)
210
+ ? [command] : extensions.map((extension) => `${command}${extension}`);
211
+ const candidates = commandHasPath(command)
212
+ ? names.map((name) => path.resolve(directory, name))
213
+ : [directory, ...pathEntries].flatMap((entry) => names.map((name) => path.resolve(directory, entry, name)));
214
+ const match = candidates.find(isRegularFile);
215
+ return match || command;
216
+ }
217
+ function commandHasPath(command) {
218
+ return command.includes('\\') || command.includes('/') || path.win32.isAbsolute(command);
219
+ }
220
+ function isRegularFile(file) {
221
+ try {
222
+ return existsSync(file) && statSync(file).isFile();
223
+ }
224
+ catch {
225
+ return false;
226
+ }
227
+ }
228
+ function quoteWindowsArg(value) {
229
+ if (!/[\s"&()^|<>]/.test(value))
230
+ return value;
231
+ return `"${value.replace(/(\\*)"/g, '$1$1\\"').replace(/(\\*)$/g, '$1$1')}"`;
232
+ }
package/dist/events.d.ts CHANGED
@@ -8,6 +8,15 @@ export declare class SessionEventLog {
8
8
  constructor(sessionId: string, maxEvents: number);
9
9
  append(type: AgentdEventType, data?: unknown): AgentdEvent;
10
10
  after(id?: string): AgentdEvent[];
11
+ replay(id?: string): {
12
+ earliestId: string;
13
+ latestId: string;
14
+ reset: {
15
+ reason: string;
16
+ requestedAfter: string | null;
17
+ } | undefined;
18
+ events: AgentdEvent[];
19
+ };
11
20
  subscribe(listener: (event: AgentdEvent) => void): () => boolean;
12
21
  get lastId(): string | undefined;
13
22
  }
package/dist/events.js CHANGED
@@ -36,10 +36,23 @@ export class SessionEventLog {
36
36
  return event;
37
37
  }
38
38
  after(id) {
39
- const sequence = Number(id || 0);
40
- return this.events
41
- .filter((event) => Number(event.id) > sequence)
42
- .map((event) => structuredClone(event));
39
+ return this.replay(id).events;
40
+ }
41
+ replay(id) {
42
+ const sequence = id === undefined ? 0 : Number(id);
43
+ const earliest = Number(this.events[0]?.id || this.sequence + 1);
44
+ const reason = id !== undefined && (!/^\d+$/.test(id) || !Number.isSafeInteger(sequence))
45
+ ? 'invalid'
46
+ : sequence > this.sequence ? 'ahead'
47
+ : sequence < earliest - 1 ? 'expired' : undefined;
48
+ return {
49
+ earliestId: this.events[0]?.id ?? null,
50
+ latestId: String(this.sequence),
51
+ reset: reason ? { reason, requestedAfter: id ?? null } : undefined,
52
+ events: reason ? [] : this.events
53
+ .filter((event) => Number(event.id) > sequence)
54
+ .map((event) => structuredClone(event))
55
+ };
43
56
  }
44
57
  subscribe(listener) {
45
58
  this.listeners.add(listener);
package/dist/index.js CHANGED
@@ -9,7 +9,16 @@ import { WorkspacePolicy } from './workspace.js';
9
9
  export async function startAgentd(configPath) {
10
10
  const absoluteConfigPath = path.resolve(configPath);
11
11
  const config = await loadAgentdConfig(absoluteConfigPath);
12
- const runStore = new RunStore(runStorePathForConfig(absoluteConfigPath), 1000, config.maxRequestBytes);
12
+ const runStore = new RunStore(runStorePathForConfig(absoluteConfigPath), {
13
+ maxRuns: config.history?.maxRuns ?? 1000,
14
+ maxTaskChars: config.maxRequestBytes,
15
+ historyRetentionDays: config.history?.retentionDays ?? 30,
16
+ historyMaxBytes: config.history?.maxBytes ?? 64 * 1024 * 1024,
17
+ retentionDays: config.artifacts?.retentionDays ?? 30,
18
+ maxTotalBytes: config.artifacts?.maxBytes ?? 512 * 1024 * 1024,
19
+ maxArtifactBytes: config.artifacts?.maxArtifactBytes ?? 12 * 1024 * 1024,
20
+ maxQueuedBytes: config.artifacts?.maxQueuedBytes ?? 64 * 1024 * 1024
21
+ });
13
22
  await runStore.init();
14
23
  const workspacePolicy = await WorkspacePolicy.create(config.workspaceRoots);
15
24
  const sessions = new SessionManager(config, workspacePolicy, createDriverRegistry(config), runStore);
@@ -3,30 +3,39 @@ const DEFAULT_GRACE_MS = 2_000;
3
3
  const FORCE_KILL_WAIT_MS = 1_000;
4
4
  export async function terminateProcessTree(child, options = {}) {
5
5
  const pid = child.pid;
6
- if (!pid || hasExited(child))
6
+ if (!pid)
7
7
  return;
8
8
  const graceMs = Math.max(0, options.graceMs ?? DEFAULT_GRACE_MS);
9
9
  if (process.platform === 'win32') {
10
+ // Once a Windows process has exited taskkill can no longer address its
11
+ // descendants by the parent's PID. Callers should tear down before
12
+ // exit when they need Windows tree semantics.
13
+ if (hasExited(child))
14
+ return;
10
15
  await taskkill(pid, false);
11
16
  if (await waitForExit(child, graceMs))
12
17
  return;
13
18
  await taskkill(pid, true);
14
- await waitForExit(child, FORCE_KILL_WAIT_MS);
15
- return;
19
+ if (await waitForExit(child, FORCE_KILL_WAIT_MS))
20
+ return;
21
+ throw new Error(`Failed to terminate process ${pid}`);
16
22
  }
17
23
  if (options.ownsProcessGroup === true) {
18
24
  signalUnixGroup(pid, 'SIGTERM');
19
25
  if (await waitForUnixGroupExit(pid, graceMs))
20
26
  return;
21
27
  signalUnixGroup(pid, 'SIGKILL');
22
- await waitForUnixGroupExit(pid, FORCE_KILL_WAIT_MS);
23
- return;
28
+ if (await waitForUnixGroupExit(pid, FORCE_KILL_WAIT_MS))
29
+ return;
30
+ throw new Error(`Failed to terminate process group ${pid}`);
24
31
  }
25
32
  signalUnix(child, 'SIGTERM');
26
33
  if (await waitForExit(child, graceMs))
27
34
  return;
28
35
  signalUnix(child, 'SIGKILL');
29
- await waitForExit(child, FORCE_KILL_WAIT_MS);
36
+ if (await waitForExit(child, FORCE_KILL_WAIT_MS))
37
+ return;
38
+ throw new Error(`Failed to terminate process ${pid}`);
30
39
  }
31
40
  function signalUnix(child, signal) {
32
41
  try {
@@ -1,5 +1,8 @@
1
- import type { AgentdProtocol, AgentdRunDetail, AgentdRunView, AgentdSessionState } from './types.js';
2
- interface StoredRun extends AgentdRunDetail {
1
+ import { ArtifactStore, type ArtifactReadResult, type ArtifactStoreMetrics, type ArtifactStoreOptions, type StoredArtifactView } from './artifact-store.js';
2
+ import type { AgentdArtifact, AgentdProtocol, AgentdRunDetail, AgentdRunView, AgentdSessionState } from './types.js';
3
+ export { ArtifactStore, artifactStorePathForRunStore } from './artifact-store.js';
4
+ export type { ArtifactReadResult, ArtifactStorageStatus, ArtifactStoreMetrics, ArtifactStoreIo, ArtifactStoreOptions, StoredArtifactView } from './artifact-store.js';
5
+ interface StoredRun extends Omit<AgentdRunDetail, 'taskPreview'> {
3
6
  ownerKeyId: string;
4
7
  }
5
8
  export interface CreateRunInput {
@@ -12,6 +15,17 @@ export interface CreateRunInput {
12
15
  ownerKeyId: string;
13
16
  task: string;
14
17
  inputAttachmentCount?: number;
18
+ retryOfRunId?: string;
19
+ }
20
+ export interface RunStoreOptions extends ArtifactStoreOptions {
21
+ /** History count cap; the positional maxRuns argument takes precedence. */
22
+ maxRuns?: number;
23
+ /** Task text cap; the positional maxTaskChars argument takes precedence. */
24
+ maxTaskChars?: number;
25
+ /** Terminal run metadata retention duration. Active runs are protected. */
26
+ historyRetentionDays?: number;
27
+ /** JSON run-history byte budget, independent from artifact payload bytes. */
28
+ historyMaxBytes?: number;
15
29
  }
16
30
  export interface RunListQuery {
17
31
  agentId?: string;
@@ -19,20 +33,89 @@ export interface RunListQuery {
19
33
  state?: AgentdSessionState;
20
34
  query?: string;
21
35
  limit?: number;
36
+ offset?: number;
37
+ }
38
+ export interface RunArtifactQuery {
39
+ agentId?: string;
40
+ /** 'all' keeps every kind; otherwise one of artifactKind() values. */
41
+ kind?: string;
42
+ query?: string;
43
+ limit?: number;
44
+ offset?: number;
45
+ }
46
+ export interface RunArtifactRow {
47
+ id: string;
48
+ runId: string;
49
+ agentId: string;
50
+ agentName: string;
51
+ runState: AgentdSessionState;
52
+ runStartedAt: number;
53
+ name?: string;
54
+ filename?: string;
55
+ mediaType?: string;
56
+ size?: number;
57
+ kind: string;
58
+ downloadable: boolean;
59
+ storageStatus?: string;
60
+ createdAt?: number;
22
61
  }
62
+ /** Coarse bucket used by the console filter and list grouping. */
63
+ export declare function artifactKind(mediaType: string | undefined): "image" | "audio" | "video" | "file";
23
64
  export declare class RunStore {
24
65
  readonly filePath: string;
25
- private readonly maxRuns;
26
- private readonly maxTaskChars;
27
66
  private readonly runs;
28
67
  private initialized;
29
68
  private dirty;
30
69
  private persistTimer?;
31
70
  private writeQueue;
32
- constructor(filePath: string, maxRuns?: number, maxTaskChars?: number);
71
+ readonly artifactStore: ArtifactStore;
72
+ private readonly maxRuns;
73
+ private readonly maxTaskChars;
74
+ private readonly historyRetentionDays?;
75
+ private readonly historyMaxBytes?;
76
+ private readonly now;
77
+ private lastWriteMs;
78
+ private writeFailures;
79
+ private historyBytes;
80
+ constructor(filePath: string, maxRuns?: number, maxTaskChars?: number | RunStoreOptions, options?: RunStoreOptions);
81
+ constructor(filePath: string, options?: RunStoreOptions);
33
82
  init(): Promise<void>;
34
83
  create(input: CreateRunInput): AgentdRunDetail;
35
- update(id: string, patch: Partial<Pick<StoredRun, 'protocolSessionId' | 'state' | 'progress' | 'output' | 'error' | 'artifacts' | 'artifactCount' | 'updatedAt' | 'endedAt' | 'durationMs' | 'inputAttachmentCount' | 'completion'>>): AgentdRunDetail | undefined;
84
+ update(id: string, patch: Partial<Pick<StoredRun, 'protocolSessionId' | 'state' | 'progress' | 'output' | 'error' | 'artifacts' | 'artifactCount' | 'updatedAt' | 'endedAt' | 'durationMs' | 'inputAttachmentCount' | 'completion' | 'retryOfRunId'>>): AgentdRunDetail | undefined;
85
+ /** Queue payload snapshots without blocking the protocol sink. */
86
+ recordArtifacts(runId: string, artifacts: AgentdArtifact[]): StoredArtifactView[] | undefined;
87
+ artifactViews(runId: string): StoredArtifactView[] | undefined;
88
+ readArtifact(runId: string, artifactId: string): Promise<ArtifactReadResult | undefined>;
89
+ /** Flat, newest-first artifact index across all retained runs. */
90
+ listArtifacts(query?: RunArtifactQuery): {
91
+ artifacts: RunArtifactRow[];
92
+ total: number;
93
+ };
94
+ /** Delete one artifact's payload and record entry; the run record stays. */
95
+ removeArtifact(runId: string, artifactId: string): Promise<boolean>;
96
+ getOwnerKeyId(runId: string): string | undefined;
97
+ metrics(): {
98
+ historyBytes: number;
99
+ retainedRuns: number;
100
+ pendingWrites: number;
101
+ lastWriteMs: number;
102
+ writeFailures: number;
103
+ artifactStoreStats: ArtifactStoreMetrics;
104
+ };
105
+ stats(): {
106
+ historyBytes: number;
107
+ retainedRuns: number;
108
+ pendingWrites: number;
109
+ lastWriteMs: number;
110
+ writeFailures: number;
111
+ artifactStoreStats: ArtifactStoreMetrics;
112
+ };
113
+ setRetryOfRunId(runId: string, retryOfRunId?: string): AgentdRunDetail | undefined;
114
+ /**
115
+ * Remove a terminal run record and its stored artifacts. Active runs are
116
+ * refused so a live turn cannot lose its progress tracking.
117
+ */
118
+ deleteRun(id: string): AgentdRunDetail | undefined;
36
119
  get(id: string): AgentdRunDetail | undefined;
37
120
  list(query?: RunListQuery): {
38
121
  runs: AgentdRunView[];
@@ -49,6 +132,6 @@ export declare class RunStore {
49
132
  private prune;
50
133
  private persist;
51
134
  private assertInitialized;
135
+ private applyArtifactViews;
52
136
  }
53
137
  export declare function runStorePathForConfig(configPath: string): string;
54
- export {};