@pellux/goodvibes-agent 1.1.7 → 1.2.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/CHANGELOG.md +170 -0
- package/README.md +46 -30
- package/dist/package/{ast-grep-napi.linux-x64-gnu-mkk8xwww.node → ast-grep-napi.linux-x64-gnu-swtppvy9.node} +0 -0
- package/dist/package/{ast-grep-napi.linux-x64-musl-ryqtgdv6.node → ast-grep-napi.linux-x64-musl-ttfcdtap.node} +0 -0
- package/dist/package/main.js +132577 -91483
- package/docs/README.md +14 -7
- package/docs/channels-remote-and-api.md +8 -5
- package/docs/connected-host.md +14 -12
- package/docs/getting-started.md +56 -30
- package/docs/knowledge-artifacts-and-multimodal.md +7 -5
- package/docs/project-planning.md +2 -2
- package/docs/providers-and-routing.md +11 -3
- package/docs/tools-and-commands.md +114 -45
- package/docs/voice-and-live-tts.md +3 -1
- package/package.json +3 -2
- package/release/release-notes.md +130 -5
- package/release/release-readiness.json +120 -90
- package/src/agent/assistant-cockpit.ts +247 -0
- package/src/agent/autonomy-schedule-format.ts +96 -0
- package/src/agent/autonomy-schedule.ts +514 -0
- package/src/agent/channel-delivery-receipts.ts +292 -0
- package/src/agent/competitive-feature-inventory.ts +296 -0
- package/src/agent/document-registry-types.ts +106 -0
- package/src/agent/document-registry.ts +734 -0
- package/src/agent/harness-control.ts +2 -2
- package/src/agent/memory-prompt.ts +2 -2
- package/src/agent/operator-actions.ts +50 -6
- package/src/agent/persona-registry.ts +8 -0
- package/src/agent/project-context-files.ts +273 -0
- package/src/agent/prompt-context-receipts.ts +502 -0
- package/src/agent/reminder-schedule-format.ts +16 -2
- package/src/agent/research-run-registry.ts +582 -0
- package/src/agent/research-source-registry.ts +441 -0
- package/src/agent/routine-registry.ts +19 -3
- package/src/agent/routine-schedule-format.ts +27 -2
- package/src/agent/runtime-profile-starters.ts +7 -0
- package/src/agent/runtime-profile.ts +106 -4
- package/src/agent/schedule-edit-format.ts +129 -0
- package/src/agent/schedule-edit.ts +496 -0
- package/src/agent/schedule-next-routes.ts +42 -0
- package/src/agent/setup-wizard-checkpoint.ts +140 -0
- package/src/agent/setup-wizard.ts +447 -0
- package/src/agent/skill-registry-types.ts +102 -0
- package/src/agent/skill-registry.ts +81 -107
- package/src/agent/vibe-confirmation-routes.ts +62 -0
- package/src/agent/vibe-file.ts +285 -0
- package/src/cli/agent-knowledge-command.ts +45 -2
- package/src/cli/help.ts +3 -0
- package/src/cli/profiles-command.ts +25 -11
- package/src/config/agent-settings-policy.ts +1 -18
- package/src/input/agent-workspace-activation.ts +53 -0
- package/src/input/agent-workspace-artifact-browser-editor.ts +494 -0
- package/src/input/agent-workspace-artifact-metadata.ts +17 -0
- package/src/input/agent-workspace-basic-command-editors.ts +5 -1
- package/src/input/agent-workspace-categories.ts +153 -155
- package/src/input/agent-workspace-channel-triage.ts +481 -0
- package/src/input/agent-workspace-channels.ts +263 -0
- package/src/input/agent-workspace-command-editor.ts +152 -0
- package/src/input/agent-workspace-context-snapshot.ts +349 -0
- package/src/input/agent-workspace-delegation-editor-submission.ts +8 -0
- package/src/input/agent-workspace-document-editor.ts +502 -0
- package/src/input/agent-workspace-document-ops-editor.ts +523 -0
- package/src/input/agent-workspace-host-category.ts +1 -1
- package/src/input/agent-workspace-local-library-snapshot.ts +167 -0
- package/src/input/agent-workspace-model-compare-editor.ts +376 -0
- package/src/input/agent-workspace-model-compare-prompt-submission.ts +552 -0
- package/src/input/agent-workspace-model-compare-types.ts +82 -0
- package/src/input/agent-workspace-model-compare-utils.ts +35 -0
- package/src/input/agent-workspace-onboarding-categories.ts +141 -0
- package/src/input/agent-workspace-operations-command-editor-submission.ts +53 -0
- package/src/input/agent-workspace-operations-command-editors.ts +22 -0
- package/src/input/agent-workspace-research-report-editor.ts +212 -0
- package/src/input/agent-workspace-research-run-editor.ts +144 -0
- package/src/input/agent-workspace-research-source-editor.ts +167 -0
- package/src/input/agent-workspace-review-packet-snapshot.ts +593 -0
- package/src/input/agent-workspace-review-packet-utils.ts +265 -0
- package/src/input/agent-workspace-settings.ts +262 -30
- package/src/input/agent-workspace-setup-checkpoint-action.ts +127 -0
- package/src/input/agent-workspace-setup-snapshot.ts +198 -0
- package/src/input/agent-workspace-setup.ts +34 -1
- package/src/input/agent-workspace-snapshot.ts +162 -147
- package/src/input/agent-workspace-types.ts +287 -2
- package/src/input/agent-workspace.ts +18 -0
- package/src/input/command-registry.ts +9 -2
- package/src/input/commands/agent-runtime-profile-runtime.ts +28 -13
- package/src/input/commands/channels-runtime.ts +102 -114
- package/src/input/commands/delegation-runtime.ts +87 -24
- package/src/input/commands/knowledge-browser-flags.ts +12 -0
- package/src/input/commands/knowledge.ts +28 -14
- package/src/input/commands/operator-actions-runtime.ts +6 -3
- package/src/input/commands/schedule-runtime.ts +71 -14
- package/src/input/commands/session-content.ts +2 -2
- package/src/input/commands/shell-core.ts +2 -0
- package/src/input/commands/vibe-runtime.ts +140 -0
- package/src/input/commands.ts +2 -0
- package/src/input/connected-host-routes.ts +123 -0
- package/src/input/settings-modal-types.ts +14 -0
- package/src/main.ts +15 -15
- package/src/panels/qr-panel.ts +2 -2
- package/src/renderer/agent-workspace-context-lines.ts +686 -0
- package/src/renderer/agent-workspace.ts +30 -419
- package/src/renderer/settings-modal-helpers.ts +12 -0
- package/src/renderer/settings-modal.ts +12 -0
- package/src/runtime/bootstrap-command-context.ts +11 -1
- package/src/runtime/bootstrap-command-parts.ts +14 -3
- package/src/runtime/bootstrap-core.ts +39 -1
- package/src/runtime/bootstrap-shell.ts +6 -0
- package/src/runtime/bootstrap.ts +113 -30
- package/src/runtime/connected-host-auth.ts +3 -2
- package/src/runtime/execution-ledger.ts +231 -0
- package/src/runtime/services.ts +88 -10
- package/src/runtime/tool-permission-safety.ts +199 -5
- package/src/shell/session-continuity-hints.ts +39 -0
- package/src/tools/agent-artifacts-tool.ts +606 -0
- package/src/tools/agent-audit-tool.ts +155 -0
- package/src/tools/agent-autonomy-schedule-tool.ts +238 -0
- package/src/tools/agent-autonomy-tool.ts +140 -0
- package/src/tools/agent-channel-send-tool.ts +24 -2
- package/src/tools/agent-channels-tool.ts +140 -0
- package/src/tools/agent-computer-tool.ts +168 -0
- package/src/tools/agent-context-policy.ts +137 -7
- package/src/tools/agent-context-tool.ts +143 -0
- package/src/tools/agent-delegation-tool.ts +128 -0
- package/src/tools/agent-device-tool.ts +240 -0
- package/src/tools/agent-documents-tool.ts +684 -0
- package/src/tools/agent-execution-tool.ts +230 -0
- package/src/tools/agent-harness-agent-orchestration.ts +703 -0
- package/src/tools/agent-harness-autonomy-intake.ts +785 -0
- package/src/tools/agent-harness-autonomy-live-records.ts +588 -0
- package/src/tools/agent-harness-autonomy-queue-types.ts +88 -0
- package/src/tools/agent-harness-autonomy-queue.ts +560 -0
- package/src/tools/agent-harness-background-processes-types.ts +35 -0
- package/src/tools/agent-harness-background-processes.ts +794 -0
- package/src/tools/agent-harness-browser-cockpit-route.ts +219 -0
- package/src/tools/agent-harness-browser-control.ts +338 -0
- package/src/tools/agent-harness-channel-metadata.ts +168 -10
- package/src/tools/agent-harness-cli-command-policy.ts +110 -0
- package/src/tools/agent-harness-command-catalog.ts +4 -2
- package/src/tools/agent-harness-command-runner.ts +42 -0
- package/src/tools/agent-harness-connected-host-status.ts +7 -4
- package/src/tools/agent-harness-delegation-posture.ts +234 -19
- package/src/tools/agent-harness-document-ops-reviewer-readiness.ts +234 -0
- package/src/tools/agent-harness-document-ops-types.ts +72 -0
- package/src/tools/agent-harness-document-ops.ts +671 -0
- package/src/tools/agent-harness-execution-history.ts +489 -0
- package/src/tools/agent-harness-execution-posture.ts +382 -0
- package/src/tools/agent-harness-file-recovery.ts +135 -0
- package/src/tools/agent-harness-keybinding-metadata.ts +16 -12
- package/src/tools/agent-harness-learning-curator-common.ts +102 -0
- package/src/tools/agent-harness-learning-curator-consolidation.ts +295 -0
- package/src/tools/agent-harness-learning-curator-proposals.ts +606 -0
- package/src/tools/agent-harness-learning-curator-types.ts +151 -0
- package/src/tools/agent-harness-learning-curator.ts +417 -0
- package/src/tools/agent-harness-local-model-benchmarks.ts +199 -0
- package/src/tools/agent-harness-local-model-cookbook.ts +238 -0
- package/src/tools/agent-harness-local-model-endpoints.ts +673 -0
- package/src/tools/agent-harness-media-posture.ts +208 -2
- package/src/tools/agent-harness-memory-posture.ts +556 -0
- package/src/tools/agent-harness-metadata.ts +100 -168
- package/src/tools/agent-harness-mode-catalog.ts +84 -33
- package/src/tools/agent-harness-model-catalog.ts +162 -0
- package/src/tools/agent-harness-model-provider-health.ts +186 -0
- package/src/tools/agent-harness-model-readiness.ts +406 -0
- package/src/tools/agent-harness-model-routing-types.ts +266 -0
- package/src/tools/agent-harness-model-routing-utils.ts +30 -0
- package/src/tools/agent-harness-model-routing.ts +137 -190
- package/src/tools/agent-harness-operator-methods.ts +115 -133
- package/src/tools/agent-harness-pairing-posture.ts +431 -18
- package/src/tools/agent-harness-personal-ops-discovery.ts +533 -0
- package/src/tools/agent-harness-personal-ops-intake.ts +385 -0
- package/src/tools/agent-harness-personal-ops-lanes.ts +204 -0
- package/src/tools/agent-harness-personal-ops-records.ts +732 -0
- package/src/tools/agent-harness-personal-ops-runner.ts +637 -0
- package/src/tools/agent-harness-personal-ops-types.ts +222 -0
- package/src/tools/agent-harness-personal-ops.ts +606 -0
- package/src/tools/agent-harness-project-context.ts +144 -0
- package/src/tools/agent-harness-prompt-context.ts +589 -0
- package/src/tools/agent-harness-provider-account-metadata.ts +2 -2
- package/src/tools/agent-harness-release-evidence.ts +9 -7
- package/src/tools/agent-harness-release-readiness.ts +9 -7
- package/src/tools/agent-harness-research-briefing.ts +427 -0
- package/src/tools/agent-harness-research-queue.ts +250 -0
- package/src/tools/agent-harness-research-runs.ts +295 -0
- package/src/tools/agent-harness-research-workflow.ts +322 -0
- package/src/tools/agent-harness-security-posture.ts +2 -2
- package/src/tools/agent-harness-service-posture.ts +9 -9
- package/src/tools/agent-harness-session-metadata.ts +3 -3
- package/src/tools/agent-harness-setup-connected-host.ts +501 -0
- package/src/tools/agent-harness-setup-describe.ts +557 -0
- package/src/tools/agent-harness-setup-handoffs.ts +502 -0
- package/src/tools/agent-harness-setup-model-helpers.ts +101 -0
- package/src/tools/agent-harness-setup-plan.ts +253 -0
- package/src/tools/agent-harness-setup-posture-types.ts +218 -0
- package/src/tools/agent-harness-setup-posture-utils.ts +173 -0
- package/src/tools/agent-harness-setup-posture.ts +449 -221
- package/src/tools/agent-harness-setup-smoke.ts +508 -0
- package/src/tools/agent-harness-sudo-posture.ts +114 -0
- package/src/tools/agent-harness-tool-schema.ts +120 -7
- package/src/tools/agent-harness-tool-types.ts +78 -0
- package/src/tools/agent-harness-tool-utils.ts +43 -0
- package/src/tools/agent-harness-tool.ts +311 -370
- package/src/tools/agent-harness-ui-surface-metadata.ts +76 -69
- package/src/tools/agent-harness-ui-surface-types.ts +46 -0
- package/src/tools/agent-harness-vibe-health.ts +105 -0
- package/src/tools/agent-harness-workspace-action-runner.ts +149 -0
- package/src/tools/agent-harness-workspace-actions.ts +114 -8
- package/src/tools/agent-harness-workspace-editor-execution.ts +214 -0
- package/src/tools/agent-harness-workspace-editor-runner.ts +755 -0
- package/src/tools/agent-host-tool.ts +159 -0
- package/src/tools/agent-knowledge-ingest-tool.ts +34 -1
- package/src/tools/agent-learning-consolidation-core.ts +327 -0
- package/src/tools/agent-learning-consolidation-tool.ts +758 -0
- package/src/tools/agent-memory-tool.ts +214 -0
- package/src/tools/agent-model-compare-export.ts +315 -0
- package/src/tools/agent-model-compare-handoff.ts +592 -0
- package/src/tools/agent-model-compare-judgment.ts +633 -0
- package/src/tools/agent-model-compare-run.ts +722 -0
- package/src/tools/agent-model-compare-tool.ts +698 -0
- package/src/tools/agent-model-compare-types.ts +191 -0
- package/src/tools/agent-model-compare-utils.ts +93 -0
- package/src/tools/agent-models-tool.ts +208 -0
- package/src/tools/agent-operator-action-tool.ts +1 -1
- package/src/tools/agent-operator-method-tool.ts +643 -0
- package/src/tools/agent-personal-ops-tool.ts +197 -0
- package/src/tools/agent-policy-explanation.ts +415 -0
- package/src/tools/agent-research-report-tool.ts +608 -0
- package/src/tools/agent-research-runs-tool.ts +434 -0
- package/src/tools/agent-research-sources-tool.ts +443 -0
- package/src/tools/agent-research-tool.ts +687 -0
- package/src/tools/agent-review-packet-presets-core.ts +757 -0
- package/src/tools/agent-review-packet-presets-tool.ts +466 -0
- package/src/tools/agent-review-packet-share-tool.ts +305 -0
- package/src/tools/agent-route-planner-candidates-setup.ts +411 -0
- package/src/tools/agent-route-planner-candidates-surfaces.ts +432 -0
- package/src/tools/agent-route-planner-candidates-work.ts +251 -0
- package/src/tools/agent-route-planner-helpers.ts +667 -0
- package/src/tools/agent-route-planner.ts +185 -0
- package/src/tools/agent-route-tool.ts +105 -0
- package/src/tools/agent-schedule-edit-tool.ts +210 -0
- package/src/tools/agent-schedule-tool.ts +282 -0
- package/src/tools/agent-security-tool.ts +145 -0
- package/src/tools/agent-sessions-tool.ts +122 -0
- package/src/tools/agent-settings-import-tool.ts +104 -0
- package/src/tools/agent-settings-tool.ts +176 -0
- package/src/tools/agent-setup-tool.ts +226 -0
- package/src/tools/agent-support-tool.ts +122 -0
- package/src/tools/agent-terminal-process-tools.ts +167 -0
- package/src/tools/agent-tool-policy-guard-types.ts +77 -0
- package/src/tools/agent-tool-policy-guard.ts +106 -85
- package/src/tools/agent-vibe-tool.ts +297 -0
- package/src/tools/agent-work-plan-tool.ts +265 -6
- package/src/tools/agent-workspace-tool.ts +305 -0
- package/src/tools/artifact-archive.ts +169 -0
- package/src/tools/tool-definition-compaction.ts +36 -0
- package/src/version.ts +1 -1
|
@@ -0,0 +1,582 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { dirname } from 'node:path';
|
|
3
|
+
import type { ShellPathService } from '@/runtime/index.ts';
|
|
4
|
+
import { GOODVIBES_AGENT_SURFACE_ROOT } from '../config/surface.ts';
|
|
5
|
+
import { assertNoSecretLikeText } from './persona-registry.ts';
|
|
6
|
+
|
|
7
|
+
export type AgentResearchRunStatus = 'planned' | 'running' | 'paused' | 'blocked' | 'cancelled' | 'completed' | 'failed';
|
|
8
|
+
export type AgentResearchRunPhase = 'planning' | 'searching' | 'reading' | 'synthesizing' | 'reviewing' | 'reporting';
|
|
9
|
+
|
|
10
|
+
export interface AgentResearchRunCheckpoint {
|
|
11
|
+
readonly id: string;
|
|
12
|
+
readonly at: string;
|
|
13
|
+
readonly phase: AgentResearchRunPhase;
|
|
14
|
+
readonly status: AgentResearchRunStatus;
|
|
15
|
+
readonly progress: number;
|
|
16
|
+
readonly note: string;
|
|
17
|
+
readonly nextSteps: readonly string[];
|
|
18
|
+
readonly sourceIds: readonly string[];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface AgentResearchRunRecord {
|
|
22
|
+
readonly id: string;
|
|
23
|
+
readonly title: string;
|
|
24
|
+
readonly question: string;
|
|
25
|
+
readonly goal: string;
|
|
26
|
+
readonly status: AgentResearchRunStatus;
|
|
27
|
+
readonly phase: AgentResearchRunPhase;
|
|
28
|
+
readonly progress: number;
|
|
29
|
+
readonly plan: readonly string[];
|
|
30
|
+
readonly nextSteps: readonly string[];
|
|
31
|
+
readonly sourceIds: readonly string[];
|
|
32
|
+
readonly checkpoints: readonly AgentResearchRunCheckpoint[];
|
|
33
|
+
readonly reportArtifactId?: string;
|
|
34
|
+
readonly note?: string;
|
|
35
|
+
readonly error?: string;
|
|
36
|
+
readonly provenance: string;
|
|
37
|
+
readonly createdAt: string;
|
|
38
|
+
readonly updatedAt: string;
|
|
39
|
+
readonly startedAt?: string;
|
|
40
|
+
readonly pausedAt?: string;
|
|
41
|
+
readonly cancelledAt?: string;
|
|
42
|
+
readonly completedAt?: string;
|
|
43
|
+
readonly failedAt?: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface AgentResearchRunCreateInput {
|
|
47
|
+
readonly title: string;
|
|
48
|
+
readonly question: string;
|
|
49
|
+
readonly goal?: string;
|
|
50
|
+
readonly plan?: readonly string[];
|
|
51
|
+
readonly nextSteps?: readonly string[];
|
|
52
|
+
readonly sourceIds?: readonly string[];
|
|
53
|
+
readonly note?: string;
|
|
54
|
+
readonly provenance?: string;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface AgentResearchRunCheckpointInput {
|
|
58
|
+
readonly phase?: AgentResearchRunPhase;
|
|
59
|
+
readonly status?: AgentResearchRunStatus;
|
|
60
|
+
readonly progress?: number;
|
|
61
|
+
readonly note: string;
|
|
62
|
+
readonly nextSteps?: readonly string[];
|
|
63
|
+
readonly sourceIds?: readonly string[];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface AgentResearchRunCompletionInput {
|
|
67
|
+
readonly note?: string;
|
|
68
|
+
readonly reportArtifactId?: string;
|
|
69
|
+
readonly sourceIds?: readonly string[];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface AgentResearchRunSnapshot {
|
|
73
|
+
readonly path: string;
|
|
74
|
+
readonly runs: readonly AgentResearchRunRecord[];
|
|
75
|
+
readonly planned: readonly AgentResearchRunRecord[];
|
|
76
|
+
readonly running: readonly AgentResearchRunRecord[];
|
|
77
|
+
readonly paused: readonly AgentResearchRunRecord[];
|
|
78
|
+
readonly blocked: readonly AgentResearchRunRecord[];
|
|
79
|
+
readonly cancelled: readonly AgentResearchRunRecord[];
|
|
80
|
+
readonly completed: readonly AgentResearchRunRecord[];
|
|
81
|
+
readonly failed: readonly AgentResearchRunRecord[];
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
interface ResearchRunStoreFile {
|
|
85
|
+
readonly version: 1;
|
|
86
|
+
readonly runs: readonly AgentResearchRunRecord[];
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
type AgentResearchRunStorePaths = Pick<ShellPathService, 'resolveProjectPath'>;
|
|
90
|
+
|
|
91
|
+
const STORE_VERSION = 1;
|
|
92
|
+
const TERMINAL_STATUSES = new Set<AgentResearchRunStatus>(['cancelled', 'completed', 'failed']);
|
|
93
|
+
|
|
94
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
95
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function readString(value: unknown, fallback = ''): string {
|
|
99
|
+
return typeof value === 'string' ? value : fallback;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function readStringArray(value: unknown): string[] {
|
|
103
|
+
if (!Array.isArray(value)) return [];
|
|
104
|
+
return value.filter((entry): entry is string => typeof entry === 'string').map((entry) => entry.trim()).filter(Boolean);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function normalizeText(value: string): string {
|
|
108
|
+
return value.trim().replace(/\s+/g, ' ');
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function normalizeList(values: readonly string[] | undefined): string[] {
|
|
112
|
+
const seen = new Set<string>();
|
|
113
|
+
const result: string[] = [];
|
|
114
|
+
for (const value of values ?? []) {
|
|
115
|
+
const trimmed = value.trim();
|
|
116
|
+
if (!trimmed) continue;
|
|
117
|
+
const key = trimmed.toLowerCase();
|
|
118
|
+
if (seen.has(key)) continue;
|
|
119
|
+
seen.add(key);
|
|
120
|
+
result.push(trimmed);
|
|
121
|
+
}
|
|
122
|
+
return result;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function slugify(value: string): string {
|
|
126
|
+
const slug = value.trim().toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');
|
|
127
|
+
return slug || 'research-run';
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function nowIso(): string {
|
|
131
|
+
return new Date().toISOString();
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function status(value: unknown): AgentResearchRunStatus {
|
|
135
|
+
if (
|
|
136
|
+
value === 'running'
|
|
137
|
+
|| value === 'paused'
|
|
138
|
+
|| value === 'blocked'
|
|
139
|
+
|| value === 'cancelled'
|
|
140
|
+
|| value === 'completed'
|
|
141
|
+
|| value === 'failed'
|
|
142
|
+
) return value;
|
|
143
|
+
return 'planned';
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function phase(value: unknown): AgentResearchRunPhase {
|
|
147
|
+
if (
|
|
148
|
+
value === 'searching'
|
|
149
|
+
|| value === 'reading'
|
|
150
|
+
|| value === 'synthesizing'
|
|
151
|
+
|| value === 'reviewing'
|
|
152
|
+
|| value === 'reporting'
|
|
153
|
+
) return value;
|
|
154
|
+
return 'planning';
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function boundedProgress(value: unknown, fallback = 0): number {
|
|
158
|
+
const parsed = typeof value === 'number' ? value : typeof value === 'string' && value.trim() ? Number(value) : fallback;
|
|
159
|
+
if (!Number.isFinite(parsed)) return fallback;
|
|
160
|
+
return Math.max(0, Math.min(100, Math.round(parsed)));
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function checkpointId(at: string, count: number): string {
|
|
164
|
+
return `cp-${at.replace(/[^0-9]/g, '').slice(0, 14)}-${count + 1}`;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function assertRunContentSafe(fields: readonly string[]): void {
|
|
168
|
+
assertNoSecretLikeText(fields, 'Research run ledger');
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function parseCheckpoint(value: unknown): AgentResearchRunCheckpoint | null {
|
|
172
|
+
if (!isRecord(value)) return null;
|
|
173
|
+
const id = readString(value.id).trim();
|
|
174
|
+
const at = readString(value.at).trim();
|
|
175
|
+
const note = readString(value.note).trim();
|
|
176
|
+
if (!id || !at || !note) return null;
|
|
177
|
+
return {
|
|
178
|
+
id,
|
|
179
|
+
at,
|
|
180
|
+
phase: phase(value.phase),
|
|
181
|
+
status: status(value.status),
|
|
182
|
+
progress: boundedProgress(value.progress),
|
|
183
|
+
note,
|
|
184
|
+
nextSteps: readStringArray(value.nextSteps),
|
|
185
|
+
sourceIds: readStringArray(value.sourceIds),
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function parseRun(value: unknown): AgentResearchRunRecord | null {
|
|
190
|
+
if (!isRecord(value)) return null;
|
|
191
|
+
const id = readString(value.id).trim();
|
|
192
|
+
const title = normalizeText(readString(value.title));
|
|
193
|
+
const question = normalizeText(readString(value.question));
|
|
194
|
+
const goal = readString(value.goal).trim();
|
|
195
|
+
if (!id || !title || !question || !goal) return null;
|
|
196
|
+
const createdAt = readString(value.createdAt, nowIso());
|
|
197
|
+
const note = readString(value.note).trim();
|
|
198
|
+
const error = readString(value.error).trim();
|
|
199
|
+
const reportArtifactId = readString(value.reportArtifactId).trim();
|
|
200
|
+
const startedAt = readString(value.startedAt).trim();
|
|
201
|
+
const pausedAt = readString(value.pausedAt).trim();
|
|
202
|
+
const cancelledAt = readString(value.cancelledAt).trim();
|
|
203
|
+
const completedAt = readString(value.completedAt).trim();
|
|
204
|
+
const failedAt = readString(value.failedAt).trim();
|
|
205
|
+
return {
|
|
206
|
+
id,
|
|
207
|
+
title,
|
|
208
|
+
question,
|
|
209
|
+
goal,
|
|
210
|
+
status: status(value.status),
|
|
211
|
+
phase: phase(value.phase),
|
|
212
|
+
progress: boundedProgress(value.progress),
|
|
213
|
+
plan: readStringArray(value.plan),
|
|
214
|
+
nextSteps: readStringArray(value.nextSteps),
|
|
215
|
+
sourceIds: readStringArray(value.sourceIds),
|
|
216
|
+
checkpoints: Array.isArray(value.checkpoints)
|
|
217
|
+
? value.checkpoints.map(parseCheckpoint).filter((entry): entry is AgentResearchRunCheckpoint => entry !== null)
|
|
218
|
+
: [],
|
|
219
|
+
...(reportArtifactId ? { reportArtifactId } : {}),
|
|
220
|
+
...(note ? { note } : {}),
|
|
221
|
+
...(error ? { error } : {}),
|
|
222
|
+
provenance: readString(value.provenance, 'agent-research-runs').trim() || 'agent-research-runs',
|
|
223
|
+
createdAt,
|
|
224
|
+
updatedAt: readString(value.updatedAt, createdAt),
|
|
225
|
+
...(startedAt ? { startedAt } : {}),
|
|
226
|
+
...(pausedAt ? { pausedAt } : {}),
|
|
227
|
+
...(cancelledAt ? { cancelledAt } : {}),
|
|
228
|
+
...(completedAt ? { completedAt } : {}),
|
|
229
|
+
...(failedAt ? { failedAt } : {}),
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function parseStore(raw: string): ResearchRunStoreFile {
|
|
234
|
+
const parsed: unknown = JSON.parse(raw);
|
|
235
|
+
if (!isRecord(parsed)) return { version: STORE_VERSION, runs: [] };
|
|
236
|
+
return {
|
|
237
|
+
version: STORE_VERSION,
|
|
238
|
+
runs: Array.isArray(parsed.runs)
|
|
239
|
+
? parsed.runs.map(parseRun).filter((entry): entry is AgentResearchRunRecord => entry !== null)
|
|
240
|
+
: [],
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function formatStore(store: ResearchRunStoreFile): string {
|
|
245
|
+
return `${JSON.stringify(store, null, 2)}\n`;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
export function researchRunStorePath(shellPaths: AgentResearchRunStorePaths): string {
|
|
249
|
+
return shellPaths.resolveProjectPath(GOODVIBES_AGENT_SURFACE_ROOT, 'research', 'runs.json');
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
export function researchRunReportLine(run: AgentResearchRunRecord): string {
|
|
253
|
+
const sourceSummary = run.sourceIds.length > 0 ? `${run.sourceIds.length} source(s)` : 'no sources';
|
|
254
|
+
const artifact = run.reportArtifactId ? ` report ${run.reportArtifactId}` : '';
|
|
255
|
+
return `${run.title} | ${run.status}/${run.phase} | ${run.progress}% | ${sourceSummary}${artifact}`;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
export function researchRunLogTail(run: AgentResearchRunRecord, limit = 5): readonly string[] {
|
|
259
|
+
const entries = [
|
|
260
|
+
...(run.startedAt ? [{
|
|
261
|
+
at: run.startedAt,
|
|
262
|
+
line: `${run.startedAt} started ${run.status}/${run.phase} ${run.progress}%${run.note ? ` ${run.note}` : ''}`,
|
|
263
|
+
}] : []),
|
|
264
|
+
...run.checkpoints.map((checkpoint) => ({
|
|
265
|
+
at: checkpoint.at,
|
|
266
|
+
line: [
|
|
267
|
+
`${checkpoint.at} checkpoint ${checkpoint.status}/${checkpoint.phase} ${checkpoint.progress}%`,
|
|
268
|
+
checkpoint.note,
|
|
269
|
+
checkpoint.sourceIds.length > 0 ? `sources ${checkpoint.sourceIds.join(', ')}` : '',
|
|
270
|
+
checkpoint.nextSteps.length > 0 ? `next ${checkpoint.nextSteps.join('; ')}` : '',
|
|
271
|
+
].filter(Boolean).join(' | '),
|
|
272
|
+
})),
|
|
273
|
+
...(run.pausedAt ? [{ at: run.pausedAt, line: `${run.pausedAt} paused ${run.status}/${run.phase} ${run.progress}%${run.note ? ` ${run.note}` : ''}` }] : []),
|
|
274
|
+
...(run.cancelledAt ? [{ at: run.cancelledAt, line: `${run.cancelledAt} cancelled ${run.status}/${run.phase} ${run.progress}%${run.note ? ` ${run.note}` : ''}` }] : []),
|
|
275
|
+
...(run.completedAt ? [{ at: run.completedAt, line: `${run.completedAt} completed ${run.status}/${run.phase} ${run.progress}%${run.reportArtifactId ? ` artifact ${run.reportArtifactId}` : ''}` }] : []),
|
|
276
|
+
...(run.failedAt ? [{ at: run.failedAt, line: `${run.failedAt} failed ${run.status}/${run.phase} ${run.progress}%${run.error ? ` ${run.error}` : ''}` }] : []),
|
|
277
|
+
].sort((left, right) => left.at.localeCompare(right.at));
|
|
278
|
+
return entries.slice(-Math.max(1, Math.min(20, Math.trunc(limit)))).map((entry) => entry.line);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
export class AgentResearchRunRegistry {
|
|
282
|
+
public constructor(private readonly storePath: string) {}
|
|
283
|
+
|
|
284
|
+
public static fromShellPaths(shellPaths: AgentResearchRunStorePaths): AgentResearchRunRegistry {
|
|
285
|
+
return new AgentResearchRunRegistry(researchRunStorePath(shellPaths));
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
public snapshot(): AgentResearchRunSnapshot {
|
|
289
|
+
const runs = [...this.readStore().runs].sort((left, right) => right.updatedAt.localeCompare(left.updatedAt));
|
|
290
|
+
return {
|
|
291
|
+
path: this.storePath,
|
|
292
|
+
runs,
|
|
293
|
+
planned: runs.filter((run) => run.status === 'planned'),
|
|
294
|
+
running: runs.filter((run) => run.status === 'running'),
|
|
295
|
+
paused: runs.filter((run) => run.status === 'paused'),
|
|
296
|
+
blocked: runs.filter((run) => run.status === 'blocked'),
|
|
297
|
+
cancelled: runs.filter((run) => run.status === 'cancelled'),
|
|
298
|
+
completed: runs.filter((run) => run.status === 'completed'),
|
|
299
|
+
failed: runs.filter((run) => run.status === 'failed'),
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
public list(statusFilter?: AgentResearchRunStatus): readonly AgentResearchRunRecord[] {
|
|
304
|
+
const runs = this.snapshot().runs;
|
|
305
|
+
return statusFilter ? runs.filter((run) => run.status === statusFilter) : runs;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
public search(query: string): readonly AgentResearchRunRecord[] {
|
|
309
|
+
const normalized = query.trim().toLowerCase();
|
|
310
|
+
if (!normalized) return this.list();
|
|
311
|
+
return this.list().filter((run) => [
|
|
312
|
+
run.id,
|
|
313
|
+
run.title,
|
|
314
|
+
run.question,
|
|
315
|
+
run.goal,
|
|
316
|
+
run.status,
|
|
317
|
+
run.phase,
|
|
318
|
+
run.note ?? '',
|
|
319
|
+
run.error ?? '',
|
|
320
|
+
run.reportArtifactId ?? '',
|
|
321
|
+
...run.plan,
|
|
322
|
+
...run.nextSteps,
|
|
323
|
+
...run.sourceIds,
|
|
324
|
+
...run.checkpoints.flatMap((checkpoint) => [
|
|
325
|
+
checkpoint.id,
|
|
326
|
+
checkpoint.note,
|
|
327
|
+
checkpoint.phase,
|
|
328
|
+
checkpoint.status,
|
|
329
|
+
...checkpoint.nextSteps,
|
|
330
|
+
...checkpoint.sourceIds,
|
|
331
|
+
]),
|
|
332
|
+
].some((field) => field.toLowerCase().includes(normalized)));
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
public get(idOrTitle: string): AgentResearchRunRecord | null {
|
|
336
|
+
const lookup = idOrTitle.trim().toLowerCase();
|
|
337
|
+
if (!lookup) return null;
|
|
338
|
+
return this.list().find((run) => (
|
|
339
|
+
run.id.toLowerCase() === lookup
|
|
340
|
+
|| run.title.toLowerCase() === lookup
|
|
341
|
+
)) ?? null;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
public create(input: AgentResearchRunCreateInput): AgentResearchRunRecord {
|
|
345
|
+
const store = this.readStore();
|
|
346
|
+
const title = normalizeText(input.title);
|
|
347
|
+
const question = normalizeText(input.question);
|
|
348
|
+
const goal = input.goal?.trim() || question;
|
|
349
|
+
const plan = normalizeList(input.plan);
|
|
350
|
+
const nextSteps = normalizeList(input.nextSteps);
|
|
351
|
+
const sourceIds = normalizeList(input.sourceIds);
|
|
352
|
+
const note = input.note?.trim();
|
|
353
|
+
if (!title) throw new Error('title is required.');
|
|
354
|
+
if (!question) throw new Error('question is required.');
|
|
355
|
+
if (!goal) throw new Error('goal is required.');
|
|
356
|
+
assertRunContentSafe([title, question, goal, ...plan, ...nextSteps, ...sourceIds, note ?? '']);
|
|
357
|
+
const id = this.uniqueId(store.runs, title);
|
|
358
|
+
const timestamp = nowIso();
|
|
359
|
+
const run: AgentResearchRunRecord = {
|
|
360
|
+
id,
|
|
361
|
+
title,
|
|
362
|
+
question,
|
|
363
|
+
goal,
|
|
364
|
+
status: 'planned',
|
|
365
|
+
phase: 'planning',
|
|
366
|
+
progress: 0,
|
|
367
|
+
plan,
|
|
368
|
+
nextSteps,
|
|
369
|
+
sourceIds,
|
|
370
|
+
checkpoints: [],
|
|
371
|
+
...(note ? { note } : {}),
|
|
372
|
+
provenance: input.provenance?.trim() || 'agent-research-runs',
|
|
373
|
+
createdAt: timestamp,
|
|
374
|
+
updatedAt: timestamp,
|
|
375
|
+
};
|
|
376
|
+
this.writeStore({ version: STORE_VERSION, runs: [...store.runs, run] });
|
|
377
|
+
return run;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
public start(id: string, note?: string): AgentResearchRunRecord {
|
|
381
|
+
return this.update(id, (run) => {
|
|
382
|
+
this.assertMutable(run, 'start');
|
|
383
|
+
const timestamp = nowIso();
|
|
384
|
+
return {
|
|
385
|
+
...run,
|
|
386
|
+
status: 'running',
|
|
387
|
+
phase: run.phase === 'planning' && run.progress > 0 ? run.phase : run.phase,
|
|
388
|
+
progress: Math.max(run.progress, run.status === 'planned' ? 1 : run.progress),
|
|
389
|
+
...(note?.trim() ? { note: note.trim() } : {}),
|
|
390
|
+
startedAt: run.startedAt ?? timestamp,
|
|
391
|
+
updatedAt: timestamp,
|
|
392
|
+
};
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
public resume(id: string, note?: string): AgentResearchRunRecord {
|
|
397
|
+
return this.update(id, (run) => {
|
|
398
|
+
this.assertMutable(run, 'resume');
|
|
399
|
+
const timestamp = nowIso();
|
|
400
|
+
return {
|
|
401
|
+
...run,
|
|
402
|
+
status: 'running',
|
|
403
|
+
...(note?.trim() ? { note: note.trim() } : {}),
|
|
404
|
+
startedAt: run.startedAt ?? timestamp,
|
|
405
|
+
updatedAt: timestamp,
|
|
406
|
+
};
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
public pause(id: string, note?: string): AgentResearchRunRecord {
|
|
411
|
+
return this.update(id, (run) => {
|
|
412
|
+
this.assertMutable(run, 'pause');
|
|
413
|
+
const timestamp = nowIso();
|
|
414
|
+
const finalNote = note?.trim();
|
|
415
|
+
if (finalNote) assertRunContentSafe([finalNote]);
|
|
416
|
+
return {
|
|
417
|
+
...run,
|
|
418
|
+
status: 'paused',
|
|
419
|
+
...(finalNote ? { note: finalNote } : {}),
|
|
420
|
+
pausedAt: timestamp,
|
|
421
|
+
updatedAt: timestamp,
|
|
422
|
+
};
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
public cancel(id: string, note: string): AgentResearchRunRecord {
|
|
427
|
+
const finalNote = note.trim();
|
|
428
|
+
if (!finalNote) throw new Error('cancellation note is required.');
|
|
429
|
+
assertRunContentSafe([finalNote]);
|
|
430
|
+
return this.update(id, (run) => {
|
|
431
|
+
this.assertMutable(run, 'cancel');
|
|
432
|
+
const timestamp = nowIso();
|
|
433
|
+
return {
|
|
434
|
+
...run,
|
|
435
|
+
status: 'cancelled',
|
|
436
|
+
note: finalNote,
|
|
437
|
+
cancelledAt: timestamp,
|
|
438
|
+
updatedAt: timestamp,
|
|
439
|
+
};
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
public checkpoint(id: string, input: AgentResearchRunCheckpointInput): AgentResearchRunRecord {
|
|
444
|
+
const note = input.note.trim();
|
|
445
|
+
if (!note) throw new Error('checkpoint note is required.');
|
|
446
|
+
const nextSteps = normalizeList(input.nextSteps);
|
|
447
|
+
const sourceIds = normalizeList(input.sourceIds);
|
|
448
|
+
const finalStatus = input.status && input.status !== 'cancelled' && input.status !== 'completed' && input.status !== 'failed'
|
|
449
|
+
? input.status
|
|
450
|
+
: undefined;
|
|
451
|
+
assertRunContentSafe([note, ...nextSteps, ...sourceIds]);
|
|
452
|
+
return this.update(id, (run) => {
|
|
453
|
+
this.assertMutable(run, 'checkpoint');
|
|
454
|
+
const timestamp = nowIso();
|
|
455
|
+
const finalPhase = input.phase ?? run.phase;
|
|
456
|
+
const progress = boundedProgress(input.progress, run.progress);
|
|
457
|
+
const mergedSourceIds = normalizeList([...run.sourceIds, ...sourceIds]);
|
|
458
|
+
const nextStatus = finalStatus ?? (run.status === 'planned' ? 'running' : run.status);
|
|
459
|
+
const checkpoint: AgentResearchRunCheckpoint = {
|
|
460
|
+
id: checkpointId(timestamp, run.checkpoints.length),
|
|
461
|
+
at: timestamp,
|
|
462
|
+
phase: finalPhase,
|
|
463
|
+
status: nextStatus,
|
|
464
|
+
progress,
|
|
465
|
+
note,
|
|
466
|
+
nextSteps,
|
|
467
|
+
sourceIds,
|
|
468
|
+
};
|
|
469
|
+
return {
|
|
470
|
+
...run,
|
|
471
|
+
status: nextStatus,
|
|
472
|
+
phase: finalPhase,
|
|
473
|
+
progress,
|
|
474
|
+
nextSteps: nextSteps.length > 0 ? nextSteps : run.nextSteps,
|
|
475
|
+
sourceIds: mergedSourceIds,
|
|
476
|
+
checkpoints: [...run.checkpoints, checkpoint],
|
|
477
|
+
note,
|
|
478
|
+
startedAt: run.startedAt ?? timestamp,
|
|
479
|
+
updatedAt: timestamp,
|
|
480
|
+
};
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
public complete(id: string, input: AgentResearchRunCompletionInput = {}): AgentResearchRunRecord {
|
|
485
|
+
const note = input.note?.trim();
|
|
486
|
+
const reportArtifactId = input.reportArtifactId?.trim();
|
|
487
|
+
const sourceIds = normalizeList(input.sourceIds);
|
|
488
|
+
assertRunContentSafe([note ?? '', reportArtifactId ?? '', ...sourceIds]);
|
|
489
|
+
return this.update(id, (run) => {
|
|
490
|
+
this.assertMutable(run, 'complete');
|
|
491
|
+
const timestamp = nowIso();
|
|
492
|
+
return {
|
|
493
|
+
...run,
|
|
494
|
+
status: 'completed',
|
|
495
|
+
phase: 'reporting',
|
|
496
|
+
progress: 100,
|
|
497
|
+
sourceIds: normalizeList([...run.sourceIds, ...sourceIds]),
|
|
498
|
+
...(reportArtifactId ? { reportArtifactId } : {}),
|
|
499
|
+
...(note ? { note } : {}),
|
|
500
|
+
completedAt: timestamp,
|
|
501
|
+
updatedAt: timestamp,
|
|
502
|
+
};
|
|
503
|
+
});
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
public fail(id: string, error: string): AgentResearchRunRecord {
|
|
507
|
+
const finalError = error.trim();
|
|
508
|
+
if (!finalError) throw new Error('failure note is required.');
|
|
509
|
+
assertRunContentSafe([finalError]);
|
|
510
|
+
return this.update(id, (run) => {
|
|
511
|
+
this.assertMutable(run, 'fail');
|
|
512
|
+
const timestamp = nowIso();
|
|
513
|
+
return {
|
|
514
|
+
...run,
|
|
515
|
+
status: 'failed',
|
|
516
|
+
error: finalError,
|
|
517
|
+
failedAt: timestamp,
|
|
518
|
+
updatedAt: timestamp,
|
|
519
|
+
};
|
|
520
|
+
});
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
public delete(id: string): AgentResearchRunRecord {
|
|
524
|
+
const store = this.readStore();
|
|
525
|
+
const run = this.find(store.runs, id);
|
|
526
|
+
if (!run) throw new Error(`Research run ${id} not found.`);
|
|
527
|
+
this.writeStore({ version: STORE_VERSION, runs: store.runs.filter((entry) => entry.id !== run.id) });
|
|
528
|
+
return run;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
private assertMutable(run: AgentResearchRunRecord, action: string): void {
|
|
532
|
+
if (TERMINAL_STATUSES.has(run.status)) throw new Error(`Cannot ${action} ${run.status} research run ${run.id}.`);
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
private update(id: string, updater: (run: AgentResearchRunRecord) => AgentResearchRunRecord): AgentResearchRunRecord {
|
|
536
|
+
const store = this.readStore();
|
|
537
|
+
const existing = this.find(store.runs, id);
|
|
538
|
+
if (!existing) throw new Error(`Research run ${id} not found.`);
|
|
539
|
+
const updated = updater(existing);
|
|
540
|
+
this.writeStore({
|
|
541
|
+
version: STORE_VERSION,
|
|
542
|
+
runs: store.runs.map((run) => run.id === existing.id ? updated : run),
|
|
543
|
+
});
|
|
544
|
+
return updated;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
private find(runs: readonly AgentResearchRunRecord[], idOrTitle: string): AgentResearchRunRecord | null {
|
|
548
|
+
const lookup = idOrTitle.trim().toLowerCase();
|
|
549
|
+
if (!lookup) return null;
|
|
550
|
+
return runs.find((run) => (
|
|
551
|
+
run.id.toLowerCase() === lookup
|
|
552
|
+
|| run.title.toLowerCase() === lookup
|
|
553
|
+
)) ?? null;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
private uniqueId(runs: readonly AgentResearchRunRecord[], title: string): string {
|
|
557
|
+
const base = slugify(title);
|
|
558
|
+
const used = new Set(runs.map((run) => run.id));
|
|
559
|
+
if (!used.has(base)) return base;
|
|
560
|
+
for (let index = 2; index < 10_000; index += 1) {
|
|
561
|
+
const candidate = `${base}-${index}`;
|
|
562
|
+
if (!used.has(candidate)) return candidate;
|
|
563
|
+
}
|
|
564
|
+
throw new Error(`Could not allocate id for research run ${title}.`);
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
private readStore(): ResearchRunStoreFile {
|
|
568
|
+
if (!existsSync(this.storePath)) return { version: STORE_VERSION, runs: [] };
|
|
569
|
+
try {
|
|
570
|
+
return parseStore(readFileSync(this.storePath, 'utf8'));
|
|
571
|
+
} catch {
|
|
572
|
+
return { version: STORE_VERSION, runs: [] };
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
private writeStore(store: ResearchRunStoreFile): void {
|
|
577
|
+
mkdirSync(dirname(this.storePath), { recursive: true });
|
|
578
|
+
const tempPath = `${this.storePath}.tmp`;
|
|
579
|
+
writeFileSync(tempPath, formatStore(store), 'utf8');
|
|
580
|
+
renameSync(tempPath, this.storePath);
|
|
581
|
+
}
|
|
582
|
+
}
|