@herdctl/core 5.10.1 → 5.12.0
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/dist/fleet-manager/__tests__/agent-management.test.d.ts +9 -0
- package/dist/fleet-manager/__tests__/agent-management.test.d.ts.map +1 -0
- package/dist/fleet-manager/__tests__/agent-management.test.js +358 -0
- package/dist/fleet-manager/__tests__/agent-management.test.js.map +1 -0
- package/dist/fleet-manager/__tests__/errors.test.js +40 -2
- package/dist/fleet-manager/__tests__/errors.test.js.map +1 -1
- package/dist/fleet-manager/__tests__/session-control.test.d.ts +14 -0
- package/dist/fleet-manager/__tests__/session-control.test.d.ts.map +1 -0
- package/dist/fleet-manager/__tests__/session-control.test.js +292 -0
- package/dist/fleet-manager/__tests__/session-control.test.js.map +1 -0
- package/dist/fleet-manager/__tests__/trigger.test.js +137 -2
- package/dist/fleet-manager/__tests__/trigger.test.js.map +1 -1
- package/dist/fleet-manager/agent-management.d.ts +107 -0
- package/dist/fleet-manager/agent-management.d.ts.map +1 -0
- package/dist/fleet-manager/agent-management.js +237 -0
- package/dist/fleet-manager/agent-management.js.map +1 -0
- package/dist/fleet-manager/errors.d.ts +30 -0
- package/dist/fleet-manager/errors.d.ts.map +1 -1
- package/dist/fleet-manager/errors.js +40 -0
- package/dist/fleet-manager/errors.js.map +1 -1
- package/dist/fleet-manager/fleet-manager.d.ts +166 -2
- package/dist/fleet-manager/fleet-manager.d.ts.map +1 -1
- package/dist/fleet-manager/fleet-manager.js +289 -2
- package/dist/fleet-manager/fleet-manager.js.map +1 -1
- package/dist/fleet-manager/index.d.ts +1 -0
- package/dist/fleet-manager/index.d.ts.map +1 -1
- package/dist/fleet-manager/index.js +2 -0
- package/dist/fleet-manager/index.js.map +1 -1
- package/dist/fleet-manager/job-control.d.ts.map +1 -1
- package/dist/fleet-manager/job-control.js +54 -9
- package/dist/fleet-manager/job-control.js.map +1 -1
- package/dist/fleet-manager/types.d.ts +28 -0
- package/dist/fleet-manager/types.d.ts.map +1 -1
- package/dist/runner/index.d.ts +1 -1
- package/dist/runner/index.d.ts.map +1 -1
- package/dist/runner/index.js +1 -1
- package/dist/runner/index.js.map +1 -1
- package/dist/runner/runtime/__tests__/cli-runtime.test.js +35 -0
- package/dist/runner/runtime/__tests__/cli-runtime.test.js.map +1 -1
- package/dist/runner/runtime/__tests__/cli-session-path.test.js +63 -6
- package/dist/runner/runtime/__tests__/cli-session-path.test.js.map +1 -1
- package/dist/runner/runtime/__tests__/docker-security.test.js +39 -0
- package/dist/runner/runtime/__tests__/docker-security.test.js.map +1 -1
- package/dist/runner/runtime/cli-session-path.d.ts +18 -6
- package/dist/runner/runtime/cli-session-path.d.ts.map +1 -1
- package/dist/runner/runtime/cli-session-path.js +48 -8
- package/dist/runner/runtime/cli-session-path.js.map +1 -1
- package/dist/runner/runtime/index.d.ts +1 -0
- package/dist/runner/runtime/index.d.ts.map +1 -1
- package/dist/runner/runtime/index.js +5 -0
- package/dist/runner/runtime/index.js.map +1 -1
- package/dist/scheduler/errors.d.ts +1 -0
- package/dist/scheduler/errors.d.ts.map +1 -1
- package/dist/state/__tests__/session-discovery.test.js +172 -6
- package/dist/state/__tests__/session-discovery.test.js.map +1 -1
- package/dist/state/session-discovery.d.ts +45 -0
- package/dist/state/session-discovery.d.ts.map +1 -1
- package/dist/state/session-discovery.js +78 -4
- package/dist/state/session-discovery.js.map +1 -1
- package/package.json +1 -1
|
@@ -11,9 +11,10 @@
|
|
|
11
11
|
* @module fleet-manager
|
|
12
12
|
*/
|
|
13
13
|
import { EventEmitter } from "node:events";
|
|
14
|
-
import { type ResolvedAgent, type ResolvedConfig } from "../config/index.js";
|
|
14
|
+
import { type AgentConfig, type ResolvedAgent, type ResolvedConfig } from "../config/index.js";
|
|
15
15
|
import { Scheduler } from "../scheduler/index.js";
|
|
16
|
-
import { type StateDirectory } from "../state/index.js";
|
|
16
|
+
import { type ChatMessage, type DiscoveredSession, type StateDirectory } from "../state/index.js";
|
|
17
|
+
import { type AddAgentOptions } from "./agent-management.js";
|
|
17
18
|
import type { IChatManager } from "./chat-manager-interface.js";
|
|
18
19
|
import type { FleetManagerContext } from "./context.js";
|
|
19
20
|
import type { AgentInfo, CancelJobResult, ConfigChange, ConfigReloadedPayload, FleetManagerLogger, FleetManagerOptions, FleetManagerState, FleetManagerStatus, FleetManagerStopOptions, FleetStatus, ForkJobResult, JobModifications, LogEntry, LogStreamOptions, ScheduleInfo, TriggerOptions, TriggerResult } from "./types.js";
|
|
@@ -40,9 +41,12 @@ export declare class FleetManager extends EventEmitter implements FleetManagerCo
|
|
|
40
41
|
private statusQueries;
|
|
41
42
|
private scheduleManagement;
|
|
42
43
|
private configReloadModule;
|
|
44
|
+
private agentManagement;
|
|
43
45
|
private jobControl;
|
|
44
46
|
private logStreaming;
|
|
45
47
|
private scheduleExecutor;
|
|
48
|
+
private sessionDiscovery;
|
|
49
|
+
private sessionMetadataStore;
|
|
46
50
|
private chatManagers;
|
|
47
51
|
constructor(options: FleetManagerOptions);
|
|
48
52
|
getConfig(): ResolvedConfig | null;
|
|
@@ -92,6 +96,142 @@ export declare class FleetManager extends EventEmitter implements FleetManagerCo
|
|
|
92
96
|
disableSchedule(agentName: string, scheduleName: string): Promise<ScheduleInfo>;
|
|
93
97
|
reload(): Promise<ConfigReloadedPayload>;
|
|
94
98
|
computeConfigChanges(oldConfig: ResolvedConfig | null, newConfig: ResolvedConfig): ConfigChange[];
|
|
99
|
+
/**
|
|
100
|
+
* Register an agent at runtime without writing YAML or calling `reload()`.
|
|
101
|
+
*
|
|
102
|
+
* The config is validated, merged with fleet defaults, normalized (working
|
|
103
|
+
* directory resolved to an absolute path), and wired into the running fleet
|
|
104
|
+
* so it is immediately triggerable and appears in fleet status. A
|
|
105
|
+
* `config:reloaded` event is emitted describing the change.
|
|
106
|
+
*
|
|
107
|
+
* This is the programmatic counterpart to editing `herdctl.yaml` and calling
|
|
108
|
+
* `reload()` — useful for apps that manage agents in memory rather than on
|
|
109
|
+
* disk.
|
|
110
|
+
*
|
|
111
|
+
* @param agent - The agent configuration to register (must include `name`)
|
|
112
|
+
* @param options - Resolution options (base dir, defaults merge, replace)
|
|
113
|
+
* @returns Info for the newly registered agent
|
|
114
|
+
* @throws {InvalidStateError} If the fleet manager is not yet initialized
|
|
115
|
+
* @throws {ConfigurationError} If validation fails or the name collides
|
|
116
|
+
*
|
|
117
|
+
* @example
|
|
118
|
+
* ```typescript
|
|
119
|
+
* await fleet.addAgent({
|
|
120
|
+
* name: "keeper-myproject",
|
|
121
|
+
* working_directory: "/abs/projects/myproject",
|
|
122
|
+
* runtime: "cli",
|
|
123
|
+
* permission_mode: "acceptEdits",
|
|
124
|
+
* });
|
|
125
|
+
* await fleet.trigger("keeper-myproject", undefined, { prompt: "Hello" });
|
|
126
|
+
* ```
|
|
127
|
+
*/
|
|
128
|
+
addAgent(agent: AgentConfig | (Record<string, unknown> & {
|
|
129
|
+
name: string;
|
|
130
|
+
}), options?: AddAgentOptions): Promise<AgentInfo>;
|
|
131
|
+
/**
|
|
132
|
+
* Unregister an agent at runtime.
|
|
133
|
+
*
|
|
134
|
+
* Removes the agent from the in-memory config and the scheduler. Accepts a
|
|
135
|
+
* qualified name or a local name. Running jobs are unaffected.
|
|
136
|
+
*
|
|
137
|
+
* @param name - The agent qualified name or local name to remove
|
|
138
|
+
* @returns `true` if an agent was removed, `false` if no match was found
|
|
139
|
+
* @throws {InvalidStateError} If the fleet manager is not yet initialized
|
|
140
|
+
*/
|
|
141
|
+
removeAgent(name: string): Promise<boolean>;
|
|
142
|
+
/**
|
|
143
|
+
* List discovered Claude Code sessions for an agent.
|
|
144
|
+
*
|
|
145
|
+
* Derives the agent's working directory and Docker mode from the loaded
|
|
146
|
+
* config, so consumers don't have to map agent → working directory by hand.
|
|
147
|
+
* Sessions are returned sorted by modification time (newest first).
|
|
148
|
+
*
|
|
149
|
+
* Note: sessions are keyed by working directory. This method uses the agent's
|
|
150
|
+
* *configured* `working_directory`. If you triggered the agent with a
|
|
151
|
+
* per-trigger `workingDirectory` override (see {@link TriggerOptions}), the
|
|
152
|
+
* resulting sessions live under that override directory and will NOT appear
|
|
153
|
+
* here — list them by scanning that directory instead (e.g. the directory
|
|
154
|
+
* grouped `getAllSessions` view on the underlying `SessionDiscoveryService`).
|
|
155
|
+
*
|
|
156
|
+
* @param name - The agent qualified name or local name
|
|
157
|
+
* @param options - Optional `limit` for top-N enrichment
|
|
158
|
+
* @returns Array of discovered sessions (empty if the agent has no working
|
|
159
|
+
* directory or no sessions yet)
|
|
160
|
+
* @throws {InvalidStateError} If the fleet manager is not yet initialized
|
|
161
|
+
* @throws {AgentNotFoundError} If no agent with that name exists
|
|
162
|
+
*/
|
|
163
|
+
getAgentSessions(name: string, options?: {
|
|
164
|
+
limit?: number;
|
|
165
|
+
}): Promise<DiscoveredSession[]>;
|
|
166
|
+
/**
|
|
167
|
+
* Get the parsed chat messages for one of an agent's sessions.
|
|
168
|
+
*
|
|
169
|
+
* Derives the working directory and Docker mode from the loaded config.
|
|
170
|
+
*
|
|
171
|
+
* @param name - The agent qualified name or local name
|
|
172
|
+
* @param sessionId - The session ID to read
|
|
173
|
+
* @returns Array of chat messages (empty if the agent has no working directory)
|
|
174
|
+
* @throws {InvalidStateError} If the fleet manager is not yet initialized
|
|
175
|
+
* @throws {AgentNotFoundError} If no agent with that name exists
|
|
176
|
+
*/
|
|
177
|
+
getAgentSessionMessages(name: string, sessionId: string): Promise<ChatMessage[]>;
|
|
178
|
+
/**
|
|
179
|
+
* Delete one of an agent's Claude Code session transcripts from disk.
|
|
180
|
+
*
|
|
181
|
+
* Resolves the agent's working directory and Docker mode from the loaded
|
|
182
|
+
* config, computes the CLI (or Docker) transcript file path with the same
|
|
183
|
+
* encoder Claude Code uses, deletes it, and invalidates the session-discovery
|
|
184
|
+
* cache so a subsequent {@link getAgentSessions} no longer lists it.
|
|
185
|
+
*
|
|
186
|
+
* The `sessionId` is validated (only `[A-Za-z0-9-]` is allowed) to prevent
|
|
187
|
+
* path traversal before any filesystem access.
|
|
188
|
+
*
|
|
189
|
+
* @param name - The agent qualified name or local name
|
|
190
|
+
* @param sessionId - The session ID whose transcript should be removed
|
|
191
|
+
* @returns `true` if a file was removed, `false` if no transcript existed (or
|
|
192
|
+
* the agent has no working directory)
|
|
193
|
+
* @throws {InvalidStateError} If the fleet manager is not yet initialized
|
|
194
|
+
* @throws {AgentNotFoundError} If no agent with that name exists
|
|
195
|
+
* @throws {Error} If `sessionId` contains invalid characters
|
|
196
|
+
*/
|
|
197
|
+
deleteSession(name: string, sessionId: string): Promise<boolean>;
|
|
198
|
+
/**
|
|
199
|
+
* Set (or clear) the custom display name for one of an agent's sessions.
|
|
200
|
+
*
|
|
201
|
+
* Writes through this fleet's shared {@link SessionMetadataStore} — the same
|
|
202
|
+
* store the session-discovery service reads — so a subsequent
|
|
203
|
+
* {@link getAgentSessions} reflects the new `customName` immediately. Passing
|
|
204
|
+
* `null` or an empty/whitespace string clears any existing custom name.
|
|
205
|
+
*
|
|
206
|
+
* @param name - The agent qualified name or local name
|
|
207
|
+
* @param sessionId - The session ID to (re)name
|
|
208
|
+
* @param customName - The custom name to set, or `null`/empty to clear it
|
|
209
|
+
* @throws {InvalidStateError} If the fleet manager is not yet initialized
|
|
210
|
+
* @throws {AgentNotFoundError} If no agent with that name exists
|
|
211
|
+
*/
|
|
212
|
+
setSessionName(name: string, sessionId: string, customName: string | null): Promise<void>;
|
|
213
|
+
/**
|
|
214
|
+
* Drop the cached session listing for an agent so the next
|
|
215
|
+
* {@link getAgentSessions} call rebuilds it from disk.
|
|
216
|
+
*
|
|
217
|
+
* The underlying {@link SessionDiscoveryService} caches each working
|
|
218
|
+
* directory's listing for up to its TTL (default 30s). That cache is now
|
|
219
|
+
* mtime-aware, so a *newly created* transcript file is normally picked up
|
|
220
|
+
* immediately. Call this when you want to force a fresh listing regardless —
|
|
221
|
+
* e.g. after each chat turn, or on filesystems whose directory mtime has
|
|
222
|
+
* coarse (1-second) granularity where a same-second create might otherwise be
|
|
223
|
+
* masked until the TTL expires. It also drops the attribution index so a
|
|
224
|
+
* session created this turn (whose job record was just written) is attributed.
|
|
225
|
+
*
|
|
226
|
+
* Resolves the agent's working directory and Docker mode from the loaded
|
|
227
|
+
* config (no override directories — see {@link getAgentSessions}). A no-op
|
|
228
|
+
* when the agent has no working directory.
|
|
229
|
+
*
|
|
230
|
+
* @param name - The agent qualified name or local name
|
|
231
|
+
* @throws {InvalidStateError} If the fleet manager is not yet initialized
|
|
232
|
+
* @throws {AgentNotFoundError} If no agent with that name exists
|
|
233
|
+
*/
|
|
234
|
+
invalidateSessions(name: string): void;
|
|
95
235
|
trigger(agentName: string, scheduleName?: string, options?: TriggerOptions): Promise<TriggerResult>;
|
|
96
236
|
cancelJob(jobId: string, options?: {
|
|
97
237
|
timeout?: number;
|
|
@@ -133,6 +273,30 @@ export declare class FleetManager extends EventEmitter implements FleetManagerCo
|
|
|
133
273
|
* @throws ConfigurationError if duplicate qualified names are found
|
|
134
274
|
*/
|
|
135
275
|
private validateUniqueAgentNames;
|
|
276
|
+
/**
|
|
277
|
+
* Get (lazily creating) the shared SessionMetadataStore bound to this fleet's
|
|
278
|
+
* state directory. Shared with the SessionDiscoveryService so metadata writes
|
|
279
|
+
* (e.g. {@link setSessionName}) and reads (during discovery) use one cache.
|
|
280
|
+
*/
|
|
281
|
+
private getSessionMetadataStore;
|
|
282
|
+
/**
|
|
283
|
+
* Get (lazily creating) the SessionDiscoveryService bound to this fleet's
|
|
284
|
+
* state directory. Reused across calls so its caches are shared. The
|
|
285
|
+
* service shares this fleet's {@link SessionMetadataStore} so custom-name
|
|
286
|
+
* writes are immediately visible to discovery.
|
|
287
|
+
*/
|
|
288
|
+
private getSessionDiscovery;
|
|
289
|
+
/**
|
|
290
|
+
* Look up an agent by qualified or local name and derive the inputs the
|
|
291
|
+
* SessionDiscoveryService needs (working directory + Docker mode).
|
|
292
|
+
*
|
|
293
|
+
* @param name - The agent qualified name or local name
|
|
294
|
+
* @param operation - The public operation name, used for the
|
|
295
|
+
* {@link InvalidStateError} message when the fleet is uninitialized
|
|
296
|
+
* @throws {InvalidStateError} If the fleet manager is not yet initialized
|
|
297
|
+
* @throws {AgentNotFoundError} If no agent with that name exists
|
|
298
|
+
*/
|
|
299
|
+
private resolveAgentForSessions;
|
|
136
300
|
private initializeStateDir;
|
|
137
301
|
private startSchedulerAsync;
|
|
138
302
|
private handleScheduleTrigger;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fleet-manager.d.ts","sourceRoot":"","sources":["../../src/fleet-manager/fleet-manager.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"fleet-manager.d.ts","sourceRoot":"","sources":["../../src/fleet-manager/fleet-manager.ts"],"names":[],"mappings":"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"}
|
|
@@ -11,19 +11,23 @@
|
|
|
11
11
|
* @module fleet-manager
|
|
12
12
|
*/
|
|
13
13
|
import { EventEmitter } from "node:events";
|
|
14
|
+
import { rm } from "node:fs/promises";
|
|
14
15
|
import { resolve } from "node:path";
|
|
15
16
|
import { ConfigError, ConfigNotFoundError, loadConfig, } from "../config/index.js";
|
|
17
|
+
import { getCliSessionFile, getDockerSessionFile } from "../runner/runtime/cli-session-path.js";
|
|
16
18
|
import { Scheduler } from "../scheduler/index.js";
|
|
17
|
-
import { initStateDirectory } from "../state/index.js";
|
|
19
|
+
import { initStateDirectory, SessionDiscoveryService, SessionMetadataStore, } from "../state/index.js";
|
|
18
20
|
import { createLogger } from "../utils/logger.js";
|
|
21
|
+
import { AgentManagement } from "./agent-management.js";
|
|
19
22
|
import { ConfigReload, computeConfigChanges } from "./config-reload.js";
|
|
20
|
-
import { ConfigurationError, FleetManagerShutdownError, FleetManagerStateDirError, InvalidStateError, } from "./errors.js";
|
|
23
|
+
import { AgentNotFoundError, ConfigurationError, FleetManagerShutdownError, FleetManagerStateDirError, InvalidStateError, } from "./errors.js";
|
|
21
24
|
import { JobControl } from "./job-control.js";
|
|
22
25
|
import { LogStreaming } from "./log-streaming.js";
|
|
23
26
|
import { ScheduleExecutor } from "./schedule-executor.js";
|
|
24
27
|
import { ScheduleManagement } from "./schedule-management.js";
|
|
25
28
|
// Module classes
|
|
26
29
|
import { StatusQueries } from "./status-queries.js";
|
|
30
|
+
import { resolveWorkingDirectory } from "./working-directory-helper.js";
|
|
27
31
|
const DEFAULT_CHECK_INTERVAL = 1000;
|
|
28
32
|
function createDefaultLogger() {
|
|
29
33
|
return createLogger("fleet-manager");
|
|
@@ -55,9 +59,16 @@ export class FleetManager extends EventEmitter {
|
|
|
55
59
|
statusQueries;
|
|
56
60
|
scheduleManagement;
|
|
57
61
|
configReloadModule;
|
|
62
|
+
agentManagement;
|
|
58
63
|
jobControl;
|
|
59
64
|
logStreaming;
|
|
60
65
|
scheduleExecutor;
|
|
66
|
+
// Lazily-created session discovery service (for getAgentSessions/* helpers)
|
|
67
|
+
sessionDiscovery = null;
|
|
68
|
+
// Lazily-created session metadata store, shared with sessionDiscovery so a
|
|
69
|
+
// setSessionName() write is reflected by a subsequent getAgentSessions()
|
|
70
|
+
// without a stale in-memory cache.
|
|
71
|
+
sessionMetadataStore = null;
|
|
61
72
|
// Chat managers (Discord, Slack, etc.)
|
|
62
73
|
// Key is platform name (e.g., "discord", "slack")
|
|
63
74
|
chatManagers = new Map();
|
|
@@ -385,6 +396,218 @@ export class FleetManager extends EventEmitter {
|
|
|
385
396
|
computeConfigChanges(oldConfig, newConfig) {
|
|
386
397
|
return computeConfigChanges(oldConfig, newConfig);
|
|
387
398
|
}
|
|
399
|
+
// Programmatic Agent Management
|
|
400
|
+
/**
|
|
401
|
+
* Register an agent at runtime without writing YAML or calling `reload()`.
|
|
402
|
+
*
|
|
403
|
+
* The config is validated, merged with fleet defaults, normalized (working
|
|
404
|
+
* directory resolved to an absolute path), and wired into the running fleet
|
|
405
|
+
* so it is immediately triggerable and appears in fleet status. A
|
|
406
|
+
* `config:reloaded` event is emitted describing the change.
|
|
407
|
+
*
|
|
408
|
+
* This is the programmatic counterpart to editing `herdctl.yaml` and calling
|
|
409
|
+
* `reload()` — useful for apps that manage agents in memory rather than on
|
|
410
|
+
* disk.
|
|
411
|
+
*
|
|
412
|
+
* @param agent - The agent configuration to register (must include `name`)
|
|
413
|
+
* @param options - Resolution options (base dir, defaults merge, replace)
|
|
414
|
+
* @returns Info for the newly registered agent
|
|
415
|
+
* @throws {InvalidStateError} If the fleet manager is not yet initialized
|
|
416
|
+
* @throws {ConfigurationError} If validation fails or the name collides
|
|
417
|
+
*
|
|
418
|
+
* @example
|
|
419
|
+
* ```typescript
|
|
420
|
+
* await fleet.addAgent({
|
|
421
|
+
* name: "keeper-myproject",
|
|
422
|
+
* working_directory: "/abs/projects/myproject",
|
|
423
|
+
* runtime: "cli",
|
|
424
|
+
* permission_mode: "acceptEdits",
|
|
425
|
+
* });
|
|
426
|
+
* await fleet.trigger("keeper-myproject", undefined, { prompt: "Hello" });
|
|
427
|
+
* ```
|
|
428
|
+
*/
|
|
429
|
+
async addAgent(agent, options) {
|
|
430
|
+
return this.agentManagement.addAgent(agent, options);
|
|
431
|
+
}
|
|
432
|
+
/**
|
|
433
|
+
* Unregister an agent at runtime.
|
|
434
|
+
*
|
|
435
|
+
* Removes the agent from the in-memory config and the scheduler. Accepts a
|
|
436
|
+
* qualified name or a local name. Running jobs are unaffected.
|
|
437
|
+
*
|
|
438
|
+
* @param name - The agent qualified name or local name to remove
|
|
439
|
+
* @returns `true` if an agent was removed, `false` if no match was found
|
|
440
|
+
* @throws {InvalidStateError} If the fleet manager is not yet initialized
|
|
441
|
+
*/
|
|
442
|
+
async removeAgent(name) {
|
|
443
|
+
return this.agentManagement.removeAgent(name);
|
|
444
|
+
}
|
|
445
|
+
// Session Access (convenience wrappers over SessionDiscoveryService)
|
|
446
|
+
/**
|
|
447
|
+
* List discovered Claude Code sessions for an agent.
|
|
448
|
+
*
|
|
449
|
+
* Derives the agent's working directory and Docker mode from the loaded
|
|
450
|
+
* config, so consumers don't have to map agent → working directory by hand.
|
|
451
|
+
* Sessions are returned sorted by modification time (newest first).
|
|
452
|
+
*
|
|
453
|
+
* Note: sessions are keyed by working directory. This method uses the agent's
|
|
454
|
+
* *configured* `working_directory`. If you triggered the agent with a
|
|
455
|
+
* per-trigger `workingDirectory` override (see {@link TriggerOptions}), the
|
|
456
|
+
* resulting sessions live under that override directory and will NOT appear
|
|
457
|
+
* here — list them by scanning that directory instead (e.g. the directory
|
|
458
|
+
* grouped `getAllSessions` view on the underlying `SessionDiscoveryService`).
|
|
459
|
+
*
|
|
460
|
+
* @param name - The agent qualified name or local name
|
|
461
|
+
* @param options - Optional `limit` for top-N enrichment
|
|
462
|
+
* @returns Array of discovered sessions (empty if the agent has no working
|
|
463
|
+
* directory or no sessions yet)
|
|
464
|
+
* @throws {InvalidStateError} If the fleet manager is not yet initialized
|
|
465
|
+
* @throws {AgentNotFoundError} If no agent with that name exists
|
|
466
|
+
*/
|
|
467
|
+
async getAgentSessions(name, options) {
|
|
468
|
+
const { agent, workingDirectory, dockerEnabled } = this.resolveAgentForSessions(name, "getAgentSessions");
|
|
469
|
+
if (!workingDirectory) {
|
|
470
|
+
return [];
|
|
471
|
+
}
|
|
472
|
+
return this.getSessionDiscovery().getAgentSessions(agent.qualifiedName, workingDirectory, dockerEnabled, options);
|
|
473
|
+
}
|
|
474
|
+
/**
|
|
475
|
+
* Get the parsed chat messages for one of an agent's sessions.
|
|
476
|
+
*
|
|
477
|
+
* Derives the working directory and Docker mode from the loaded config.
|
|
478
|
+
*
|
|
479
|
+
* @param name - The agent qualified name or local name
|
|
480
|
+
* @param sessionId - The session ID to read
|
|
481
|
+
* @returns Array of chat messages (empty if the agent has no working directory)
|
|
482
|
+
* @throws {InvalidStateError} If the fleet manager is not yet initialized
|
|
483
|
+
* @throws {AgentNotFoundError} If no agent with that name exists
|
|
484
|
+
*/
|
|
485
|
+
async getAgentSessionMessages(name, sessionId) {
|
|
486
|
+
const { workingDirectory, dockerEnabled } = this.resolveAgentForSessions(name, "getAgentSessionMessages");
|
|
487
|
+
if (!workingDirectory) {
|
|
488
|
+
return [];
|
|
489
|
+
}
|
|
490
|
+
return this.getSessionDiscovery().getSessionMessages(workingDirectory, sessionId, {
|
|
491
|
+
dockerEnabled,
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
/**
|
|
495
|
+
* Delete one of an agent's Claude Code session transcripts from disk.
|
|
496
|
+
*
|
|
497
|
+
* Resolves the agent's working directory and Docker mode from the loaded
|
|
498
|
+
* config, computes the CLI (or Docker) transcript file path with the same
|
|
499
|
+
* encoder Claude Code uses, deletes it, and invalidates the session-discovery
|
|
500
|
+
* cache so a subsequent {@link getAgentSessions} no longer lists it.
|
|
501
|
+
*
|
|
502
|
+
* The `sessionId` is validated (only `[A-Za-z0-9-]` is allowed) to prevent
|
|
503
|
+
* path traversal before any filesystem access.
|
|
504
|
+
*
|
|
505
|
+
* @param name - The agent qualified name or local name
|
|
506
|
+
* @param sessionId - The session ID whose transcript should be removed
|
|
507
|
+
* @returns `true` if a file was removed, `false` if no transcript existed (or
|
|
508
|
+
* the agent has no working directory)
|
|
509
|
+
* @throws {InvalidStateError} If the fleet manager is not yet initialized
|
|
510
|
+
* @throws {AgentNotFoundError} If no agent with that name exists
|
|
511
|
+
* @throws {Error} If `sessionId` contains invalid characters
|
|
512
|
+
*/
|
|
513
|
+
async deleteSession(name, sessionId) {
|
|
514
|
+
const { workingDirectory, dockerEnabled } = this.resolveAgentForSessions(name, "deleteSession");
|
|
515
|
+
if (!workingDirectory) {
|
|
516
|
+
return false;
|
|
517
|
+
}
|
|
518
|
+
// Compute the transcript path. getCliSessionFile/getDockerSessionFile both
|
|
519
|
+
// reject a sessionId containing anything other than [A-Za-z0-9-], so this
|
|
520
|
+
// throws before touching the filesystem when traversal is attempted.
|
|
521
|
+
const sessionFile = dockerEnabled
|
|
522
|
+
? getDockerSessionFile(this.stateDir, sessionId)
|
|
523
|
+
: getCliSessionFile(workingDirectory, sessionId);
|
|
524
|
+
let removed;
|
|
525
|
+
try {
|
|
526
|
+
await rm(sessionFile);
|
|
527
|
+
removed = true;
|
|
528
|
+
}
|
|
529
|
+
catch (error) {
|
|
530
|
+
if (error.code === "ENOENT") {
|
|
531
|
+
// No transcript on disk — nothing to remove.
|
|
532
|
+
removed = false;
|
|
533
|
+
}
|
|
534
|
+
else {
|
|
535
|
+
throw error;
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
// Invalidate the discovery cache so the deleted session disappears from
|
|
539
|
+
// subsequent listings even within the cache TTL window.
|
|
540
|
+
this.getSessionDiscovery().invalidateCache(workingDirectory, { dockerEnabled });
|
|
541
|
+
this.logger.debug(`deleteSession: ${removed ? "removed" : "no file for"} session "${sessionId}" of agent "${name}"`);
|
|
542
|
+
return removed;
|
|
543
|
+
}
|
|
544
|
+
/**
|
|
545
|
+
* Set (or clear) the custom display name for one of an agent's sessions.
|
|
546
|
+
*
|
|
547
|
+
* Writes through this fleet's shared {@link SessionMetadataStore} — the same
|
|
548
|
+
* store the session-discovery service reads — so a subsequent
|
|
549
|
+
* {@link getAgentSessions} reflects the new `customName` immediately. Passing
|
|
550
|
+
* `null` or an empty/whitespace string clears any existing custom name.
|
|
551
|
+
*
|
|
552
|
+
* @param name - The agent qualified name or local name
|
|
553
|
+
* @param sessionId - The session ID to (re)name
|
|
554
|
+
* @param customName - The custom name to set, or `null`/empty to clear it
|
|
555
|
+
* @throws {InvalidStateError} If the fleet manager is not yet initialized
|
|
556
|
+
* @throws {AgentNotFoundError} If no agent with that name exists
|
|
557
|
+
*/
|
|
558
|
+
async setSessionName(name, sessionId, customName) {
|
|
559
|
+
// Resolve to validate state + agent existence and to key metadata by the
|
|
560
|
+
// agent's qualified name (consistent with how discovery stores custom names).
|
|
561
|
+
const { agent } = this.resolveAgentForSessions(name, "setSessionName");
|
|
562
|
+
const store = this.getSessionMetadataStore();
|
|
563
|
+
const trimmed = customName?.trim();
|
|
564
|
+
if (trimmed) {
|
|
565
|
+
await store.setCustomName(agent.qualifiedName, sessionId, trimmed);
|
|
566
|
+
this.logger.debug(`setSessionName: set custom name for session "${sessionId}" of agent "${agent.qualifiedName}"`);
|
|
567
|
+
}
|
|
568
|
+
else {
|
|
569
|
+
await store.removeCustomName(agent.qualifiedName, sessionId);
|
|
570
|
+
this.logger.debug(`setSessionName: cleared custom name for session "${sessionId}" of agent "${agent.qualifiedName}"`);
|
|
571
|
+
}
|
|
572
|
+
// Custom names are read live (not part of the directory listing cache), and
|
|
573
|
+
// the metadata store is shared with discovery, so the change is already
|
|
574
|
+
// visible. Invalidate the directory listing too for safety/consistency.
|
|
575
|
+
const workingDirectory = resolveWorkingDirectory(agent);
|
|
576
|
+
if (workingDirectory) {
|
|
577
|
+
this.getSessionDiscovery().invalidateCache(workingDirectory, {
|
|
578
|
+
dockerEnabled: agent.docker?.enabled === true,
|
|
579
|
+
});
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
/**
|
|
583
|
+
* Drop the cached session listing for an agent so the next
|
|
584
|
+
* {@link getAgentSessions} call rebuilds it from disk.
|
|
585
|
+
*
|
|
586
|
+
* The underlying {@link SessionDiscoveryService} caches each working
|
|
587
|
+
* directory's listing for up to its TTL (default 30s). That cache is now
|
|
588
|
+
* mtime-aware, so a *newly created* transcript file is normally picked up
|
|
589
|
+
* immediately. Call this when you want to force a fresh listing regardless —
|
|
590
|
+
* e.g. after each chat turn, or on filesystems whose directory mtime has
|
|
591
|
+
* coarse (1-second) granularity where a same-second create might otherwise be
|
|
592
|
+
* masked until the TTL expires. It also drops the attribution index so a
|
|
593
|
+
* session created this turn (whose job record was just written) is attributed.
|
|
594
|
+
*
|
|
595
|
+
* Resolves the agent's working directory and Docker mode from the loaded
|
|
596
|
+
* config (no override directories — see {@link getAgentSessions}). A no-op
|
|
597
|
+
* when the agent has no working directory.
|
|
598
|
+
*
|
|
599
|
+
* @param name - The agent qualified name or local name
|
|
600
|
+
* @throws {InvalidStateError} If the fleet manager is not yet initialized
|
|
601
|
+
* @throws {AgentNotFoundError} If no agent with that name exists
|
|
602
|
+
*/
|
|
603
|
+
invalidateSessions(name) {
|
|
604
|
+
const { workingDirectory, dockerEnabled } = this.resolveAgentForSessions(name, "invalidateSessions");
|
|
605
|
+
if (!workingDirectory) {
|
|
606
|
+
return;
|
|
607
|
+
}
|
|
608
|
+
this.getSessionDiscovery().invalidateWorkingDirectory(workingDirectory, { dockerEnabled });
|
|
609
|
+
this.logger.debug(`invalidateSessions: cleared session cache for agent "${name}"`);
|
|
610
|
+
}
|
|
388
611
|
// Job Control
|
|
389
612
|
async trigger(agentName, scheduleName, options) {
|
|
390
613
|
return this.jobControl.trigger(agentName, scheduleName, options);
|
|
@@ -417,6 +640,9 @@ export class FleetManager extends EventEmitter {
|
|
|
417
640
|
this.configReloadModule = new ConfigReload(this, () => this.loadConfiguration(), (config) => {
|
|
418
641
|
this.config = config;
|
|
419
642
|
});
|
|
643
|
+
this.agentManagement = new AgentManagement(this, (config) => {
|
|
644
|
+
this.config = config;
|
|
645
|
+
}, (name) => this.statusQueries.getAgentInfoByName(name));
|
|
420
646
|
this.jobControl = new JobControl(this, () => this.statusQueries.getAgentInfo());
|
|
421
647
|
this.logStreaming = new LogStreaming(this);
|
|
422
648
|
this.scheduleExecutor = new ScheduleExecutor(this);
|
|
@@ -576,6 +802,67 @@ export class FleetManager extends EventEmitter {
|
|
|
576
802
|
throw new ConfigurationError(`Duplicate agent qualified names found: ${duplicateList}. Agent names must be unique within a fleet.`);
|
|
577
803
|
}
|
|
578
804
|
}
|
|
805
|
+
/**
|
|
806
|
+
* Get (lazily creating) the shared SessionMetadataStore bound to this fleet's
|
|
807
|
+
* state directory. Shared with the SessionDiscoveryService so metadata writes
|
|
808
|
+
* (e.g. {@link setSessionName}) and reads (during discovery) use one cache.
|
|
809
|
+
*/
|
|
810
|
+
getSessionMetadataStore() {
|
|
811
|
+
if (!this.sessionMetadataStore) {
|
|
812
|
+
this.sessionMetadataStore = new SessionMetadataStore(this.stateDir);
|
|
813
|
+
}
|
|
814
|
+
return this.sessionMetadataStore;
|
|
815
|
+
}
|
|
816
|
+
/**
|
|
817
|
+
* Get (lazily creating) the SessionDiscoveryService bound to this fleet's
|
|
818
|
+
* state directory. Reused across calls so its caches are shared. The
|
|
819
|
+
* service shares this fleet's {@link SessionMetadataStore} so custom-name
|
|
820
|
+
* writes are immediately visible to discovery.
|
|
821
|
+
*/
|
|
822
|
+
getSessionDiscovery() {
|
|
823
|
+
if (!this.sessionDiscovery) {
|
|
824
|
+
this.sessionDiscovery = new SessionDiscoveryService({
|
|
825
|
+
stateDir: this.stateDir,
|
|
826
|
+
sessionMetadataStore: this.getSessionMetadataStore(),
|
|
827
|
+
});
|
|
828
|
+
}
|
|
829
|
+
return this.sessionDiscovery;
|
|
830
|
+
}
|
|
831
|
+
/**
|
|
832
|
+
* Look up an agent by qualified or local name and derive the inputs the
|
|
833
|
+
* SessionDiscoveryService needs (working directory + Docker mode).
|
|
834
|
+
*
|
|
835
|
+
* @param name - The agent qualified name or local name
|
|
836
|
+
* @param operation - The public operation name, used for the
|
|
837
|
+
* {@link InvalidStateError} message when the fleet is uninitialized
|
|
838
|
+
* @throws {InvalidStateError} If the fleet manager is not yet initialized
|
|
839
|
+
* @throws {AgentNotFoundError} If no agent with that name exists
|
|
840
|
+
*/
|
|
841
|
+
resolveAgentForSessions(name, operation) {
|
|
842
|
+
// Guard against pre-init calls. Without this, an uninitialized fleet has a
|
|
843
|
+
// null config, so the agent lookup below would throw AgentNotFoundError —
|
|
844
|
+
// masking the real cause and contradicting the documented behavior (these
|
|
845
|
+
// helpers throw InvalidStateError before initialize()).
|
|
846
|
+
if (this.status === "uninitialized" || !this.config) {
|
|
847
|
+
throw new InvalidStateError(operation, this.status, [
|
|
848
|
+
"initialized",
|
|
849
|
+
"starting",
|
|
850
|
+
"running",
|
|
851
|
+
"stopping",
|
|
852
|
+
"stopped",
|
|
853
|
+
]);
|
|
854
|
+
}
|
|
855
|
+
const agents = this.config.agents;
|
|
856
|
+
const agent = agents.find((a) => a.qualifiedName === name) ?? agents.find((a) => a.name === name);
|
|
857
|
+
if (!agent) {
|
|
858
|
+
throw new AgentNotFoundError(name);
|
|
859
|
+
}
|
|
860
|
+
return {
|
|
861
|
+
agent,
|
|
862
|
+
workingDirectory: resolveWorkingDirectory(agent),
|
|
863
|
+
dockerEnabled: agent.docker?.enabled === true,
|
|
864
|
+
};
|
|
865
|
+
}
|
|
579
866
|
async initializeStateDir() {
|
|
580
867
|
try {
|
|
581
868
|
return await initStateDirectory({ path: this.stateDir });
|