actoviq-agent-sdk 0.4.11 → 0.4.13
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 +42 -0
- package/dist/src/computer/hadamardComputerUse.d.ts +15 -0
- package/dist/src/computer/hadamardComputerUse.d.ts.map +1 -1
- package/dist/src/computer/hadamardComputerUse.js +120 -4
- package/dist/src/computer/hadamardComputerUse.js.map +1 -1
- package/dist/src/config/agentDefinitionMigration.d.ts +125 -0
- package/dist/src/config/agentDefinitionMigration.d.ts.map +1 -0
- package/dist/src/config/agentDefinitionMigration.js +540 -0
- package/dist/src/config/agentDefinitionMigration.js.map +1 -0
- package/dist/src/config/agentProfiles.d.ts +23 -0
- package/dist/src/config/agentProfiles.d.ts.map +1 -1
- package/dist/src/config/agentProfiles.js +69 -13
- package/dist/src/config/agentProfiles.js.map +1 -1
- package/dist/src/config/hadamardHome.js +0 -1
- package/dist/src/config/hadamardHome.js.map +1 -1
- package/dist/src/config/modelConfigurationCatalog.d.ts +25 -0
- package/dist/src/config/modelConfigurationCatalog.d.ts.map +1 -0
- package/dist/src/config/modelConfigurationCatalog.js +50 -0
- package/dist/src/config/modelConfigurationCatalog.js.map +1 -0
- package/dist/src/config/projectSettings.d.ts +62 -0
- package/dist/src/config/projectSettings.d.ts.map +1 -0
- package/dist/src/config/projectSettings.js +242 -0
- package/dist/src/config/projectSettings.js.map +1 -0
- package/dist/src/config/resolveRuntimeConfig.d.ts.map +1 -1
- package/dist/src/config/resolveRuntimeConfig.js +97 -4
- package/dist/src/config/resolveRuntimeConfig.js.map +1 -1
- package/dist/src/goal/goalCommandService.d.ts +11 -0
- package/dist/src/goal/goalCommandService.d.ts.map +1 -0
- package/dist/src/goal/goalCommandService.js +138 -0
- package/dist/src/goal/goalCommandService.js.map +1 -0
- package/dist/src/goal/goalContinuation.d.ts +50 -0
- package/dist/src/goal/goalContinuation.d.ts.map +1 -0
- package/dist/src/goal/goalContinuation.js +131 -0
- package/dist/src/goal/goalContinuation.js.map +1 -0
- package/dist/src/goal/goalController.d.ts +42 -0
- package/dist/src/goal/goalController.d.ts.map +1 -0
- package/dist/src/goal/goalController.js +281 -0
- package/dist/src/goal/goalController.js.map +1 -0
- package/dist/src/goal/goalPrompt.d.ts +3 -0
- package/dist/src/goal/goalPrompt.d.ts.map +1 -1
- package/dist/src/goal/goalPrompt.js +28 -2
- package/dist/src/goal/goalPrompt.js.map +1 -1
- package/dist/src/goal/goalService.d.ts +68 -2
- package/dist/src/goal/goalService.d.ts.map +1 -1
- package/dist/src/goal/goalService.js +500 -8
- package/dist/src/goal/goalService.js.map +1 -1
- package/dist/src/goal/goalStore.d.ts.map +1 -1
- package/dist/src/goal/goalStore.js +172 -5
- package/dist/src/goal/goalStore.js.map +1 -1
- package/dist/src/goal/goalTools.d.ts +2 -1
- package/dist/src/goal/goalTools.d.ts.map +1 -1
- package/dist/src/goal/goalTools.js +70 -5
- package/dist/src/goal/goalTools.js.map +1 -1
- package/dist/src/goal/goalWorkCoordinator.d.ts +43 -0
- package/dist/src/goal/goalWorkCoordinator.d.ts.map +1 -0
- package/dist/src/goal/goalWorkCoordinator.js +72 -0
- package/dist/src/goal/goalWorkCoordinator.js.map +1 -0
- package/dist/src/goal/index.d.ts +10 -4
- package/dist/src/goal/index.d.ts.map +1 -1
- package/dist/src/goal/index.js +7 -1
- package/dist/src/goal/index.js.map +1 -1
- package/dist/src/goal/projectGoalApi.d.ts +82 -0
- package/dist/src/goal/projectGoalApi.d.ts.map +1 -0
- package/dist/src/goal/projectGoalApi.js +287 -0
- package/dist/src/goal/projectGoalApi.js.map +1 -0
- package/dist/src/goal/projectGoalStore.d.ts +131 -0
- package/dist/src/goal/projectGoalStore.d.ts.map +1 -0
- package/dist/src/goal/projectGoalStore.js +808 -0
- package/dist/src/goal/projectGoalStore.js.map +1 -0
- package/dist/src/goal/types.d.ts +127 -3
- package/dist/src/goal/types.d.ts.map +1 -1
- package/dist/src/goal/types.js +2 -2
- package/dist/src/goal/types.js.map +1 -1
- package/dist/src/gui/agentTargetPicker.d.ts +32 -0
- package/dist/src/gui/agentTargetPicker.d.ts.map +1 -0
- package/dist/src/gui/agentTargetPicker.js +99 -0
- package/dist/src/gui/agentTargetPicker.js.map +1 -0
- package/dist/src/gui/electronMain.js +10 -3
- package/dist/src/gui/electronMain.js.map +1 -1
- package/dist/src/gui/hadamardGui.d.ts +24 -0
- package/dist/src/gui/hadamardGui.d.ts.map +1 -1
- package/dist/src/gui/hadamardGui.js +6321 -1327
- package/dist/src/gui/hadamardGui.js.map +1 -1
- package/dist/src/gui/projectWorkbench.d.ts +13 -2
- package/dist/src/gui/projectWorkbench.d.ts.map +1 -1
- package/dist/src/gui/projectWorkbench.js +109 -67
- package/dist/src/gui/projectWorkbench.js.map +1 -1
- package/dist/src/gui/promptTemplates.d.ts +18 -0
- package/dist/src/gui/promptTemplates.d.ts.map +1 -0
- package/dist/src/gui/promptTemplates.js +178 -0
- package/dist/src/gui/promptTemplates.js.map +1 -0
- package/dist/src/gui/terminalManager.d.ts +14 -0
- package/dist/src/gui/terminalManager.d.ts.map +1 -1
- package/dist/src/gui/terminalManager.js +25 -5
- package/dist/src/gui/terminalManager.js.map +1 -1
- package/dist/src/gui/transcript/clientBundle.d.ts +6 -1
- package/dist/src/gui/transcript/clientBundle.d.ts.map +1 -1
- package/dist/src/gui/transcript/clientBundle.js +6 -1
- package/dist/src/gui/transcript/clientBundle.js.map +1 -1
- package/dist/src/help/productCapabilities.d.ts +17 -0
- package/dist/src/help/productCapabilities.d.ts.map +1 -0
- package/dist/src/help/productCapabilities.js +146 -0
- package/dist/src/help/productCapabilities.js.map +1 -0
- package/dist/src/index.d.ts +21 -6
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +15 -5
- package/dist/src/index.js.map +1 -1
- package/dist/src/issues/issueExecution.d.ts.map +1 -1
- package/dist/src/issues/issueExecution.js +29 -9
- package/dist/src/issues/issueExecution.js.map +1 -1
- package/dist/src/manager/assistantGlobalTools.d.ts +44 -0
- package/dist/src/manager/assistantGlobalTools.d.ts.map +1 -1
- package/dist/src/manager/assistantGlobalTools.js +565 -34
- package/dist/src/manager/assistantGlobalTools.js.map +1 -1
- package/dist/src/manager/assistantProposals.d.ts +87 -0
- package/dist/src/manager/assistantProposals.d.ts.map +1 -0
- package/dist/src/manager/assistantProposals.js +207 -0
- package/dist/src/manager/assistantProposals.js.map +1 -0
- package/dist/src/manager/referenceIndex.d.ts +82 -0
- package/dist/src/manager/referenceIndex.d.ts.map +1 -0
- package/dist/src/manager/referenceIndex.js +167 -0
- package/dist/src/manager/referenceIndex.js.map +1 -0
- package/dist/src/manager/referenceOperations.d.ts +108 -0
- package/dist/src/manager/referenceOperations.d.ts.map +1 -0
- package/dist/src/manager/referenceOperations.js +821 -0
- package/dist/src/manager/referenceOperations.js.map +1 -0
- package/dist/src/manager/resolveTargetRef.d.ts +67 -0
- package/dist/src/manager/resolveTargetRef.d.ts.map +1 -0
- package/dist/src/manager/resolveTargetRef.js +81 -0
- package/dist/src/manager/resolveTargetRef.js.map +1 -0
- package/dist/src/memory/durableMemoryPipeline.d.ts +51 -0
- package/dist/src/memory/durableMemoryPipeline.d.ts.map +1 -0
- package/dist/src/memory/durableMemoryPipeline.js +266 -0
- package/dist/src/memory/durableMemoryPipeline.js.map +1 -0
- package/dist/src/memory/durableMemoryStore.d.ts +59 -0
- package/dist/src/memory/durableMemoryStore.d.ts.map +1 -0
- package/dist/src/memory/durableMemoryStore.js +194 -0
- package/dist/src/memory/durableMemoryStore.js.map +1 -0
- package/dist/src/memory/hadamardDream.d.ts +18 -0
- package/dist/src/memory/hadamardDream.d.ts.map +1 -1
- package/dist/src/memory/hadamardDream.js +119 -39
- package/dist/src/memory/hadamardDream.js.map +1 -1
- package/dist/src/memory/hadamardMemory.d.ts +20 -39
- package/dist/src/memory/hadamardMemory.d.ts.map +1 -1
- package/dist/src/memory/hadamardMemory.js +112 -450
- package/dist/src/memory/hadamardMemory.js.map +1 -1
- package/dist/src/memory/hadamardSessionMemoryState.d.ts +2 -5
- package/dist/src/memory/hadamardSessionMemoryState.d.ts.map +1 -1
- package/dist/src/memory/hadamardSessionMemoryState.js +5 -61
- package/dist/src/memory/hadamardSessionMemoryState.js.map +1 -1
- package/dist/src/memory/memoryCommandService.d.ts +25 -0
- package/dist/src/memory/memoryCommandService.d.ts.map +1 -0
- package/dist/src/memory/memoryCommandService.js +93 -0
- package/dist/src/memory/memoryCommandService.js.map +1 -0
- package/dist/src/memory/memoryProposalCommandService.d.ts.map +1 -1
- package/dist/src/memory/memoryProposalCommandService.js +2 -1
- package/dist/src/memory/memoryProposalCommandService.js.map +1 -1
- package/dist/src/memory/memoryProposalTools.d.ts +1 -0
- package/dist/src/memory/memoryProposalTools.d.ts.map +1 -1
- package/dist/src/memory/memoryProposalTools.js +16 -11
- package/dist/src/memory/memoryProposalTools.js.map +1 -1
- package/dist/src/parity/bridgeConfigs.d.ts +4 -0
- package/dist/src/parity/bridgeConfigs.d.ts.map +1 -1
- package/dist/src/parity/bridgeConfigs.js +30 -1
- package/dist/src/parity/bridgeConfigs.js.map +1 -1
- package/dist/src/router/modelRouter.d.ts +22 -2
- package/dist/src/router/modelRouter.d.ts.map +1 -1
- package/dist/src/router/modelRouter.js +76 -9
- package/dist/src/router/modelRouter.js.map +1 -1
- package/dist/src/runtime/agentClient.d.ts +24 -4
- package/dist/src/runtime/agentClient.d.ts.map +1 -1
- package/dist/src/runtime/agentClient.js +581 -262
- package/dist/src/runtime/agentClient.js.map +1 -1
- package/dist/src/runtime/agentSession.d.ts +8 -3
- package/dist/src/runtime/agentSession.d.ts.map +1 -1
- package/dist/src/runtime/agentSession.js +10 -3
- package/dist/src/runtime/agentSession.js.map +1 -1
- package/dist/src/runtime/agentTemplates.d.ts +28 -0
- package/dist/src/runtime/agentTemplates.d.ts.map +1 -0
- package/dist/src/runtime/agentTemplates.js +73 -0
- package/dist/src/runtime/agentTemplates.js.map +1 -0
- package/dist/src/runtime/conversationEngine.d.ts +4 -0
- package/dist/src/runtime/conversationEngine.d.ts.map +1 -1
- package/dist/src/runtime/conversationEngine.js +52 -3
- package/dist/src/runtime/conversationEngine.js.map +1 -1
- package/dist/src/runtime/defaultHadamardAgents.d.ts.map +1 -1
- package/dist/src/runtime/defaultHadamardAgents.js +9 -47
- package/dist/src/runtime/defaultHadamardAgents.js.map +1 -1
- package/dist/src/runtime/effectiveAgentRunOptions.d.ts +49 -0
- package/dist/src/runtime/effectiveAgentRunOptions.d.ts.map +1 -0
- package/dist/src/runtime/effectiveAgentRunOptions.js +50 -0
- package/dist/src/runtime/effectiveAgentRunOptions.js.map +1 -0
- package/dist/src/runtime/externalAgentRunner.d.ts +25 -0
- package/dist/src/runtime/externalAgentRunner.d.ts.map +1 -0
- package/dist/src/runtime/externalAgentRunner.js +57 -0
- package/dist/src/runtime/externalAgentRunner.js.map +1 -0
- package/dist/src/runtime/hadamardAgentDefinitions.d.ts +15 -0
- package/dist/src/runtime/hadamardAgentDefinitions.d.ts.map +1 -1
- package/dist/src/runtime/hadamardAgentDefinitions.js +42 -2
- package/dist/src/runtime/hadamardAgentDefinitions.js.map +1 -1
- package/dist/src/runtime/hadamardAgents.d.ts +6 -0
- package/dist/src/runtime/hadamardAgents.d.ts.map +1 -1
- package/dist/src/runtime/hadamardAgents.js +52 -1
- package/dist/src/runtime/hadamardAgents.js.map +1 -1
- package/dist/src/runtime/hadamardCompact.d.ts +12 -1
- package/dist/src/runtime/hadamardCompact.d.ts.map +1 -1
- package/dist/src/runtime/hadamardCompact.js +91 -35
- package/dist/src/runtime/hadamardCompact.js.map +1 -1
- package/dist/src/runtime/hadamardPermissions.d.ts +1 -0
- package/dist/src/runtime/hadamardPermissions.d.ts.map +1 -1
- package/dist/src/runtime/hadamardPermissions.js +6 -0
- package/dist/src/runtime/hadamardPermissions.js.map +1 -1
- package/dist/src/runtime/hadamardSlashCommands.d.ts +7 -1
- package/dist/src/runtime/hadamardSlashCommands.d.ts.map +1 -1
- package/dist/src/runtime/hadamardSlashCommands.js +47 -5
- package/dist/src/runtime/hadamardSlashCommands.js.map +1 -1
- package/dist/src/runtime/tools.d.ts.map +1 -1
- package/dist/src/runtime/tools.js +2 -0
- package/dist/src/runtime/tools.js.map +1 -1
- package/dist/src/team/executorTarget.d.ts +26 -0
- package/dist/src/team/executorTarget.d.ts.map +1 -0
- package/dist/src/team/executorTarget.js +15 -0
- package/dist/src/team/executorTarget.js.map +1 -0
- package/dist/src/team/modelTeam.d.ts +5 -1
- package/dist/src/team/modelTeam.d.ts.map +1 -1
- package/dist/src/team/modelTeam.js +157 -41
- package/dist/src/team/modelTeam.js.map +1 -1
- package/dist/src/team/teamComposition.d.ts +20 -0
- package/dist/src/team/teamComposition.d.ts.map +1 -0
- package/dist/src/team/teamComposition.js +78 -0
- package/dist/src/team/teamComposition.js.map +1 -0
- package/dist/src/team/teamDefinitions.d.ts +10 -1
- package/dist/src/team/teamDefinitions.d.ts.map +1 -1
- package/dist/src/team/teamDefinitions.js +123 -16
- package/dist/src/team/teamDefinitions.js.map +1 -1
- package/dist/src/team/teamGraph.d.ts +1 -1
- package/dist/src/team/teamGraph.d.ts.map +1 -1
- package/dist/src/team/teamGraph.js +1 -1
- package/dist/src/team/teamGraph.js.map +1 -1
- package/dist/src/team/teamPrompts.d.ts.map +1 -1
- package/dist/src/team/teamPrompts.js +0 -4
- package/dist/src/team/teamPrompts.js.map +1 -1
- package/dist/src/team/teamProposalService.d.ts +9 -1
- package/dist/src/team/teamProposalService.d.ts.map +1 -1
- package/dist/src/team/teamProposalService.js +66 -6
- package/dist/src/team/teamProposalService.js.map +1 -1
- package/dist/src/team/teamPropose.d.ts +3 -1
- package/dist/src/team/teamPropose.d.ts.map +1 -1
- package/dist/src/team/teamPropose.js +16 -10
- package/dist/src/team/teamPropose.js.map +1 -1
- package/dist/src/team/teamRuntime.d.ts +5 -0
- package/dist/src/team/teamRuntime.d.ts.map +1 -1
- package/dist/src/team/teamRuntime.js +23 -5
- package/dist/src/team/teamRuntime.js.map +1 -1
- package/dist/src/team/workflowSquad.d.ts +10 -2
- package/dist/src/team/workflowSquad.d.ts.map +1 -1
- package/dist/src/team/workflowSquad.js +250 -12
- package/dist/src/team/workflowSquad.js.map +1 -1
- package/dist/src/tools/enterWorktree.d.ts.map +1 -1
- package/dist/src/tools/enterWorktree.js +3 -0
- package/dist/src/tools/enterWorktree.js.map +1 -1
- package/dist/src/tools/exaSearch.d.ts +2 -2
- package/dist/src/tools/exitWorktree.d.ts.map +1 -1
- package/dist/src/tools/exitWorktree.js +3 -1
- package/dist/src/tools/exitWorktree.js.map +1 -1
- package/dist/src/tools/hadamardCoreTools.d.ts +5 -14
- package/dist/src/tools/hadamardCoreTools.d.ts.map +1 -1
- package/dist/src/tools/hadamardCoreTools.js +31 -0
- package/dist/src/tools/hadamardCoreTools.js.map +1 -1
- package/dist/src/tools/hadamardFileTools.d.ts.map +1 -1
- package/dist/src/tools/hadamardFileTools.js +116 -38
- package/dist/src/tools/hadamardFileTools.js.map +1 -1
- package/dist/src/tools/planMode/PlanModeTools.d.ts +22 -0
- package/dist/src/tools/planMode/PlanModeTools.d.ts.map +1 -1
- package/dist/src/tools/planMode/PlanModeTools.js +102 -5
- package/dist/src/tools/planMode/PlanModeTools.js.map +1 -1
- package/dist/src/tools/todo/TodoWriteTool.d.ts +1 -1
- package/dist/src/tui/hadamardTui.d.ts.map +1 -1
- package/dist/src/tui/hadamardTui.js +387 -168
- package/dist/src/tui/hadamardTui.js.map +1 -1
- package/dist/src/tui/pendingInput.d.ts +17 -0
- package/dist/src/tui/pendingInput.d.ts.map +1 -0
- package/dist/src/tui/pendingInput.js +23 -0
- package/dist/src/tui/pendingInput.js.map +1 -0
- package/dist/src/tui/reasoningDisplay.d.ts +13 -0
- package/dist/src/tui/reasoningDisplay.d.ts.map +1 -0
- package/dist/src/tui/reasoningDisplay.js +45 -0
- package/dist/src/tui/reasoningDisplay.js.map +1 -0
- package/dist/src/tui/screen.d.ts +11 -1
- package/dist/src/tui/screen.d.ts.map +1 -1
- package/dist/src/tui/screen.js +64 -34
- package/dist/src/tui/screen.js.map +1 -1
- package/dist/src/types.d.ts +210 -77
- package/dist/src/types.d.ts.map +1 -1
- package/dist/src/types.js.map +1 -1
- package/dist/src/ui/commandSurface.d.ts +8 -0
- package/dist/src/ui/commandSurface.d.ts.map +1 -1
- package/dist/src/ui/commandSurface.js +78 -13
- package/dist/src/ui/commandSurface.js.map +1 -1
- package/dist/src/update/appUpdateService.d.ts +34 -1
- package/dist/src/update/appUpdateService.d.ts.map +1 -1
- package/dist/src/update/appUpdateService.js +114 -9
- package/dist/src/update/appUpdateService.js.map +1 -1
- package/package.json +3 -2
- package/dist/src/prompts/outputStyles.d.ts +0 -20
- package/dist/src/prompts/outputStyles.d.ts.map +0 -1
- package/dist/src/prompts/outputStyles.js +0 -35
- package/dist/src/prompts/outputStyles.js.map +0 -1
|
@@ -0,0 +1,808 @@
|
|
|
1
|
+
import { mkdir } from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { nodeSqliteDriverFactory } from '../storage-v2/sqliteDriver.js';
|
|
4
|
+
import { createId } from '../runtime/helpers.js';
|
|
5
|
+
import { normalizeGoal } from './goalStore.js';
|
|
6
|
+
export const PROJECT_GOAL_DATABASE = 'goal-state.sqlite';
|
|
7
|
+
/** Project-scoped durable store for Goal snapshots, audit events, and session attachments. */
|
|
8
|
+
export class ProjectGoalStore {
|
|
9
|
+
driver;
|
|
10
|
+
constructor(driver) {
|
|
11
|
+
this.driver = driver;
|
|
12
|
+
}
|
|
13
|
+
static async open(projectStateDirectory) {
|
|
14
|
+
await mkdir(projectStateDirectory, { recursive: true });
|
|
15
|
+
const driver = await nodeSqliteDriverFactory.open(path.join(projectStateDirectory, PROJECT_GOAL_DATABASE));
|
|
16
|
+
initializeSchema(driver);
|
|
17
|
+
return new ProjectGoalStore(driver);
|
|
18
|
+
}
|
|
19
|
+
close() {
|
|
20
|
+
this.driver.close();
|
|
21
|
+
}
|
|
22
|
+
createGoalId() {
|
|
23
|
+
return `goal:${createId()}`;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Goals are session-scoped: a session only sees the goal it is attached to, so
|
|
27
|
+
* a new chat starts without one instead of inheriting the project's last goal.
|
|
28
|
+
*/
|
|
29
|
+
portForSession(sessionId, options = {}) {
|
|
30
|
+
const attached = options.forceNew ? undefined : this.goalIdForSession(sessionId);
|
|
31
|
+
return new ProjectGoalSessionPort(this, sessionId, attached);
|
|
32
|
+
}
|
|
33
|
+
importLegacyGoal(sessionId, raw, now = new Date().toISOString()) {
|
|
34
|
+
if (this.goalIdForSession(sessionId))
|
|
35
|
+
return this.goalIdForSession(sessionId);
|
|
36
|
+
const goal = normalizeGoal(raw, now);
|
|
37
|
+
if (!goal)
|
|
38
|
+
return undefined;
|
|
39
|
+
const goalId = this.createGoalId();
|
|
40
|
+
this.writeSnapshot(goalId, goal, 'legacy_imported');
|
|
41
|
+
this.attachSession(sessionId, goalId, now);
|
|
42
|
+
return goalId;
|
|
43
|
+
}
|
|
44
|
+
readForSession(sessionId) {
|
|
45
|
+
const goalId = this.goalIdForSession(sessionId);
|
|
46
|
+
if (!goalId)
|
|
47
|
+
return undefined;
|
|
48
|
+
const goal = this.readSnapshot(goalId);
|
|
49
|
+
return goal ? { id: goalId, goal } : undefined;
|
|
50
|
+
}
|
|
51
|
+
listGoals(options = {}) {
|
|
52
|
+
const rows = this.driver.prepare(`
|
|
53
|
+
SELECT goal_id, objective, status, revision, created_at, updated_at
|
|
54
|
+
FROM goals
|
|
55
|
+
${options.includeArchived ? '' : 'WHERE archived_at IS NULL'}
|
|
56
|
+
ORDER BY updated_at DESC
|
|
57
|
+
LIMIT ?
|
|
58
|
+
`).all(Math.max(1, Math.min(options.limit ?? 100, 500)));
|
|
59
|
+
const attachments = this.driver.prepare('SELECT session_id FROM goal_sessions WHERE goal_id = ? ORDER BY attached_at ASC');
|
|
60
|
+
return rows.map(row => ({
|
|
61
|
+
id: String(row.goal_id),
|
|
62
|
+
objective: String(row.objective),
|
|
63
|
+
status: String(row.status),
|
|
64
|
+
revision: Number(row.revision),
|
|
65
|
+
createdAt: String(row.created_at),
|
|
66
|
+
updatedAt: String(row.updated_at),
|
|
67
|
+
attachedSessionIds: attachments.all(String(row.goal_id)).map(item => String(item.session_id)),
|
|
68
|
+
}));
|
|
69
|
+
}
|
|
70
|
+
history(goalId, limit = 100) {
|
|
71
|
+
return this.driver.prepare(`
|
|
72
|
+
SELECT event_id, goal_id, event_type, revision, payload_json, created_at
|
|
73
|
+
FROM goal_events
|
|
74
|
+
WHERE goal_id = ?
|
|
75
|
+
ORDER BY event_id DESC
|
|
76
|
+
LIMIT ?
|
|
77
|
+
`).all(goalId, Math.max(1, Math.min(limit, 500))).reverse().map(row => ({
|
|
78
|
+
id: Number(row.event_id),
|
|
79
|
+
goalId: String(row.goal_id),
|
|
80
|
+
type: String(row.event_type),
|
|
81
|
+
at: String(row.created_at),
|
|
82
|
+
...(typeof row.revision === 'number' ? { revision: row.revision } : {}),
|
|
83
|
+
...(typeof row.payload_json === 'string'
|
|
84
|
+
? { payload: parseObject(row.payload_json) }
|
|
85
|
+
: {}),
|
|
86
|
+
}));
|
|
87
|
+
}
|
|
88
|
+
continuationState(goalId) {
|
|
89
|
+
const row = this.driver.prepare(`
|
|
90
|
+
SELECT c.*, (
|
|
91
|
+
SELECT session_id FROM goal_sessions s
|
|
92
|
+
WHERE s.goal_id = c.goal_id ORDER BY attached_at DESC LIMIT 1
|
|
93
|
+
) AS session_id
|
|
94
|
+
FROM goal_continuation c WHERE c.goal_id = ?
|
|
95
|
+
`).get(goalId);
|
|
96
|
+
return row ? mapContinuation(row) : undefined;
|
|
97
|
+
}
|
|
98
|
+
listScheduledContinuations() {
|
|
99
|
+
return this.driver.prepare(`
|
|
100
|
+
SELECT c.*, (
|
|
101
|
+
SELECT session_id FROM goal_sessions s
|
|
102
|
+
WHERE s.goal_id = c.goal_id ORDER BY attached_at DESC LIMIT 1
|
|
103
|
+
) AS session_id
|
|
104
|
+
FROM goal_continuation c
|
|
105
|
+
JOIN goals g ON g.goal_id = c.goal_id
|
|
106
|
+
WHERE c.mode = 'scheduled' AND g.archived_at IS NULL AND g.status = 'active'
|
|
107
|
+
ORDER BY COALESCE(c.next_wake_at, g.updated_at) ASC
|
|
108
|
+
`).all().map(mapContinuation);
|
|
109
|
+
}
|
|
110
|
+
configureContinuation(input) {
|
|
111
|
+
const now = input.now ?? new Date();
|
|
112
|
+
const min = clampSeconds(input.minIntervalSeconds ?? 30, 1, 86_400);
|
|
113
|
+
const max = clampSeconds(input.maxIntervalSeconds ?? 1_800, min, 604_800);
|
|
114
|
+
const current = this.continuationState(input.goalId);
|
|
115
|
+
const interval = Math.max(min, Math.min(current?.currentIntervalSeconds ?? min, max));
|
|
116
|
+
const nextWakeAt = input.mode === 'scheduled'
|
|
117
|
+
? new Date(now.getTime() + interval * 1_000).toISOString()
|
|
118
|
+
: null;
|
|
119
|
+
this.driver.prepare(`
|
|
120
|
+
INSERT INTO goal_continuation (
|
|
121
|
+
goal_id, mode, execution_profile_json, min_interval_seconds, max_interval_seconds,
|
|
122
|
+
current_interval_seconds, next_wake_at, consecutive_no_change, updated_at
|
|
123
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, 0, ?)
|
|
124
|
+
ON CONFLICT(goal_id) DO UPDATE SET
|
|
125
|
+
mode = excluded.mode,
|
|
126
|
+
execution_profile_json = excluded.execution_profile_json,
|
|
127
|
+
min_interval_seconds = excluded.min_interval_seconds,
|
|
128
|
+
max_interval_seconds = excluded.max_interval_seconds,
|
|
129
|
+
current_interval_seconds = excluded.current_interval_seconds,
|
|
130
|
+
next_wake_at = excluded.next_wake_at,
|
|
131
|
+
updated_at = excluded.updated_at,
|
|
132
|
+
last_error = NULL
|
|
133
|
+
`).run(input.goalId, input.mode, input.executionProfile ? JSON.stringify(input.executionProfile) : null, min, max, interval, nextWakeAt, now.toISOString());
|
|
134
|
+
this.insertEvent(input.goalId, 'continuation_configured', now.toISOString(), undefined, {
|
|
135
|
+
mode: input.mode,
|
|
136
|
+
...(input.executionProfile ? { executionProfile: input.executionProfile } : {}),
|
|
137
|
+
});
|
|
138
|
+
return this.continuationState(input.goalId);
|
|
139
|
+
}
|
|
140
|
+
acquireContinuationLease(goalId, owner, now = new Date(), leaseMs = 15 * 60_000) {
|
|
141
|
+
return this.driver.transaction(() => {
|
|
142
|
+
const state = this.continuationState(goalId);
|
|
143
|
+
if (!state)
|
|
144
|
+
return false;
|
|
145
|
+
if (state.leaseOwner && isFuture(state.leaseExpiresAt, now))
|
|
146
|
+
return false;
|
|
147
|
+
const result = this.driver.prepare(`
|
|
148
|
+
UPDATE goal_continuation SET lease_owner = ?, lease_expires_at = ?, updated_at = ?
|
|
149
|
+
WHERE goal_id = ?
|
|
150
|
+
`).run(owner, new Date(now.getTime() + leaseMs).toISOString(), now.toISOString(), goalId);
|
|
151
|
+
return result.changes === 1;
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
settleContinuation(input) {
|
|
155
|
+
const now = input.now ?? new Date();
|
|
156
|
+
const current = this.continuationState(input.goalId);
|
|
157
|
+
if (!current || current.leaseOwner !== input.owner)
|
|
158
|
+
return current;
|
|
159
|
+
// Replan attempts are not "no progress" — only true stalls back off.
|
|
160
|
+
const noChange = input.outcome === 'no_change'
|
|
161
|
+
|| input.outcome === 'failed'
|
|
162
|
+
|| input.outcome === 'interrupted'
|
|
163
|
+
|| input.outcome === 'validation_failed'
|
|
164
|
+
|| Boolean(input.error);
|
|
165
|
+
const interval = noChange
|
|
166
|
+
? Math.min(current.maxIntervalSeconds, Math.max(current.minIntervalSeconds, current.currentIntervalSeconds * 2))
|
|
167
|
+
: Math.max(current.minIntervalSeconds, Math.floor(current.currentIntervalSeconds / 2));
|
|
168
|
+
const nextWakeAt = current.mode === 'scheduled'
|
|
169
|
+
? new Date(now.getTime() + interval * 1_000).toISOString()
|
|
170
|
+
: null;
|
|
171
|
+
this.driver.prepare(`
|
|
172
|
+
UPDATE goal_continuation SET
|
|
173
|
+
current_interval_seconds = ?, next_wake_at = ?, last_wake_at = ?, last_outcome = ?,
|
|
174
|
+
consecutive_no_change = ?, lease_owner = NULL, lease_expires_at = NULL,
|
|
175
|
+
last_error = ?, updated_at = ?
|
|
176
|
+
WHERE goal_id = ? AND lease_owner = ?
|
|
177
|
+
`).run(interval, nextWakeAt, now.toISOString(), input.outcome, noChange ? current.consecutiveNoChange + 1 : 0, input.error?.slice(0, 4_000) ?? null, now.toISOString(), input.goalId, input.owner);
|
|
178
|
+
this.insertEvent(input.goalId, input.error ? 'continuation_failed' : 'continuation_settled', now.toISOString(), undefined, {
|
|
179
|
+
outcome: input.outcome,
|
|
180
|
+
intervalSeconds: interval,
|
|
181
|
+
...(input.error ? { error: input.error.slice(0, 4_000) } : {}),
|
|
182
|
+
});
|
|
183
|
+
return this.continuationState(input.goalId);
|
|
184
|
+
}
|
|
185
|
+
claimNextWork(input) {
|
|
186
|
+
const now = input.now ?? new Date();
|
|
187
|
+
const roleScopes = unique(input.roleScopes ?? []);
|
|
188
|
+
return this.driver.transaction(() => {
|
|
189
|
+
const goal = this.readSnapshot(input.goalId);
|
|
190
|
+
if (!goal || goal.status !== 'active')
|
|
191
|
+
return undefined;
|
|
192
|
+
const completed = new Set(goal.workItems
|
|
193
|
+
.filter(item => item.status === 'done' || item.status === 'cancelled')
|
|
194
|
+
.map(item => item.id));
|
|
195
|
+
const claims = new Map(this.driver.prepare(`
|
|
196
|
+
SELECT * FROM goal_work_claims WHERE goal_id = ? AND lease_expires_at > ?
|
|
197
|
+
`).all(input.goalId, now.toISOString()).map(row => [String(row.work_item_id), row]));
|
|
198
|
+
const candidate = [...goal.workItems]
|
|
199
|
+
.filter(item => (item.role === 'agent'
|
|
200
|
+
&& ['open', 'claimed', 'running'].includes(item.status)
|
|
201
|
+
&& item.dependsOn.every(id => completed.has(id))
|
|
202
|
+
&& !claims.has(item.id)
|
|
203
|
+
&& !(item.excludedAgentIds ?? []).includes(input.agentId)
|
|
204
|
+
&& ((item.roleScopes ?? []).length === 0
|
|
205
|
+
|| (item.roleScopes ?? []).some(scope => roleScopes.includes(scope)))))
|
|
206
|
+
.sort(compareGoalWorkItems)[0];
|
|
207
|
+
if (!candidate)
|
|
208
|
+
return undefined;
|
|
209
|
+
const claimedAt = now.toISOString();
|
|
210
|
+
const claim = {
|
|
211
|
+
goalId: input.goalId,
|
|
212
|
+
workItemId: candidate.id,
|
|
213
|
+
agentId: input.agentId,
|
|
214
|
+
claimToken: `goal-claim:${createId()}`,
|
|
215
|
+
roleScopes,
|
|
216
|
+
claimedAt,
|
|
217
|
+
leaseExpiresAt: new Date(now.getTime() + Math.max(1_000, input.leaseMs ?? 15 * 60_000)).toISOString(),
|
|
218
|
+
updatedAt: claimedAt,
|
|
219
|
+
};
|
|
220
|
+
this.driver.prepare(`
|
|
221
|
+
INSERT INTO goal_work_claims (
|
|
222
|
+
goal_id, work_item_id, agent_id, claim_token, role_scopes_json,
|
|
223
|
+
claimed_at, lease_expires_at, updated_at
|
|
224
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
225
|
+
ON CONFLICT(goal_id, work_item_id) DO UPDATE SET
|
|
226
|
+
agent_id = excluded.agent_id,
|
|
227
|
+
claim_token = excluded.claim_token,
|
|
228
|
+
role_scopes_json = excluded.role_scopes_json,
|
|
229
|
+
claimed_at = excluded.claimed_at,
|
|
230
|
+
lease_expires_at = excluded.lease_expires_at,
|
|
231
|
+
updated_at = excluded.updated_at
|
|
232
|
+
WHERE goal_work_claims.lease_expires_at <= ?
|
|
233
|
+
`).run(claim.goalId, claim.workItemId, claim.agentId, claim.claimToken, JSON.stringify(claim.roleScopes), claim.claimedAt, claim.leaseExpiresAt, claim.updatedAt, claimedAt);
|
|
234
|
+
this.insertEvent(input.goalId, 'work_claimed', claimedAt, undefined, {
|
|
235
|
+
workItemId: candidate.id,
|
|
236
|
+
agentId: input.agentId,
|
|
237
|
+
leaseExpiresAt: claim.leaseExpiresAt,
|
|
238
|
+
});
|
|
239
|
+
return claim;
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
renewWorkClaim(claimToken, leaseMs = 15 * 60_000, now = new Date()) {
|
|
243
|
+
const row = this.driver.prepare('SELECT * FROM goal_work_claims WHERE claim_token = ? AND lease_expires_at > ?').get(claimToken, now.toISOString());
|
|
244
|
+
if (!row)
|
|
245
|
+
return undefined;
|
|
246
|
+
const leaseExpiresAt = new Date(now.getTime() + Math.max(1_000, leaseMs)).toISOString();
|
|
247
|
+
this.driver.prepare(`
|
|
248
|
+
UPDATE goal_work_claims SET lease_expires_at = ?, updated_at = ? WHERE claim_token = ?
|
|
249
|
+
`).run(leaseExpiresAt, now.toISOString(), claimToken);
|
|
250
|
+
return mapWorkClaim({ ...row, lease_expires_at: leaseExpiresAt, updated_at: now.toISOString() });
|
|
251
|
+
}
|
|
252
|
+
markClaimRunning(claimToken, now = new Date()) {
|
|
253
|
+
const row = this.driver.prepare('SELECT goal_id, work_item_id FROM goal_work_claims WHERE claim_token = ? AND lease_expires_at > ?').get(claimToken, now.toISOString());
|
|
254
|
+
if (!row)
|
|
255
|
+
return false;
|
|
256
|
+
let changed = false;
|
|
257
|
+
this.updateSnapshot(String(row.goal_id), raw => {
|
|
258
|
+
const goal = normalizeGoal(raw, now.toISOString());
|
|
259
|
+
if (!goal)
|
|
260
|
+
return undefined;
|
|
261
|
+
const workItems = goal.workItems.map(item => {
|
|
262
|
+
if (item.id !== String(row.work_item_id))
|
|
263
|
+
return item;
|
|
264
|
+
changed = true;
|
|
265
|
+
return { ...item, status: 'running', updatedAt: now.toISOString() };
|
|
266
|
+
});
|
|
267
|
+
return changed ? {
|
|
268
|
+
...goal,
|
|
269
|
+
workItems,
|
|
270
|
+
updatedAt: now.toISOString(),
|
|
271
|
+
revision: goal.revision + 1,
|
|
272
|
+
} : goal;
|
|
273
|
+
});
|
|
274
|
+
return changed;
|
|
275
|
+
}
|
|
276
|
+
releaseWorkClaim(claimToken, reason = 'released', now = new Date()) {
|
|
277
|
+
const row = this.driver.prepare('SELECT goal_id, work_item_id, agent_id FROM goal_work_claims WHERE claim_token = ?').get(claimToken);
|
|
278
|
+
if (!row)
|
|
279
|
+
return false;
|
|
280
|
+
const result = this.driver.prepare('DELETE FROM goal_work_claims WHERE claim_token = ?').run(claimToken);
|
|
281
|
+
if (result.changes === 1) {
|
|
282
|
+
this.updateSnapshot(String(row.goal_id), raw => {
|
|
283
|
+
const goal = normalizeGoal(raw, now.toISOString());
|
|
284
|
+
if (!goal)
|
|
285
|
+
return undefined;
|
|
286
|
+
let changed = false;
|
|
287
|
+
const workItems = goal.workItems.map(item => {
|
|
288
|
+
if (item.id !== String(row.work_item_id))
|
|
289
|
+
return item;
|
|
290
|
+
if (item.status !== 'running' && item.status !== 'claimed')
|
|
291
|
+
return item;
|
|
292
|
+
changed = true;
|
|
293
|
+
return { ...item, status: 'open', updatedAt: now.toISOString() };
|
|
294
|
+
});
|
|
295
|
+
return changed ? {
|
|
296
|
+
...goal,
|
|
297
|
+
workItems,
|
|
298
|
+
updatedAt: now.toISOString(),
|
|
299
|
+
revision: goal.revision + 1,
|
|
300
|
+
} : goal;
|
|
301
|
+
});
|
|
302
|
+
this.insertEvent(String(row.goal_id), 'work_claim_released', now.toISOString(), undefined, {
|
|
303
|
+
workItemId: String(row.work_item_id),
|
|
304
|
+
agentId: String(row.agent_id),
|
|
305
|
+
reason,
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
return result.changes === 1;
|
|
309
|
+
}
|
|
310
|
+
/** Atomically claim a specific work item if it is runnable and unclaimed. */
|
|
311
|
+
claimWorkItem(input) {
|
|
312
|
+
const now = input.now ?? new Date();
|
|
313
|
+
const roleScopes = unique(input.roleScopes ?? []);
|
|
314
|
+
return this.driver.transaction(() => {
|
|
315
|
+
const goal = this.readSnapshot(input.goalId);
|
|
316
|
+
if (!goal || goal.status !== 'active')
|
|
317
|
+
return undefined;
|
|
318
|
+
const completed = new Set(goal.workItems
|
|
319
|
+
.filter(item => item.status === 'done' || item.status === 'cancelled')
|
|
320
|
+
.map(item => item.id));
|
|
321
|
+
const existing = this.driver.prepare(`
|
|
322
|
+
SELECT * FROM goal_work_claims WHERE goal_id = ? AND work_item_id = ? AND lease_expires_at > ?
|
|
323
|
+
`).get(input.goalId, input.workItemId, now.toISOString());
|
|
324
|
+
if (existing) {
|
|
325
|
+
return String(existing.agent_id) === input.agentId ? mapWorkClaim(existing) : undefined;
|
|
326
|
+
}
|
|
327
|
+
const item = goal.workItems.find(candidate => candidate.id === input.workItemId);
|
|
328
|
+
if (!item
|
|
329
|
+
|| item.role !== 'agent'
|
|
330
|
+
|| !['open', 'claimed', 'running'].includes(item.status)
|
|
331
|
+
|| !item.dependsOn.every(id => completed.has(id))
|
|
332
|
+
|| (item.excludedAgentIds ?? []).includes(input.agentId)) {
|
|
333
|
+
return undefined;
|
|
334
|
+
}
|
|
335
|
+
// Empty roleScopes = session owner / unrestricted claimant.
|
|
336
|
+
if (roleScopes.length > 0
|
|
337
|
+
&& (item.roleScopes ?? []).length > 0
|
|
338
|
+
&& !(item.roleScopes ?? []).some(scope => roleScopes.includes(scope))) {
|
|
339
|
+
return undefined;
|
|
340
|
+
}
|
|
341
|
+
const claimedAt = now.toISOString();
|
|
342
|
+
const claim = {
|
|
343
|
+
goalId: input.goalId,
|
|
344
|
+
workItemId: item.id,
|
|
345
|
+
agentId: input.agentId,
|
|
346
|
+
claimToken: `goal-claim:${createId()}`,
|
|
347
|
+
roleScopes,
|
|
348
|
+
claimedAt,
|
|
349
|
+
leaseExpiresAt: new Date(now.getTime() + Math.max(1_000, input.leaseMs ?? 15 * 60_000)).toISOString(),
|
|
350
|
+
updatedAt: claimedAt,
|
|
351
|
+
};
|
|
352
|
+
const result = this.driver.prepare(`
|
|
353
|
+
INSERT INTO goal_work_claims (
|
|
354
|
+
goal_id, work_item_id, agent_id, claim_token, role_scopes_json,
|
|
355
|
+
claimed_at, lease_expires_at, updated_at
|
|
356
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
357
|
+
ON CONFLICT(goal_id, work_item_id) DO UPDATE SET
|
|
358
|
+
agent_id = excluded.agent_id,
|
|
359
|
+
claim_token = excluded.claim_token,
|
|
360
|
+
role_scopes_json = excluded.role_scopes_json,
|
|
361
|
+
claimed_at = excluded.claimed_at,
|
|
362
|
+
lease_expires_at = excluded.lease_expires_at,
|
|
363
|
+
updated_at = excluded.updated_at
|
|
364
|
+
WHERE goal_work_claims.lease_expires_at <= ?
|
|
365
|
+
`).run(claim.goalId, claim.workItemId, claim.agentId, claim.claimToken, JSON.stringify(claim.roleScopes), claim.claimedAt, claim.leaseExpiresAt, claim.updatedAt, claimedAt);
|
|
366
|
+
if (result.changes !== 1)
|
|
367
|
+
return undefined;
|
|
368
|
+
this.insertEvent(input.goalId, 'work_claimed', claimedAt, undefined, {
|
|
369
|
+
workItemId: claim.workItemId,
|
|
370
|
+
agentId: input.agentId,
|
|
371
|
+
leaseExpiresAt: claim.leaseExpiresAt,
|
|
372
|
+
});
|
|
373
|
+
return claim;
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
completeWorkClaim(claimToken, evidenceRefs, now = new Date()) {
|
|
377
|
+
const refs = unique(evidenceRefs);
|
|
378
|
+
if (refs.length === 0)
|
|
379
|
+
return false;
|
|
380
|
+
const row = this.driver.prepare('SELECT goal_id, work_item_id, agent_id FROM goal_work_claims WHERE claim_token = ? AND lease_expires_at > ?').get(claimToken, now.toISOString());
|
|
381
|
+
if (!row)
|
|
382
|
+
return false;
|
|
383
|
+
let changed = false;
|
|
384
|
+
this.updateSnapshot(String(row.goal_id), raw => {
|
|
385
|
+
const goal = normalizeGoal(raw, now.toISOString());
|
|
386
|
+
if (!goal)
|
|
387
|
+
return undefined;
|
|
388
|
+
const workItems = goal.workItems.map(item => {
|
|
389
|
+
if (item.id !== String(row.work_item_id) || item.status === 'done')
|
|
390
|
+
return item;
|
|
391
|
+
changed = true;
|
|
392
|
+
return {
|
|
393
|
+
...item,
|
|
394
|
+
status: 'done',
|
|
395
|
+
evidenceRefs: unique([...item.evidenceRefs, ...refs]),
|
|
396
|
+
updatedAt: now.toISOString(),
|
|
397
|
+
};
|
|
398
|
+
});
|
|
399
|
+
return changed ? {
|
|
400
|
+
...goal,
|
|
401
|
+
workItems,
|
|
402
|
+
delivery: {
|
|
403
|
+
...goal.delivery,
|
|
404
|
+
completedWorkItems: goal.delivery.completedWorkItems + 1,
|
|
405
|
+
},
|
|
406
|
+
planRevision: goal.planRevision + 1,
|
|
407
|
+
updatedAt: now.toISOString(),
|
|
408
|
+
revision: goal.revision + 1,
|
|
409
|
+
} : goal;
|
|
410
|
+
});
|
|
411
|
+
if (!changed)
|
|
412
|
+
return false;
|
|
413
|
+
this.driver.prepare('DELETE FROM goal_work_claims WHERE claim_token = ?').run(claimToken);
|
|
414
|
+
this.insertEvent(String(row.goal_id), 'work_claim_completed', now.toISOString(), undefined, {
|
|
415
|
+
workItemId: String(row.work_item_id),
|
|
416
|
+
agentId: String(row.agent_id),
|
|
417
|
+
evidenceRefs: refs,
|
|
418
|
+
});
|
|
419
|
+
return true;
|
|
420
|
+
}
|
|
421
|
+
handoffWork(input) {
|
|
422
|
+
const now = input.now ?? new Date();
|
|
423
|
+
return this.driver.transaction(() => {
|
|
424
|
+
const row = this.driver.prepare('SELECT * FROM goal_work_claims WHERE claim_token = ? AND lease_expires_at > ?').get(input.claimToken, now.toISOString());
|
|
425
|
+
if (!row)
|
|
426
|
+
return undefined;
|
|
427
|
+
const evidenceRefs = unique(input.evidenceRefs ?? []);
|
|
428
|
+
const goal = this.readSnapshot(String(row.goal_id));
|
|
429
|
+
const workItem = goal?.workItems.find(item => item.id === String(row.work_item_id));
|
|
430
|
+
const toAgentRoleScopes = unique(input.toAgentRoleScopes ?? []);
|
|
431
|
+
if (input.toAgentId
|
|
432
|
+
&& (workItem?.excludedAgentIds?.includes(input.toAgentId)
|
|
433
|
+
|| ((workItem?.roleScopes?.length ?? 0) > 0
|
|
434
|
+
&& !workItem.roleScopes.some(scope => toAgentRoleScopes.includes(scope))))) {
|
|
435
|
+
return undefined;
|
|
436
|
+
}
|
|
437
|
+
const inserted = this.driver.prepare(`
|
|
438
|
+
INSERT INTO goal_handoffs (
|
|
439
|
+
goal_id, work_item_id, from_agent_id, to_agent_id, reason, evidence_refs_json, created_at
|
|
440
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
441
|
+
`).run(row.goal_id, row.work_item_id, row.agent_id, input.toAgentId ?? null, input.reason.trim() || 'handoff', JSON.stringify(evidenceRefs), now.toISOString());
|
|
442
|
+
this.driver.prepare('DELETE FROM goal_work_claims WHERE claim_token = ?').run(input.claimToken);
|
|
443
|
+
let claim;
|
|
444
|
+
if (input.toAgentId) {
|
|
445
|
+
claim = {
|
|
446
|
+
goalId: String(row.goal_id),
|
|
447
|
+
workItemId: String(row.work_item_id),
|
|
448
|
+
agentId: input.toAgentId,
|
|
449
|
+
claimToken: `goal-claim:${createId()}`,
|
|
450
|
+
roleScopes: toAgentRoleScopes,
|
|
451
|
+
claimedAt: now.toISOString(),
|
|
452
|
+
leaseExpiresAt: new Date(now.getTime() + Math.max(1_000, input.leaseMs ?? 15 * 60_000)).toISOString(),
|
|
453
|
+
updatedAt: now.toISOString(),
|
|
454
|
+
};
|
|
455
|
+
this.driver.prepare(`
|
|
456
|
+
INSERT INTO goal_work_claims (
|
|
457
|
+
goal_id, work_item_id, agent_id, claim_token, role_scopes_json,
|
|
458
|
+
claimed_at, lease_expires_at, updated_at
|
|
459
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
460
|
+
`).run(claim.goalId, claim.workItemId, claim.agentId, claim.claimToken, JSON.stringify(claim.roleScopes), claim.claimedAt, claim.leaseExpiresAt, claim.updatedAt);
|
|
461
|
+
}
|
|
462
|
+
const receipt = {
|
|
463
|
+
id: Number(inserted.lastInsertRowid),
|
|
464
|
+
goalId: String(row.goal_id),
|
|
465
|
+
workItemId: String(row.work_item_id),
|
|
466
|
+
fromAgentId: String(row.agent_id),
|
|
467
|
+
...(input.toAgentId ? { toAgentId: input.toAgentId } : {}),
|
|
468
|
+
reason: input.reason.trim() || 'handoff',
|
|
469
|
+
evidenceRefs,
|
|
470
|
+
at: now.toISOString(),
|
|
471
|
+
};
|
|
472
|
+
this.insertEvent(receipt.goalId, 'work_handed_off', receipt.at, undefined, {
|
|
473
|
+
workItemId: receipt.workItemId,
|
|
474
|
+
fromAgentId: receipt.fromAgentId,
|
|
475
|
+
...(receipt.toAgentId ? { toAgentId: receipt.toAgentId } : {}),
|
|
476
|
+
});
|
|
477
|
+
return { receipt, ...(claim ? { claim } : {}) };
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
listWorkClaims(goalId, options = {}) {
|
|
481
|
+
const rows = this.driver.prepare(`
|
|
482
|
+
SELECT * FROM goal_work_claims WHERE goal_id = ?
|
|
483
|
+
${options.includeExpired ? '' : 'AND lease_expires_at > ?'}
|
|
484
|
+
ORDER BY claimed_at ASC
|
|
485
|
+
`).all(...(options.includeExpired ? [goalId] : [goalId, (options.now ?? new Date()).toISOString()]));
|
|
486
|
+
return rows.map(mapWorkClaim);
|
|
487
|
+
}
|
|
488
|
+
listHandoffs(goalId, limit = 100) {
|
|
489
|
+
return this.driver.prepare(`
|
|
490
|
+
SELECT * FROM goal_handoffs WHERE goal_id = ? ORDER BY handoff_id DESC LIMIT ?
|
|
491
|
+
`).all(goalId, Math.max(1, Math.min(limit, 500))).reverse().map(row => ({
|
|
492
|
+
id: Number(row.handoff_id),
|
|
493
|
+
goalId: String(row.goal_id),
|
|
494
|
+
workItemId: String(row.work_item_id),
|
|
495
|
+
fromAgentId: String(row.from_agent_id),
|
|
496
|
+
...(typeof row.to_agent_id === 'string' ? { toAgentId: row.to_agent_id } : {}),
|
|
497
|
+
reason: String(row.reason),
|
|
498
|
+
evidenceRefs: parseStringArray(row.evidence_refs_json),
|
|
499
|
+
at: String(row.created_at),
|
|
500
|
+
}));
|
|
501
|
+
}
|
|
502
|
+
goalIdForSession(sessionId) {
|
|
503
|
+
const row = this.driver.prepare('SELECT goal_id FROM goal_sessions WHERE session_id = ?').get(sessionId);
|
|
504
|
+
return typeof row?.goal_id === 'string' ? row.goal_id : undefined;
|
|
505
|
+
}
|
|
506
|
+
currentGoalId() {
|
|
507
|
+
const row = this.driver.prepare(`
|
|
508
|
+
SELECT goal_id FROM goals
|
|
509
|
+
WHERE archived_at IS NULL AND status NOT IN ('complete', 'cancelled')
|
|
510
|
+
ORDER BY updated_at DESC
|
|
511
|
+
LIMIT 1
|
|
512
|
+
`).get();
|
|
513
|
+
return typeof row?.goal_id === 'string' ? row.goal_id : undefined;
|
|
514
|
+
}
|
|
515
|
+
readSnapshot(goalId) {
|
|
516
|
+
const row = this.driver.prepare('SELECT snapshot_json FROM goals WHERE goal_id = ? AND archived_at IS NULL').get(goalId);
|
|
517
|
+
if (typeof row?.snapshot_json !== 'string')
|
|
518
|
+
return null;
|
|
519
|
+
return normalizeGoal(row.snapshot_json, new Date().toISOString());
|
|
520
|
+
}
|
|
521
|
+
writeSnapshot(goalId, goal, eventType = 'snapshot_updated') {
|
|
522
|
+
const now = goal?.updatedAt ?? new Date().toISOString();
|
|
523
|
+
this.driver.transaction(() => {
|
|
524
|
+
if (!goal) {
|
|
525
|
+
this.driver.prepare('UPDATE goals SET archived_at = ?, updated_at = ? WHERE goal_id = ?').run(now, now, goalId);
|
|
526
|
+
this.driver.prepare('DELETE FROM goal_work_claims WHERE goal_id = ?').run(goalId);
|
|
527
|
+
this.insertEvent(goalId, 'archived', now);
|
|
528
|
+
return;
|
|
529
|
+
}
|
|
530
|
+
this.driver.prepare(`
|
|
531
|
+
INSERT INTO goals (
|
|
532
|
+
goal_id, objective, status, revision, snapshot_json, created_at, updated_at, archived_at
|
|
533
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, NULL)
|
|
534
|
+
ON CONFLICT(goal_id) DO UPDATE SET
|
|
535
|
+
objective = excluded.objective,
|
|
536
|
+
status = excluded.status,
|
|
537
|
+
revision = excluded.revision,
|
|
538
|
+
snapshot_json = excluded.snapshot_json,
|
|
539
|
+
updated_at = excluded.updated_at,
|
|
540
|
+
archived_at = NULL
|
|
541
|
+
`).run(goalId, goal.objective, goal.status, goal.revision, JSON.stringify(goal), goal.createdAt, goal.updatedAt);
|
|
542
|
+
this.insertEvent(goalId, eventType, now, goal.revision, {
|
|
543
|
+
status: goal.status,
|
|
544
|
+
objective: goal.objective,
|
|
545
|
+
});
|
|
546
|
+
if (goal.status === 'complete' || goal.status === 'cancelled' || goal.status === 'blocked') {
|
|
547
|
+
this.driver.prepare('DELETE FROM goal_work_claims WHERE goal_id = ?').run(goalId);
|
|
548
|
+
}
|
|
549
|
+
});
|
|
550
|
+
}
|
|
551
|
+
updateSnapshot(goalId, mutation) {
|
|
552
|
+
return this.driver.transaction(() => {
|
|
553
|
+
const current = this.driver.prepare('SELECT snapshot_json FROM goals WHERE goal_id = ? AND archived_at IS NULL').get(goalId);
|
|
554
|
+
const next = mutation(current?.snapshot_json);
|
|
555
|
+
if (!next) {
|
|
556
|
+
const now = new Date().toISOString();
|
|
557
|
+
this.driver.prepare('UPDATE goals SET archived_at = ?, updated_at = ? WHERE goal_id = ?').run(now, now, goalId);
|
|
558
|
+
this.driver.prepare('DELETE FROM goal_work_claims WHERE goal_id = ?').run(goalId);
|
|
559
|
+
this.insertEvent(goalId, 'archived', now);
|
|
560
|
+
return undefined;
|
|
561
|
+
}
|
|
562
|
+
this.driver.prepare(`
|
|
563
|
+
INSERT INTO goals (
|
|
564
|
+
goal_id, objective, status, revision, snapshot_json, created_at, updated_at, archived_at
|
|
565
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, NULL)
|
|
566
|
+
ON CONFLICT(goal_id) DO UPDATE SET
|
|
567
|
+
objective = excluded.objective,
|
|
568
|
+
status = excluded.status,
|
|
569
|
+
revision = excluded.revision,
|
|
570
|
+
snapshot_json = excluded.snapshot_json,
|
|
571
|
+
updated_at = excluded.updated_at,
|
|
572
|
+
archived_at = NULL
|
|
573
|
+
`).run(goalId, next.objective, next.status, next.revision, JSON.stringify(next), next.createdAt, next.updatedAt);
|
|
574
|
+
this.insertEvent(goalId, 'snapshot_updated', next.updatedAt, next.revision, {
|
|
575
|
+
status: next.status,
|
|
576
|
+
});
|
|
577
|
+
if (next.status === 'complete' || next.status === 'cancelled' || next.status === 'blocked') {
|
|
578
|
+
this.driver.prepare('DELETE FROM goal_work_claims WHERE goal_id = ?').run(goalId);
|
|
579
|
+
}
|
|
580
|
+
return next;
|
|
581
|
+
});
|
|
582
|
+
}
|
|
583
|
+
attachSession(sessionId, goalId, at = new Date().toISOString()) {
|
|
584
|
+
this.driver.prepare(`
|
|
585
|
+
INSERT INTO goal_sessions (session_id, goal_id, attached_at)
|
|
586
|
+
VALUES (?, ?, ?)
|
|
587
|
+
ON CONFLICT(session_id) DO UPDATE SET
|
|
588
|
+
goal_id = excluded.goal_id,
|
|
589
|
+
attached_at = excluded.attached_at
|
|
590
|
+
`).run(sessionId, goalId, at);
|
|
591
|
+
this.insertEvent(goalId, 'session_attached', at, undefined, { sessionId });
|
|
592
|
+
}
|
|
593
|
+
detachSession(sessionId, at = new Date().toISOString()) {
|
|
594
|
+
const goalId = this.goalIdForSession(sessionId);
|
|
595
|
+
this.driver.prepare('DELETE FROM goal_sessions WHERE session_id = ?').run(sessionId);
|
|
596
|
+
if (goalId)
|
|
597
|
+
this.insertEvent(goalId, 'session_detached', at, undefined, { sessionId });
|
|
598
|
+
}
|
|
599
|
+
insertEvent(goalId, type, at, revision, payload) {
|
|
600
|
+
this.driver.prepare(`
|
|
601
|
+
INSERT INTO goal_events (goal_id, event_type, revision, payload_json, created_at)
|
|
602
|
+
VALUES (?, ?, ?, ?, ?)
|
|
603
|
+
`).run(goalId, type, revision ?? null, payload ? JSON.stringify(payload) : null, at);
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
class ProjectGoalSessionPort {
|
|
607
|
+
store;
|
|
608
|
+
sessionId;
|
|
609
|
+
goalId;
|
|
610
|
+
constructor(store, sessionId, goalId) {
|
|
611
|
+
this.store = store;
|
|
612
|
+
this.sessionId = sessionId;
|
|
613
|
+
this.goalId = goalId;
|
|
614
|
+
}
|
|
615
|
+
readGoalMetadata() {
|
|
616
|
+
return this.goalId ? this.store.readSnapshot(this.goalId) : undefined;
|
|
617
|
+
}
|
|
618
|
+
async writeGoalMetadata(value) {
|
|
619
|
+
if (!this.goalId && value)
|
|
620
|
+
this.goalId = this.store.createGoalId();
|
|
621
|
+
if (!this.goalId)
|
|
622
|
+
return;
|
|
623
|
+
this.store.writeSnapshot(this.goalId, value);
|
|
624
|
+
if (value)
|
|
625
|
+
this.store.attachSession(this.sessionId, this.goalId);
|
|
626
|
+
}
|
|
627
|
+
async updateGoalMetadata(mutation) {
|
|
628
|
+
if (!this.goalId) {
|
|
629
|
+
const next = mutation(undefined);
|
|
630
|
+
if (!next)
|
|
631
|
+
return undefined;
|
|
632
|
+
this.goalId = this.store.createGoalId();
|
|
633
|
+
this.store.writeSnapshot(this.goalId, next, 'created');
|
|
634
|
+
this.store.attachSession(this.sessionId, this.goalId, next.createdAt);
|
|
635
|
+
return next;
|
|
636
|
+
}
|
|
637
|
+
const next = this.store.updateSnapshot(this.goalId, mutation);
|
|
638
|
+
if (next)
|
|
639
|
+
this.store.attachSession(this.sessionId, this.goalId, next.updatedAt);
|
|
640
|
+
return next;
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
function initializeSchema(driver) {
|
|
644
|
+
driver.exec(`
|
|
645
|
+
PRAGMA journal_mode = WAL;
|
|
646
|
+
PRAGMA foreign_keys = ON;
|
|
647
|
+
|
|
648
|
+
CREATE TABLE IF NOT EXISTS goals (
|
|
649
|
+
goal_id TEXT PRIMARY KEY,
|
|
650
|
+
objective TEXT NOT NULL,
|
|
651
|
+
status TEXT NOT NULL,
|
|
652
|
+
revision INTEGER NOT NULL,
|
|
653
|
+
snapshot_json TEXT NOT NULL,
|
|
654
|
+
created_at TEXT NOT NULL,
|
|
655
|
+
updated_at TEXT NOT NULL,
|
|
656
|
+
archived_at TEXT
|
|
657
|
+
);
|
|
658
|
+
|
|
659
|
+
CREATE INDEX IF NOT EXISTS idx_goals_current
|
|
660
|
+
ON goals(archived_at, status, updated_at DESC);
|
|
661
|
+
|
|
662
|
+
CREATE TABLE IF NOT EXISTS goal_sessions (
|
|
663
|
+
session_id TEXT PRIMARY KEY,
|
|
664
|
+
goal_id TEXT NOT NULL REFERENCES goals(goal_id) ON DELETE CASCADE,
|
|
665
|
+
attached_at TEXT NOT NULL
|
|
666
|
+
);
|
|
667
|
+
|
|
668
|
+
CREATE INDEX IF NOT EXISTS idx_goal_sessions_goal
|
|
669
|
+
ON goal_sessions(goal_id, attached_at);
|
|
670
|
+
|
|
671
|
+
CREATE TABLE IF NOT EXISTS goal_events (
|
|
672
|
+
event_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
673
|
+
goal_id TEXT NOT NULL REFERENCES goals(goal_id) ON DELETE CASCADE,
|
|
674
|
+
event_type TEXT NOT NULL,
|
|
675
|
+
revision INTEGER,
|
|
676
|
+
payload_json TEXT,
|
|
677
|
+
created_at TEXT NOT NULL
|
|
678
|
+
);
|
|
679
|
+
|
|
680
|
+
CREATE INDEX IF NOT EXISTS idx_goal_events_goal
|
|
681
|
+
ON goal_events(goal_id, event_id);
|
|
682
|
+
|
|
683
|
+
CREATE TABLE IF NOT EXISTS goal_continuation (
|
|
684
|
+
goal_id TEXT PRIMARY KEY REFERENCES goals(goal_id) ON DELETE CASCADE,
|
|
685
|
+
mode TEXT NOT NULL,
|
|
686
|
+
execution_profile_json TEXT,
|
|
687
|
+
min_interval_seconds INTEGER NOT NULL,
|
|
688
|
+
max_interval_seconds INTEGER NOT NULL,
|
|
689
|
+
current_interval_seconds INTEGER NOT NULL,
|
|
690
|
+
next_wake_at TEXT,
|
|
691
|
+
last_wake_at TEXT,
|
|
692
|
+
last_outcome TEXT,
|
|
693
|
+
consecutive_no_change INTEGER NOT NULL DEFAULT 0,
|
|
694
|
+
lease_owner TEXT,
|
|
695
|
+
lease_expires_at TEXT,
|
|
696
|
+
last_error TEXT,
|
|
697
|
+
updated_at TEXT NOT NULL
|
|
698
|
+
);
|
|
699
|
+
|
|
700
|
+
CREATE INDEX IF NOT EXISTS idx_goal_continuation_wake
|
|
701
|
+
ON goal_continuation(mode, next_wake_at);
|
|
702
|
+
|
|
703
|
+
CREATE TABLE IF NOT EXISTS goal_work_claims (
|
|
704
|
+
goal_id TEXT NOT NULL REFERENCES goals(goal_id) ON DELETE CASCADE,
|
|
705
|
+
work_item_id TEXT NOT NULL,
|
|
706
|
+
agent_id TEXT NOT NULL,
|
|
707
|
+
claim_token TEXT NOT NULL UNIQUE,
|
|
708
|
+
role_scopes_json TEXT NOT NULL,
|
|
709
|
+
claimed_at TEXT NOT NULL,
|
|
710
|
+
lease_expires_at TEXT NOT NULL,
|
|
711
|
+
updated_at TEXT NOT NULL,
|
|
712
|
+
PRIMARY KEY(goal_id, work_item_id)
|
|
713
|
+
);
|
|
714
|
+
|
|
715
|
+
CREATE INDEX IF NOT EXISTS idx_goal_work_claims_lease
|
|
716
|
+
ON goal_work_claims(goal_id, lease_expires_at);
|
|
717
|
+
|
|
718
|
+
CREATE TABLE IF NOT EXISTS goal_handoffs (
|
|
719
|
+
handoff_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
720
|
+
goal_id TEXT NOT NULL REFERENCES goals(goal_id) ON DELETE CASCADE,
|
|
721
|
+
work_item_id TEXT NOT NULL,
|
|
722
|
+
from_agent_id TEXT NOT NULL,
|
|
723
|
+
to_agent_id TEXT,
|
|
724
|
+
reason TEXT NOT NULL,
|
|
725
|
+
evidence_refs_json TEXT NOT NULL,
|
|
726
|
+
created_at TEXT NOT NULL
|
|
727
|
+
);
|
|
728
|
+
|
|
729
|
+
CREATE INDEX IF NOT EXISTS idx_goal_handoffs_goal
|
|
730
|
+
ON goal_handoffs(goal_id, handoff_id);
|
|
731
|
+
`);
|
|
732
|
+
}
|
|
733
|
+
function parseObject(value) {
|
|
734
|
+
try {
|
|
735
|
+
const parsed = JSON.parse(value);
|
|
736
|
+
return parsed && typeof parsed === 'object' && !Array.isArray(parsed)
|
|
737
|
+
? parsed
|
|
738
|
+
: {};
|
|
739
|
+
}
|
|
740
|
+
catch {
|
|
741
|
+
return {};
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
function mapContinuation(row) {
|
|
745
|
+
const parsedProfile = typeof row.execution_profile_json === 'string'
|
|
746
|
+
? parseObject(row.execution_profile_json)
|
|
747
|
+
: undefined;
|
|
748
|
+
const executionProfile = parsedProfile
|
|
749
|
+
&& (parsedProfile.kind === 'config' || parsedProfile.kind === 'agent')
|
|
750
|
+
&& typeof parsedProfile.name === 'string'
|
|
751
|
+
? { kind: parsedProfile.kind, name: parsedProfile.name }
|
|
752
|
+
: undefined;
|
|
753
|
+
return {
|
|
754
|
+
goalId: String(row.goal_id),
|
|
755
|
+
mode: String(row.mode),
|
|
756
|
+
...(executionProfile ? { executionProfile } : {}),
|
|
757
|
+
minIntervalSeconds: Number(row.min_interval_seconds),
|
|
758
|
+
maxIntervalSeconds: Number(row.max_interval_seconds),
|
|
759
|
+
currentIntervalSeconds: Number(row.current_interval_seconds),
|
|
760
|
+
...(typeof row.next_wake_at === 'string' ? { nextWakeAt: row.next_wake_at } : {}),
|
|
761
|
+
...(typeof row.last_wake_at === 'string' ? { lastWakeAt: row.last_wake_at } : {}),
|
|
762
|
+
...(typeof row.last_outcome === 'string' ? { lastOutcome: row.last_outcome } : {}),
|
|
763
|
+
consecutiveNoChange: Number(row.consecutive_no_change ?? 0),
|
|
764
|
+
...(typeof row.lease_owner === 'string' ? { leaseOwner: row.lease_owner } : {}),
|
|
765
|
+
...(typeof row.lease_expires_at === 'string' ? { leaseExpiresAt: row.lease_expires_at } : {}),
|
|
766
|
+
...(typeof row.last_error === 'string' ? { lastError: row.last_error } : {}),
|
|
767
|
+
...(typeof row.session_id === 'string' ? { sessionId: row.session_id } : {}),
|
|
768
|
+
};
|
|
769
|
+
}
|
|
770
|
+
function clampSeconds(value, min, max) {
|
|
771
|
+
if (!Number.isFinite(value))
|
|
772
|
+
return min;
|
|
773
|
+
return Math.max(min, Math.min(max, Math.floor(value)));
|
|
774
|
+
}
|
|
775
|
+
function isFuture(value, now) {
|
|
776
|
+
return Boolean(value && Date.parse(value) > now.getTime());
|
|
777
|
+
}
|
|
778
|
+
function mapWorkClaim(row) {
|
|
779
|
+
return {
|
|
780
|
+
goalId: String(row.goal_id),
|
|
781
|
+
workItemId: String(row.work_item_id),
|
|
782
|
+
agentId: String(row.agent_id),
|
|
783
|
+
claimToken: String(row.claim_token),
|
|
784
|
+
roleScopes: parseStringArray(row.role_scopes_json),
|
|
785
|
+
claimedAt: String(row.claimed_at),
|
|
786
|
+
leaseExpiresAt: String(row.lease_expires_at),
|
|
787
|
+
updatedAt: String(row.updated_at),
|
|
788
|
+
};
|
|
789
|
+
}
|
|
790
|
+
function parseStringArray(value) {
|
|
791
|
+
if (typeof value !== 'string')
|
|
792
|
+
return [];
|
|
793
|
+
try {
|
|
794
|
+
const parsed = JSON.parse(value);
|
|
795
|
+
return Array.isArray(parsed) ? parsed.filter(item => typeof item === 'string') : [];
|
|
796
|
+
}
|
|
797
|
+
catch {
|
|
798
|
+
return [];
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
function unique(values) {
|
|
802
|
+
return [...new Set(values.map(value => value.trim()).filter(Boolean))];
|
|
803
|
+
}
|
|
804
|
+
function compareGoalWorkItems(a, b) {
|
|
805
|
+
const rank = { P0: 0, P1: 1, P2: 2 };
|
|
806
|
+
return rank[a.priority] - rank[b.priority] || a.createdAt.localeCompare(b.createdAt);
|
|
807
|
+
}
|
|
808
|
+
//# sourceMappingURL=projectGoalStore.js.map
|