Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 24 additions & 5 deletions src/agent/delegation/delegation-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,10 @@ export class DelegationManager {
private runningTasks = new Map<string, Set<string>>(); // agent ID -> set of task IDs
private maxRetries: number;
private defaultTimeoutMs: number;
private onStateChangeCallback?: (taskId: string, state: DelegationState) => void;
private onStateChangeCallback?: (
taskId: string,
state: DelegationState,
) => void;
private onProgressCallback?: (event: DelegationProgressEvent) => void;

constructor(config: DelegationManagerConfig = {}) {
Expand Down Expand Up @@ -94,7 +97,11 @@ export class DelegationManager {

// Emit initial state change
this.setState(task.id, "pending");
this.taskLog(task.id, "info", `Task ${task.id} created: ${task.description}`);
this.taskLog(
task.id,
"info",
`Task ${task.id} created: ${task.description}`,
);

let lastError: Error | undefined;
const maxAttempts = this.maxRetries + 1;
Expand All @@ -121,7 +128,11 @@ export class DelegationManager {
taskStatus.attempts = attempt;
this.setState(task.id, "running");

this.taskLog(task.id, "info", `Task assigned to ${agent.name} (${agent.id})`);
this.taskLog(
task.id,
"info",
`Task assigned to ${agent.name} (${agent.id})`,
);

// Emit agent assigned event
this.emitProgress({
Expand Down Expand Up @@ -186,7 +197,11 @@ export class DelegationManager {
runningSet.delete(task.id);

lastError = error as Error;
this.taskLog(task.id, "warn", `Attempt ${attempt} failed: ${(error as Error).message}`);
this.taskLog(
task.id,
"warn",
`Attempt ${attempt} failed: ${(error as Error).message}`,
);

// Emit retry event if we'll retry
if (attempt < maxAttempts) {
Expand All @@ -205,7 +220,11 @@ export class DelegationManager {
}
} catch (error) {
lastError = error as Error;
this.taskLog(task.id, "error", `Attempt ${attempt} error: ${(error as Error).message}`);
this.taskLog(
task.id,
"error",
`Attempt ${attempt} error: ${(error as Error).message}`,
);
}
}

Expand Down
10 changes: 7 additions & 3 deletions src/checkpoints/checkpoint-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,11 @@ export class CheckpointStore {
// Convert Uint8Array to base64 strings for JSON serialization
const serialized = {
...checkpoint,
snapshots: checkpoint.snapshots.map(snap => ({
snapshots: checkpoint.snapshots.map((snap) => ({
filePath: snap.filePath,
content: snap.content ? Buffer.from(snap.content).toString('base64') : null,
content: snap.content
? Buffer.from(snap.content).toString("base64")
: null,
existed: snap.existed,
})),
};
Expand Down Expand Up @@ -63,7 +65,9 @@ export class CheckpointStore {
timestamp: parsed.timestamp,
snapshots: parsed.snapshots.map((snap: any) => ({
filePath: snap.filePath,
content: snap.content ? new Uint8Array(Buffer.from(snap.content, 'base64')) : null,
content: snap.content
? new Uint8Array(Buffer.from(snap.content, "base64"))
: null,
existed: snap.existed,
})),
};
Expand Down
74 changes: 59 additions & 15 deletions src/config/config-loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -886,7 +886,9 @@ export class ConfigLoader {

// Required fields
if (typeof target.host !== "string" || !target.host.trim()) {
errors.push(`ssh.targets[${idx}].host must be a non-empty string`);
errors.push(
`ssh.targets[${idx}].host must be a non-empty string`,
);
continue;
}
if (
Expand All @@ -910,7 +912,10 @@ export class ConfigLoader {

// Validate authMethod-specific requirements
if (target.authMethod === "key") {
if (typeof target.keyPath !== "string" || !target.keyPath.trim()) {
if (
typeof target.keyPath !== "string" ||
!target.keyPath.trim()
) {
errors.push(
`ssh.targets[${idx}].keyPath is required when authMethod is "key"`,
);
Expand Down Expand Up @@ -941,7 +946,11 @@ export class ConfigLoader {
// Optional port
let port = 22;
if ("port" in target) {
if (typeof target.port !== "number" || target.port < 1 || target.port > 65535) {
if (
typeof target.port !== "number" ||
target.port < 1 ||
target.port > 65535
) {
errors.push(
`ssh.targets[${idx}].port must be a number between 1 and 65535`,
);
Expand All @@ -951,9 +960,11 @@ export class ConfigLoader {
}

// Name
let name = target.name;
const name = target.name;
if (typeof name !== "string" || !name.trim()) {
errors.push(`ssh.targets[${idx}].name must be a non-empty string`);
errors.push(
`ssh.targets[${idx}].name must be a non-empty string`,
);
continue;
}

Expand All @@ -974,7 +985,12 @@ export class ConfigLoader {
if ("certPath" in target && typeof target.certPath === "string") {
sshTarget.certPath = target.certPath;
}
if ("options" in target && typeof target.options === "object" && target.options !== null && !Array.isArray(target.options)) {
if (
"options" in target &&
typeof target.options === "object" &&
target.options !== null &&
!Array.isArray(target.options)
) {
sshTarget.options = target.options as Record<string, string>;
}

Expand All @@ -989,7 +1005,11 @@ export class ConfigLoader {
errors.push("ssh.trustedHosts must be an array");
} else {
out.trustedHosts = [];
for (let idx = 0; idx < (s.trustedHosts as unknown[]).length; idx++) {
for (
let idx = 0;
idx < (s.trustedHosts as unknown[]).length;
idx++
) {
const h = (s.trustedHosts as unknown[])[idx];
if (typeof h !== "object" || h === null || Array.isArray(h)) {
errors.push(`ssh.trustedHosts[${idx}] must be an object`);
Expand All @@ -1003,13 +1023,20 @@ export class ConfigLoader {
);
continue;
}
if (typeof host.port !== "number" || host.port < 1 || host.port > 65535) {
if (
typeof host.port !== "number" ||
host.port < 1 ||
host.port > 65535
) {
errors.push(
`ssh.trustedHosts[${idx}].port must be a number between 1 and 65535`,
);
continue;
}
if (typeof host.fingerprint !== "string" || !host.fingerprint.trim()) {
if (
typeof host.fingerprint !== "string" ||
!host.fingerprint.trim()
) {
errors.push(
`ssh.trustedHosts[${idx}].fingerprint must be a non-empty string`,
);
Expand Down Expand Up @@ -1042,8 +1069,14 @@ export class ConfigLoader {
const def: SSHDefaults = {};

if ("port" in defaults) {
if (typeof defaults.port !== "number" || defaults.port < 1 || defaults.port > 65535) {
errors.push("ssh.defaults.port must be a number between 1 and 65535");
if (
typeof defaults.port !== "number" ||
defaults.port < 1 ||
defaults.port > 65535
) {
errors.push(
"ssh.defaults.port must be a number between 1 and 65535",
);
} else {
def.port = defaults.port;
}
Expand All @@ -1056,21 +1089,32 @@ export class ConfigLoader {
}
}
if ("connectTimeoutMs" in defaults) {
if (typeof defaults.connectTimeoutMs !== "number" || defaults.connectTimeoutMs < 1) {
errors.push("ssh.defaults.connectTimeoutMs must be a number >= 1");
if (
typeof defaults.connectTimeoutMs !== "number" ||
defaults.connectTimeoutMs < 1
) {
errors.push(
"ssh.defaults.connectTimeoutMs must be a number >= 1",
);
} else {
def.connectTimeoutMs = defaults.connectTimeoutMs;
}
}
if ("retryCount" in defaults) {
if (typeof defaults.retryCount !== "number" || defaults.retryCount < 1) {
if (
typeof defaults.retryCount !== "number" ||
defaults.retryCount < 1
) {
errors.push("ssh.defaults.retryCount must be a number >= 1");
} else {
def.retryCount = defaults.retryCount;
}
}
if ("retryDelayMs" in defaults) {
if (typeof defaults.retryDelayMs !== "number" || defaults.retryDelayMs < 1) {
if (
typeof defaults.retryDelayMs !== "number" ||
defaults.retryDelayMs < 1
) {
errors.push("ssh.defaults.retryDelayMs must be a number >= 1");
} else {
def.retryDelayMs = defaults.retryDelayMs;
Expand Down
Loading
Loading