Skip to content
Open
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
18 changes: 12 additions & 6 deletions src/browser/components/AutomationModal/AutomationModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@ import {
workflowScheduleIntervalMinutesToMs,
} from "@/browser/utils/workflowScheduleIntervalMinutes";

// Fallback messages shown when the save/remove RPC fails without a specific
// error string. Deduplicated so the wording stays consistent across the
// pre-check, RPC-result, and thrown-error paths below.
const SAVE_AUTOMATION_ERROR_MESSAGE = "Failed to save automation.";
const REMOVE_AUTOMATION_ERROR_MESSAGE = "Failed to remove automation.";

interface AutomationModalProps {
open: boolean;
projectPath: string;
Expand Down Expand Up @@ -308,7 +314,7 @@ export function AutomationModal(props: AutomationModalProps) {
schedule: workspaceSchedule,
});
if (!result.success) {
setSaveError(result.error ?? "Failed to save automation.");
setSaveError(result.error ?? SAVE_AUTOMATION_ERROR_MESSAGE);
return false;
}
} else {
Expand All @@ -327,15 +333,15 @@ export function AutomationModal(props: AutomationModalProps) {
},
});
if (!result.success) {
setSaveError(result.error ?? "Failed to save automation.");
setSaveError(result.error ?? SAVE_AUTOMATION_ERROR_MESSAGE);
return false;
}
}

await refreshProjects();
return true;
} catch (error) {
setSaveError(getErrorMessage(error) || "Failed to save automation.");
setSaveError(getErrorMessage(error) || SAVE_AUTOMATION_ERROR_MESSAGE);
return false;
} finally {
setIsSaving(false);
Expand Down Expand Up @@ -388,21 +394,21 @@ export function AutomationModal(props: AutomationModalProps) {
scheduleId: props.projectWorkflowSchedule.id,
});
if (!result.success) {
throw new Error(result.error ?? "Failed to remove automation.");
throw new Error(result.error ?? REMOVE_AUTOMATION_ERROR_MESSAGE);
}
} else if (isLegacyWorkspaceSchedule) {
const result = await api.workspace.setWorkflowSchedule({
workspaceId: props.workspaceId,
schedule: null,
});
if (!result.success) {
throw new Error(result.error ?? "Failed to remove automation.");
throw new Error(result.error ?? REMOVE_AUTOMATION_ERROR_MESSAGE);
}
}
await refreshProjects();
props.onOpenChange(false);
} catch (error) {
setSaveError(getErrorMessage(error) || "Failed to remove automation.");
setSaveError(getErrorMessage(error) || REMOVE_AUTOMATION_ERROR_MESSAGE);
} finally {
setIsSaving(false);
}
Expand Down
7 changes: 5 additions & 2 deletions src/node/services/analytics/etl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,12 @@ import { getErrorMessage } from "@/common/utils/errors";
import { createDisplayUsage } from "@/common/utils/tokens/displayUsage";
import { log } from "@/node/services/log";
import { toUtcDateString } from "@/node/services/analytics/dateUtils";
import { CHAT_ARCHIVE_FILE_NAME } from "@/common/constants/paths";
import { CHAT_FILE_NAME, CHAT_ARCHIVE_FILE_NAME } from "@/common/constants/paths";

export const CHAT_FILE_NAME = "chat.jsonl";
// Re-export the canonical chat history filename (defined in constants/paths.ts)
// so existing analytics consumers (workspaceDiscovery, tests) can keep importing
// it from this module without duplicating the "chat.jsonl" literal.
export { CHAT_FILE_NAME };

/**
* Sealed pre-boundary history rotates from chat.jsonl into chat-archive.jsonl
Expand Down
21 changes: 14 additions & 7 deletions src/node/services/memoryConsolidationService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -177,13 +177,22 @@ function findNewestWorkspaceRecord(
);
}

/**
* Effective ordering timestamp for a harvest record: when it completed, or when
* it started if still pending. findNewestHarvestRecord and pruneHarvestRecords
* must rank records the same way, so both derive recency from this single key.
*/
function harvestRecordTime(record: MemoryHarvestRecord): number {
return record.completedAt ?? record.startedAt;
}

function findNewestHarvestRecord(
records: Record<string, MemoryHarvestRecord> | undefined
): MemoryHarvestRecord | null {
if (records === undefined) return null;
return Object.values(records).reduce<MemoryHarvestRecord | null>((latest, record) => {
const recordTime = record.completedAt ?? record.startedAt;
const latestTime = latest === null ? -1 : (latest.completedAt ?? latest.startedAt);
const recordTime = harvestRecordTime(record);
const latestTime = latest === null ? -1 : harvestRecordTime(latest);
return recordTime > latestTime ? record : latest;
}, null);
}
Expand Down Expand Up @@ -231,11 +240,9 @@ function parseHarvestRecords(value: unknown): Record<string, Record<string, Memo
}

function pruneHarvestRecords(records: Record<string, MemoryHarvestRecord>): void {
const ranked = Object.entries(records).sort(([, left], [, right]) => {
const leftTime = left.completedAt ?? left.startedAt;
const rightTime = right.completedAt ?? right.startedAt;
return rightTime - leftTime;
});
const ranked = Object.entries(records).sort(
([, left], [, right]) => harvestRecordTime(right) - harvestRecordTime(left)
);
for (const [boundaryKey] of ranked.slice(HARVEST_RECORD_RETENTION)) {
delete records[boundaryKey];
}
Expand Down
3 changes: 2 additions & 1 deletion src/node/services/workflows/WorkflowRunner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import assert from "@/common/utils/assert";
import { getErrorMessage } from "@/common/utils/errors";
import { validateJsonSchemaSubset } from "@/common/utils/jsonSchemaSubset";
import type { IJSRuntime, IJSRuntimeFactory } from "@/node/services/ptc/runtime";
import { isWorkflowRunTaskId } from "@/node/services/tools/taskId";
import { AsyncMutex } from "@/node/utils/concurrency/asyncMutex";
import { AsyncSemaphore } from "@/node/utils/concurrency/asyncSemaphore";
import type { ResolvedWorkflowAction, WorkflowActionRegistry } from "./WorkflowActionRegistry";
Expand Down Expand Up @@ -857,7 +858,7 @@ export class WorkflowRunner {
const run = await this.runStore.getRun(runId);
const driftedStep = run.steps.find(
(step) =>
step.stepId === spec.id && step.inputHash !== inputHash && step.taskId?.startsWith("wfr_")
step.stepId === spec.id && step.inputHash !== inputHash && isWorkflowRunTaskId(step.taskId)
);
if (driftedStep != null) {
throw new Error(
Expand Down
Loading