openpond 0.0.30-bootstrap.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 +128 -0
- package/LICENSE +21 -0
- package/README.md +162 -0
- package/RELEASE.md +20 -0
- package/dist/api/core.d.ts +22 -0
- package/dist/api/deployments.d.ts +30 -0
- package/dist/api/types.d.ts +624 -0
- package/dist/api.d.ts +90 -0
- package/dist/cache.d.ts +22 -0
- package/dist/chunks/app-layer-YKXEQGOW.js +163 -0
- package/dist/chunks/apps-GGSIN2ZF.js +539 -0
- package/dist/chunks/chunk-3CC6MH35.js +1663 -0
- package/dist/chunks/chunk-3Y4EL3P6.js +1090 -0
- package/dist/chunks/chunk-4HYJG2JE.js +3875 -0
- package/dist/chunks/chunk-5G4E547X.js +294 -0
- package/dist/chunks/chunk-74O6FGVN.js +598 -0
- package/dist/chunks/chunk-AUJLPKGI.js +632 -0
- package/dist/chunks/chunk-BYAPLT37.js +744 -0
- package/dist/chunks/chunk-FUP7EGT7.js +1845 -0
- package/dist/chunks/chunk-I6HN3ZGW.js +88 -0
- package/dist/chunks/chunk-KRKGHVMQ.js +294 -0
- package/dist/chunks/chunk-LHYK6QO2.js +1300 -0
- package/dist/chunks/chunk-NLBKZZEK.js +224 -0
- package/dist/chunks/chunk-OG75GMVO.js +1 -0
- package/dist/chunks/chunk-Q57SVPEH.js +11663 -0
- package/dist/chunks/chunk-UACZUFIA.js +47 -0
- package/dist/chunks/chunk-VQX33YAG.js +5135 -0
- package/dist/chunks/chunk-W5GKHH77.js +168 -0
- package/dist/chunks/chunk-WYDVQOBL.js +17581 -0
- package/dist/chunks/chunk-XSAXENQD.js +4167 -0
- package/dist/chunks/chunk-Y6G6WRTG.js +210 -0
- package/dist/chunks/cli-NVKNE4AZ.js +11 -0
- package/dist/chunks/cli-X3HPLSOV.js +19 -0
- package/dist/chunks/core-commands-IM2UKLU4.js +41 -0
- package/dist/chunks/extend-YXCBULPO.js +84 -0
- package/dist/chunks/harness-GMK6IY2U.js +140 -0
- package/dist/chunks/help-LMH5FGT2.js +65 -0
- package/dist/chunks/opchat-BWXZ32GX.js +169 -0
- package/dist/chunks/organizations-4ISQRGMQ.js +738 -0
- package/dist/chunks/profile-NLDZDT3L.js +1110 -0
- package/dist/chunks/project-agent-ZPVO25HA.js +892 -0
- package/dist/chunks/sandbox-command-5GFJ2U3V.js +1652 -0
- package/dist/chunks/sandbox-template-LZN4F55Z.js +1646 -0
- package/dist/chunks/src-3RBI4DNN.js +3595 -0
- package/dist/chunks/src-EYXQS6XD.js +73427 -0
- package/dist/chunks/teams-bot-WP3FHKGA.js +137 -0
- package/dist/cli.js +77 -0
- package/dist/client-types.d.ts +222 -0
- package/dist/client.d.ts +19 -0
- package/dist/config.d.ts +184 -0
- package/dist/hosted-chat.d.ts +162 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +25481 -0
- package/dist/indicators.d.ts +38 -0
- package/dist/opchat-smoke.d.ts +47 -0
- package/dist/private-json-file.d.ts +3 -0
- package/dist/profile/local-profile-types.d.ts +160 -0
- package/dist/profile/local-profile.d.ts +69 -0
- package/dist/profile/profile-catalog.d.ts +14 -0
- package/dist/profile/profile-git.d.ts +17 -0
- package/dist/profile/profile-promotion.d.ts +21 -0
- package/dist/profile/profile-setup-gate.d.ts +22 -0
- package/dist/profile/profile-skills.d.ts +30 -0
- package/dist/profile/profile-source-upload.d.ts +29 -0
- package/dist/profile/source-upload-cache.d.ts +29 -0
- package/dist/sandbox/async-request-options.d.ts +2 -0
- package/dist/sandbox/client-handles.d.ts +113 -0
- package/dist/sandbox/client.d.ts +348 -0
- package/dist/sandbox/runtime-handle.d.ts +36 -0
- package/dist/sandbox/sandbox-instance-client.d.ts +72 -0
- package/dist/sandbox/smoke.d.ts +3 -0
- package/dist/sandbox/stream.d.ts +7 -0
- package/dist/sandbox/types/billing.d.ts +176 -0
- package/dist/sandbox/types/client.d.ts +103 -0
- package/dist/sandbox/types/core.d.ts +140 -0
- package/dist/sandbox/types/index.d.ts +14 -0
- package/dist/sandbox/types/io.d.ts +240 -0
- package/dist/sandbox/types/microsoft-teams-bot.d.ts +51 -0
- package/dist/sandbox/types/org-project-agent.d.ts +610 -0
- package/dist/sandbox/types/record.d.ts +53 -0
- package/dist/sandbox/types/responses.d.ts +295 -0
- package/dist/sandbox/types/runtime-profiles.d.ts +15 -0
- package/dist/sandbox/types/runtime.d.ts +152 -0
- package/dist/sandbox/types/schedules.d.ts +141 -0
- package/dist/sandbox/types/snapshots.d.ts +205 -0
- package/dist/sandbox/types/workflow-modes.d.ts +1 -0
- package/dist/sandbox/types/workload-sources.d.ts +20 -0
- package/dist/sandbox/url.d.ts +2 -0
- package/dist/sandbox-template/manifest.d.ts +304 -0
- package/dist/sandbox-template/manifest.js +18381 -0
- package/dist/skills/openpond-cli/SKILL.md +49 -0
- package/dist/skills/openpond-desktop-harness/SKILL.md +113 -0
- package/dist/skills/openpond-desktop-harness/agents/openai.yaml +4 -0
- package/dist/skills/openpond-taskset-authoring/SKILL.md +38 -0
- package/dist/skills/openpond-taskset-authoring/references/graders-and-rewards.md +11 -0
- package/dist/skills/openpond-taskset-authoring/references/method-selection.md +12 -0
- package/dist/skills/openpond-taskset-authoring/references/privacy-and-provenance.md +8 -0
- package/dist/skills/openpond-taskset-authoring/references/task-design.md +13 -0
- package/dist/stream.d.ts +20 -0
- package/dist/types.d.ts +57 -0
- package/dist/urls.d.ts +4 -0
- package/dist/web/assets/AppsView-Bu-Nc3Ew.js +1 -0
- package/dist/web/assets/AppsView-DIhprTIC.css +1 -0
- package/dist/web/assets/BrowserSidebar-CCguQ-DR.js +1 -0
- package/dist/web/assets/BrowserSidebar-D9i7gQzw.css +1 -0
- package/dist/web/assets/CloudWorkView-DNoPIlMj.css +1 -0
- package/dist/web/assets/CloudWorkView-Dr3xqLE5.js +1 -0
- package/dist/web/assets/CommandMenu-CnodJFHD.js +1 -0
- package/dist/web/assets/CommandMenu-hNQCm8mw.css +1 -0
- package/dist/web/assets/CommunityView-CIbjNUZr.js +1 -0
- package/dist/web/assets/CommunityView-DSik4sd2.css +1 -0
- package/dist/web/assets/GetStartedView-C3ZrnEz5.css +1 -0
- package/dist/web/assets/GetStartedView-C8nyH_PT.js +1 -0
- package/dist/web/assets/LabsExtensions-GtJzxDnK.js +1 -0
- package/dist/web/assets/LabsProfile-DG7rWYur.js +1 -0
- package/dist/web/assets/LabsRoute-D6MDHBzq.js +2 -0
- package/dist/web/assets/LabsRoute-TEt9j1vB.css +1 -0
- package/dist/web/assets/LabsSignals-BfsDN86w.js +1 -0
- package/dist/web/assets/LabsSignals-D6iA6Ryl.css +1 -0
- package/dist/web/assets/NewProjectDialog-CaKiCoVS.js +1 -0
- package/dist/web/assets/ProfileSettingsSection-DTQS2DVL.css +1 -0
- package/dist/web/assets/ProfileSettingsSection-DxEzoF3K.js +1 -0
- package/dist/web/assets/ProfileView-CEscW7Ru.js +1 -0
- package/dist/web/assets/SettingsView-DwbGu8Kj.css +1 -0
- package/dist/web/assets/SettingsView-JIRR_FO4.js +63 -0
- package/dist/web/assets/TeamChatView-B5M1ThhJ.js +3 -0
- package/dist/web/assets/TeamChatView-Bx44q3WV.css +1 -0
- package/dist/web/assets/TerminalOverlay-C1iP4OS1.js +19 -0
- package/dist/web/assets/TerminalOverlay-DwBiZLwk.css +1 -0
- package/dist/web/assets/TrainingSettingsSection-BDYvI2te.js +1 -0
- package/dist/web/assets/TrainingView-CoCFK6W3.js +5 -0
- package/dist/web/assets/WorkspaceDiffPanel-CMxKQeo7.js +14 -0
- package/dist/web/assets/WorkspaceEnvironmentMenu-B8JJbwIz.js +32 -0
- package/dist/web/assets/WorkspaceEnvironmentMenu-DGIFdG3o.css +1 -0
- package/dist/web/assets/WorkspaceGitDialogs-B4sp9BGn.js +1 -0
- package/dist/web/assets/WorkspaceMonacoEditor-BBqimG5X.js +845 -0
- package/dist/web/assets/WorkspaceMonacoEditor-BwKcXIZw.css +1 -0
- package/dist/web/assets/boxes-DAaDUabq.js +1 -0
- package/dist/web/assets/codicon-ngg6Pgfi.ttf +0 -0
- package/dist/web/assets/css.worker-DBVD8oXr.js +93 -0
- package/dist/web/assets/cssMode-CBF1mieJ.js +1 -0
- package/dist/web/assets/dockerfile-BG73LgW2.js +1 -0
- package/dist/web/assets/editor.worker-CdQrwHl8.js +26 -0
- package/dist/web/assets/git-commit-horizontal-CAickXpN.js +1 -0
- package/dist/web/assets/git-dialogs-BZDqS1eT.css +1 -0
- package/dist/web/assets/html.worker-CwpTb9lJ.js +470 -0
- package/dist/web/assets/htmlMode-2u8OHtWO.js +1 -0
- package/dist/web/assets/index-BdY6A-YN.css +1 -0
- package/dist/web/assets/index-CU2qhUYV.js +195 -0
- package/dist/web/assets/index-Dz2B3-4C.js +1 -0
- package/dist/web/assets/info-CQNTh78o.js +1 -0
- package/dist/web/assets/json.worker-BoL8UZqY.js +58 -0
- package/dist/web/assets/jsonMode-C4CWXViX.js +7 -0
- package/dist/web/assets/lspLanguageFeatures-CBs_BOlB.js +4 -0
- package/dist/web/assets/markdown-BFxVWTOG.js +1 -0
- package/dist/web/assets/monaco.contribution-C1LUxAKf.js +2 -0
- package/dist/web/assets/monaco.contribution-Cpf6dWsh.js +2 -0
- package/dist/web/assets/monaco.contribution-lOXpG2xQ.js +2 -0
- package/dist/web/assets/monaco.contribution-sd3cFkcN.js +2 -0
- package/dist/web/assets/monitor-DsIswrMV.js +1 -0
- package/dist/web/assets/play-CeRw21NV.js +1 -0
- package/dist/web/assets/python-CXYq3BUl.js +1 -0
- package/dist/web/assets/refresh-cw-N5b_6AP5.js +1 -0
- package/dist/web/assets/reply-BSKjZrJg.js +1 -0
- package/dist/web/assets/save-D0oHH1a3.js +1 -0
- package/dist/web/assets/settings-lists-BEg6LMcy.css +1 -0
- package/dist/web/assets/shell-DiJ1NA_G.js +1 -0
- package/dist/web/assets/toggleHighContrast-B7NOMXDY.css +1 -0
- package/dist/web/assets/toggleHighContrast-DRjlcwes.js +62 -0
- package/dist/web/assets/training-model-data-BPrc68VG.js +1 -0
- package/dist/web/assets/ts.worker-BH9nVgjN.js +67718 -0
- package/dist/web/assets/tsMode-x-1A7ZcB.js +11 -0
- package/dist/web/assets/wifi-off-Dt52ofzh.js +1 -0
- package/dist/web/assets/workers-Ciff6Dzi.js +1 -0
- package/dist/web/assets/yaml-CB67Kxnz.js +1 -0
- package/dist/web/connected-apps/github.svg +3 -0
- package/dist/web/connected-apps/google.svg +6 -0
- package/dist/web/connected-apps/linear.svg +3 -0
- package/dist/web/connected-apps/microsoft.svg +6 -0
- package/dist/web/connected-apps/notion.svg +3 -0
- package/dist/web/connected-apps/openpond-mcp.svg +4 -0
- package/dist/web/connected-apps/slack.svg +6 -0
- package/dist/web/connected-apps/x.svg +3 -0
- package/dist/web/favicon-16x16.png +0 -0
- package/dist/web/favicon-32x32.png +0 -0
- package/dist/web/index.html +16 -0
- package/dist/web/openpond-icon.png +0 -0
- package/dist/web/openpond-wordlogo-white.png +0 -0
- package/dist/web/tokens/eth.svg +1 -0
- package/dist/web/tokens/usdc.svg +1 -0
- package/docs/agent-edit-check-status-json.md +132 -0
- package/docs/command-reference.md +695 -0
- package/package.json +84 -0
|
@@ -0,0 +1,3875 @@
|
|
|
1
|
+
import { createRequire as __openpondCreateRequire } from "node:module"; var require = __openpondCreateRequire(import.meta.url);
|
|
2
|
+
import {
|
|
3
|
+
resolveOpChatApiBaseUrl,
|
|
4
|
+
sendHostedChatTurn
|
|
5
|
+
} from "./chunk-NLBKZZEK.js";
|
|
6
|
+
import {
|
|
7
|
+
CreatePipelinePlanSchema,
|
|
8
|
+
CreatePipelineSnapshotSchema,
|
|
9
|
+
WorkflowCaptureArtifactSchema,
|
|
10
|
+
createPipelineActionShapeFromMetadata,
|
|
11
|
+
inferCreatePipelineActionShape
|
|
12
|
+
} from "./chunk-VQX33YAG.js";
|
|
13
|
+
import {
|
|
14
|
+
external_exports
|
|
15
|
+
} from "./chunk-WYDVQOBL.js";
|
|
16
|
+
import {
|
|
17
|
+
apiFetch,
|
|
18
|
+
loadGlobalConfig,
|
|
19
|
+
readApiJson,
|
|
20
|
+
runShellCommand
|
|
21
|
+
} from "./chunk-XSAXENQD.js";
|
|
22
|
+
|
|
23
|
+
// src/goal/cli.ts
|
|
24
|
+
import { randomUUID as randomUUID9 } from "node:crypto";
|
|
25
|
+
import { readFile as readFile3 } from "node:fs/promises";
|
|
26
|
+
|
|
27
|
+
// src/goal/config.ts
|
|
28
|
+
import { randomUUID } from "node:crypto";
|
|
29
|
+
import os from "node:os";
|
|
30
|
+
import { join, resolve } from "node:path";
|
|
31
|
+
var DEFAULT_GOAL_EXECUTION_POLICY = {
|
|
32
|
+
sourceUpdates: "auto_commit_push_default_branch",
|
|
33
|
+
externalEffects: "approval_required",
|
|
34
|
+
budgetEscalation: "approval_required"
|
|
35
|
+
};
|
|
36
|
+
var DEFAULT_GOAL_VERIFICATION = {
|
|
37
|
+
commands: [],
|
|
38
|
+
requiredChecks: [],
|
|
39
|
+
successCriteria: [],
|
|
40
|
+
regressionPolicy: "existing_checks_must_pass",
|
|
41
|
+
artifactRefs: []
|
|
42
|
+
};
|
|
43
|
+
var DEFAULT_GOAL_BUDGET = {
|
|
44
|
+
maxIterations: 8,
|
|
45
|
+
maxRuntimeMinutes: 60,
|
|
46
|
+
maxModelCostUsd: null
|
|
47
|
+
};
|
|
48
|
+
function goalKindDefaultProfile(kind) {
|
|
49
|
+
return kind === "create_agent" || kind === "update_agent" ? "openpond_agent" : "generic_coding";
|
|
50
|
+
}
|
|
51
|
+
function defaultPromptPackForGoal(kind) {
|
|
52
|
+
if (kind === "create_agent") return "openpond_agent_create_v1";
|
|
53
|
+
if (kind === "update_agent") return "openpond_agent_update_v1";
|
|
54
|
+
return "generic_coding_v1";
|
|
55
|
+
}
|
|
56
|
+
function createGoalState(input) {
|
|
57
|
+
const kind = input.kind ?? "general_code_goal";
|
|
58
|
+
const profile = input.profile ?? goalKindDefaultProfile(kind);
|
|
59
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
60
|
+
return {
|
|
61
|
+
id: `goal_${randomUUID()}`,
|
|
62
|
+
teamId: input.teamId ?? null,
|
|
63
|
+
projectId: input.projectId ?? null,
|
|
64
|
+
agentId: input.agentId ?? null,
|
|
65
|
+
workItemId: input.workItemId ?? null,
|
|
66
|
+
conversationId: input.conversationId ?? null,
|
|
67
|
+
sandboxId: null,
|
|
68
|
+
runtimeId: null,
|
|
69
|
+
kind,
|
|
70
|
+
profile,
|
|
71
|
+
promptPack: input.promptPack ?? defaultPromptPackForGoal(kind),
|
|
72
|
+
objective: input.objective,
|
|
73
|
+
status: "queued",
|
|
74
|
+
executionPolicy: DEFAULT_GOAL_EXECUTION_POLICY,
|
|
75
|
+
verification: {
|
|
76
|
+
...DEFAULT_GOAL_VERIFICATION,
|
|
77
|
+
...input.verification ?? {},
|
|
78
|
+
commands: input.verification?.commands ?? DEFAULT_GOAL_VERIFICATION.commands,
|
|
79
|
+
requiredChecks: input.verification?.requiredChecks ?? DEFAULT_GOAL_VERIFICATION.requiredChecks,
|
|
80
|
+
successCriteria: input.verification?.successCriteria ?? DEFAULT_GOAL_VERIFICATION.successCriteria,
|
|
81
|
+
artifactRefs: input.verification?.artifactRefs ?? DEFAULT_GOAL_VERIFICATION.artifactRefs
|
|
82
|
+
},
|
|
83
|
+
constraints: [],
|
|
84
|
+
evidenceRefs: [],
|
|
85
|
+
budget: DEFAULT_GOAL_BUDGET,
|
|
86
|
+
questions: [],
|
|
87
|
+
answers: [],
|
|
88
|
+
approvals: [],
|
|
89
|
+
events: [
|
|
90
|
+
{
|
|
91
|
+
id: `event_${randomUUID()}`,
|
|
92
|
+
goalId: "",
|
|
93
|
+
iterationId: null,
|
|
94
|
+
kind: "goal.created",
|
|
95
|
+
summary: "Goal created",
|
|
96
|
+
payload: { kind, profile },
|
|
97
|
+
createdAt: now
|
|
98
|
+
}
|
|
99
|
+
],
|
|
100
|
+
createdAt: now,
|
|
101
|
+
updatedAt: now
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
function normalizeGoalState(state) {
|
|
105
|
+
return {
|
|
106
|
+
...state,
|
|
107
|
+
events: state.events.map(
|
|
108
|
+
(event) => event.goalId ? event : { ...event, goalId: state.id }
|
|
109
|
+
)
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
function resolveGoalWorkspace(cwd) {
|
|
113
|
+
return resolve(cwd || process.cwd());
|
|
114
|
+
}
|
|
115
|
+
var DEFAULT_GOAL_STORAGE_LOCATION = "global";
|
|
116
|
+
function parseGoalStorageLocation(value) {
|
|
117
|
+
const normalized = value?.trim().toLowerCase();
|
|
118
|
+
if (!normalized) return null;
|
|
119
|
+
if (normalized === "global" || normalized === "home" || normalized === "user-home") return "global";
|
|
120
|
+
if (normalized === "workspace" || normalized === "cwd" || normalized === "working-directory") return "workspace";
|
|
121
|
+
throw new Error("goal storage must be global or workspace");
|
|
122
|
+
}
|
|
123
|
+
async function resolveGoalStorageLocation(explicit) {
|
|
124
|
+
const explicitLocation = parseGoalStorageLocation(explicit);
|
|
125
|
+
if (explicitLocation) return explicitLocation;
|
|
126
|
+
const envLocation = parseGoalStorageLocation(
|
|
127
|
+
process.env.OPENPOND_GOAL_STORAGE_LOCATION || process.env.OPENPOND_GOAL_STORAGE
|
|
128
|
+
);
|
|
129
|
+
if (envLocation) return envLocation;
|
|
130
|
+
const config = await loadGlobalConfig().catch(() => null);
|
|
131
|
+
return config?.goalStorageLocation ?? DEFAULT_GOAL_STORAGE_LOCATION;
|
|
132
|
+
}
|
|
133
|
+
function resolveGoalStorageRoot(input) {
|
|
134
|
+
return input.location === "workspace" ? resolveGoalWorkspace(input.cwd) : os.homedir();
|
|
135
|
+
}
|
|
136
|
+
function goalStateDisplayPath(input) {
|
|
137
|
+
const home = os.homedir();
|
|
138
|
+
const relative3 = join(".openpond", "goals", input.goalId, input.fileName);
|
|
139
|
+
return input.storageRoot === home ? `~/${relative3}` : relative3;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// src/goal/create-pipeline.ts
|
|
143
|
+
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
144
|
+
function shouldCreatePipelineForGoal(kind) {
|
|
145
|
+
return kind === "create_agent" || kind === "update_agent";
|
|
146
|
+
}
|
|
147
|
+
function createInitialCreatePipeline(input) {
|
|
148
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
149
|
+
const requestId = `create_request_${randomUUID2()}`;
|
|
150
|
+
const planId = `create_plan_${randomUUID2()}`;
|
|
151
|
+
const approvalId = `approval_${randomUUID2()}`;
|
|
152
|
+
const operation = input.goal.kind === "update_agent" ? "edit" : "create";
|
|
153
|
+
const createAgentId = input.goal.agentId ?? slugFromObjective(input.goal.objective);
|
|
154
|
+
const sourceRoot = sourceRootPathForAgent(
|
|
155
|
+
operation === "edit" && input.goal.agentId ? input.goal.agentId : createAgentId
|
|
156
|
+
);
|
|
157
|
+
const request = {
|
|
158
|
+
schemaVersion: "openpond.createPipeline.request.v1",
|
|
159
|
+
id: requestId,
|
|
160
|
+
operation,
|
|
161
|
+
surface: input.surface,
|
|
162
|
+
command: input.command,
|
|
163
|
+
objective: input.goal.objective,
|
|
164
|
+
adapter: {
|
|
165
|
+
kind: "local",
|
|
166
|
+
sourceAuthority: "local_profile",
|
|
167
|
+
activeProfile: input.profile?.activeProfile ?? null,
|
|
168
|
+
repoPath: input.profile?.repoPath ?? null,
|
|
169
|
+
sourcePath: input.profile?.sourcePath ?? null,
|
|
170
|
+
localHead: input.profile?.localHead ?? null,
|
|
171
|
+
confirmationPolicy: "always_require_plan_approval"
|
|
172
|
+
},
|
|
173
|
+
actor: {
|
|
174
|
+
id: input.actorId ?? null,
|
|
175
|
+
kind: "user",
|
|
176
|
+
label: null
|
|
177
|
+
},
|
|
178
|
+
scope: {
|
|
179
|
+
conversationId: input.goal.conversationId,
|
|
180
|
+
workItemId: input.goal.workItemId,
|
|
181
|
+
projectId: input.goal.projectId,
|
|
182
|
+
targetProject: input.goal.projectId ? {
|
|
183
|
+
id: input.goal.projectId,
|
|
184
|
+
name: null,
|
|
185
|
+
workspacePath: null,
|
|
186
|
+
sourceRef: null,
|
|
187
|
+
baseSha: null
|
|
188
|
+
} : null
|
|
189
|
+
},
|
|
190
|
+
context: {
|
|
191
|
+
messageIds: [],
|
|
192
|
+
conversationExcerpts: [],
|
|
193
|
+
attachments: [],
|
|
194
|
+
apps: [],
|
|
195
|
+
tools: [],
|
|
196
|
+
targetRepoAssumptions: []
|
|
197
|
+
},
|
|
198
|
+
targetAgent: {
|
|
199
|
+
agentId: input.goal.agentId,
|
|
200
|
+
displayName: null,
|
|
201
|
+
defaultActionKey: input.goal.agentId ? `${input.goal.agentId}.chat` : "chat"
|
|
202
|
+
},
|
|
203
|
+
metadata: {
|
|
204
|
+
goalKind: input.goal.kind,
|
|
205
|
+
promptPack: input.goal.promptPack,
|
|
206
|
+
sourceOwnership: "Profile source is durable agent code/config; local Goal state is operational run history and is not committed by default.",
|
|
207
|
+
stateOwnership: "Local questions, answers, approvals, events, create plans, and workflow capture are stored under the configured local Goal storage root."
|
|
208
|
+
},
|
|
209
|
+
createdAt: now
|
|
210
|
+
};
|
|
211
|
+
const parsedRequest = request;
|
|
212
|
+
const actionShape = inferCreatePipelineActionShape(parsedRequest);
|
|
213
|
+
const actionShapeDecisionSource = createPipelineActionShapeFromMetadata(
|
|
214
|
+
parsedRequest.metadata
|
|
215
|
+
) ? "request_metadata" : "default_chat_fallback";
|
|
216
|
+
const plan = CreatePipelinePlanSchema.parse({
|
|
217
|
+
schemaVersion: "openpond.createPipeline.plan.v1",
|
|
218
|
+
id: planId,
|
|
219
|
+
goalId: input.goal.id,
|
|
220
|
+
requestId,
|
|
221
|
+
status: "pending_approval",
|
|
222
|
+
objective: input.goal.objective,
|
|
223
|
+
summary: operation === "edit" ? "Edit the selected source-backed OpenPond profile agent after this plan is approved." : "Create a source-backed OpenPond profile agent after this plan is approved.",
|
|
224
|
+
capturedContextSummary: input.surface === "local_extend" ? "Local extend request from the active profile source. No prior chat context was attached." : "Direct prompt request. No prior chat context was attached.",
|
|
225
|
+
defaultChatAction: {
|
|
226
|
+
key: input.goal.agentId ? `${input.goal.agentId}.chat` : "chat",
|
|
227
|
+
label: "Default chat",
|
|
228
|
+
required: true
|
|
229
|
+
},
|
|
230
|
+
sourcePlan: [
|
|
231
|
+
{
|
|
232
|
+
path: sourceRoot,
|
|
233
|
+
operation: operation === "edit" ? "update" : "create",
|
|
234
|
+
reason: operation === "edit" ? "Revise the selected profile agent source while preserving the default chat route." : "Implement the new profile agent source without overwriting existing profile agents."
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
path: "settings/profile.yaml",
|
|
238
|
+
operation: "update",
|
|
239
|
+
reason: "Register the agent and expose the default chat action in the local profile catalog."
|
|
240
|
+
},
|
|
241
|
+
{
|
|
242
|
+
path: sdkOutputPath(sourceRoot, "agent-manifest.json"),
|
|
243
|
+
operation: "inspect",
|
|
244
|
+
reason: "Regenerate and reload the action catalog from SDK outputs after checks pass."
|
|
245
|
+
},
|
|
246
|
+
{
|
|
247
|
+
path: sdkOutputPath(sourceRoot, "action-registry.json"),
|
|
248
|
+
operation: "inspect",
|
|
249
|
+
reason: "Verify the default chat action and explicit actions are catalog-addressable."
|
|
250
|
+
}
|
|
251
|
+
],
|
|
252
|
+
requirements: [
|
|
253
|
+
{
|
|
254
|
+
kind: "target_project",
|
|
255
|
+
name: "active OpenPond profile source",
|
|
256
|
+
status: "required",
|
|
257
|
+
detail: input.profile?.sourcePath ?? "Resolved from the local goal workspace.",
|
|
258
|
+
metadata: {}
|
|
259
|
+
}
|
|
260
|
+
],
|
|
261
|
+
checks: [
|
|
262
|
+
{ name: "inspect", command: "pnpm agent:inspect --json", required: true },
|
|
263
|
+
{ name: "build", command: "pnpm agent:build", required: true },
|
|
264
|
+
{ name: "validate", command: "pnpm agent:validate --json", required: true },
|
|
265
|
+
{ name: "eval", command: "pnpm agent:eval --json", required: true }
|
|
266
|
+
],
|
|
267
|
+
approvalId,
|
|
268
|
+
approvedAt: null,
|
|
269
|
+
editedFromPlanId: null,
|
|
270
|
+
metadata: {
|
|
271
|
+
actionShape,
|
|
272
|
+
actionShapeDecisionSource,
|
|
273
|
+
reviewSections: [
|
|
274
|
+
"What happened",
|
|
275
|
+
"Agent/workflow plan",
|
|
276
|
+
"Files/source to create",
|
|
277
|
+
"Dependencies and runtime setup",
|
|
278
|
+
"Secrets and connections needed",
|
|
279
|
+
"Validation checks"
|
|
280
|
+
]
|
|
281
|
+
},
|
|
282
|
+
createdAt: now,
|
|
283
|
+
updatedAt: now
|
|
284
|
+
});
|
|
285
|
+
const workflowCapture = WorkflowCaptureArtifactSchema.parse({
|
|
286
|
+
schemaVersion: "openpond.createPipeline.workflowCapture.v1",
|
|
287
|
+
id: `workflow_capture_${randomUUID2()}`,
|
|
288
|
+
goalId: input.goal.id,
|
|
289
|
+
requestId,
|
|
290
|
+
command: input.command,
|
|
291
|
+
objective: input.goal.objective,
|
|
292
|
+
conversationExcerpts: [],
|
|
293
|
+
attachments: [],
|
|
294
|
+
apps: [],
|
|
295
|
+
tools: [],
|
|
296
|
+
profileActions: [],
|
|
297
|
+
externalProviders: [],
|
|
298
|
+
environmentVariables: [],
|
|
299
|
+
files: [],
|
|
300
|
+
schedules: [],
|
|
301
|
+
webhooks: [],
|
|
302
|
+
channelTargets: [],
|
|
303
|
+
outputArtifacts: [],
|
|
304
|
+
targetRepoAssumptions: [],
|
|
305
|
+
traceRefs: [],
|
|
306
|
+
metadata: {
|
|
307
|
+
capturePolicy: "Moderate capture only: names, high-level inputs/outputs, setup requirements, artifacts, and side effects. Raw secrets and large logs are excluded."
|
|
308
|
+
},
|
|
309
|
+
createdAt: now
|
|
310
|
+
});
|
|
311
|
+
const snapshot = CreatePipelineSnapshotSchema.parse({
|
|
312
|
+
schemaVersion: "openpond.createPipeline.snapshot.v1",
|
|
313
|
+
id: `create_pipeline_${randomUUID2()}`,
|
|
314
|
+
goalId: input.goal.id,
|
|
315
|
+
state: "awaiting_plan_approval",
|
|
316
|
+
request: parsedRequest,
|
|
317
|
+
plan,
|
|
318
|
+
workflowCapture,
|
|
319
|
+
approvalIds: [approvalId],
|
|
320
|
+
checkRefs: [],
|
|
321
|
+
sourceRefs: [],
|
|
322
|
+
localGoalId: input.goal.id,
|
|
323
|
+
localProfileCommit: input.profile?.localHead ?? null,
|
|
324
|
+
hostedGoalId: null,
|
|
325
|
+
hostedSourceCommit: null,
|
|
326
|
+
hostedSourceRef: null,
|
|
327
|
+
blockedReason: null,
|
|
328
|
+
metadata: {},
|
|
329
|
+
createdAt: now,
|
|
330
|
+
updatedAt: now
|
|
331
|
+
});
|
|
332
|
+
const approval = {
|
|
333
|
+
id: approvalId,
|
|
334
|
+
goalId: input.goal.id,
|
|
335
|
+
kind: "create_plan",
|
|
336
|
+
title: operation === "edit" ? "Approve agent edit plan" : "Approve agent create plan",
|
|
337
|
+
reason: "OpenPond requires plan approval before mutating durable profile agent source.",
|
|
338
|
+
payload: {
|
|
339
|
+
requestId,
|
|
340
|
+
planId,
|
|
341
|
+
pipelineId: snapshot.id,
|
|
342
|
+
objective: input.goal.objective,
|
|
343
|
+
defaultActionKey: plan.defaultChatAction.key,
|
|
344
|
+
sourcePlan: plan.sourcePlan,
|
|
345
|
+
checks: plan.checks
|
|
346
|
+
},
|
|
347
|
+
createdAt: now,
|
|
348
|
+
status: "pending",
|
|
349
|
+
decidedAt: null,
|
|
350
|
+
decisionNote: null
|
|
351
|
+
};
|
|
352
|
+
return { snapshot, approval };
|
|
353
|
+
}
|
|
354
|
+
function slugFromObjective(objective) {
|
|
355
|
+
return normalizeAgentId(
|
|
356
|
+
objective.toLowerCase().replace(/['"]/g, "").replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 48) || "created-agent"
|
|
357
|
+
);
|
|
358
|
+
}
|
|
359
|
+
function sourceRootPathForAgent(agentId) {
|
|
360
|
+
return agentId === "default" ? "agent" : `agents/${agentId}`;
|
|
361
|
+
}
|
|
362
|
+
function sdkOutputPath(sourceRoot, fileName) {
|
|
363
|
+
return sourceRoot === "agent" ? `.openpond/${fileName}` : `${sourceRoot}/.openpond/${fileName}`;
|
|
364
|
+
}
|
|
365
|
+
function normalizeAgentId(value) {
|
|
366
|
+
const normalized = value.trim().toLowerCase().replace(/['"]/g, "").replace(/[^a-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 64);
|
|
367
|
+
return normalized || "created-agent";
|
|
368
|
+
}
|
|
369
|
+
function approveCreatePipelinePlan(goal, approvalId) {
|
|
370
|
+
return decideCreatePipelinePlan(goal, approvalId, "approved");
|
|
371
|
+
}
|
|
372
|
+
function rejectCreatePipelinePlan(goal, approvalId, decisionNote) {
|
|
373
|
+
return decideCreatePipelinePlan(goal, approvalId, "rejected", decisionNote);
|
|
374
|
+
}
|
|
375
|
+
function cancelCreatePipelinePlan(goal, approvalId, decisionNote) {
|
|
376
|
+
return decideCreatePipelinePlan(goal, approvalId, "cancelled", decisionNote);
|
|
377
|
+
}
|
|
378
|
+
function reviseCreatePipelinePlan(goal, input) {
|
|
379
|
+
const pipeline = goal.createPipeline;
|
|
380
|
+
if (!pipeline?.plan) {
|
|
381
|
+
throw new Error("goal has no create plan to edit");
|
|
382
|
+
}
|
|
383
|
+
if (pipeline.plan.status !== "pending_approval") {
|
|
384
|
+
throw new Error("only a pending create plan can be edited before source mutation");
|
|
385
|
+
}
|
|
386
|
+
const revision = input.revision.trim();
|
|
387
|
+
if (!revision) {
|
|
388
|
+
throw new Error("plan edit instructions are required");
|
|
389
|
+
}
|
|
390
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
391
|
+
const previousPlan = pipeline.plan;
|
|
392
|
+
const nextPlan = CreatePipelinePlanSchema.parse({
|
|
393
|
+
...previousPlan,
|
|
394
|
+
id: `create_plan_${randomUUID2()}`,
|
|
395
|
+
status: "pending_approval",
|
|
396
|
+
summary: `${previousPlan.summary}
|
|
397
|
+
|
|
398
|
+
Revision requested: ${revision}`,
|
|
399
|
+
sourcePlan: previousPlan.sourcePlan.map((item) => ({
|
|
400
|
+
...item,
|
|
401
|
+
reason: `${item.reason} Revision requested: ${revision}`
|
|
402
|
+
})),
|
|
403
|
+
approvedAt: null,
|
|
404
|
+
editedFromPlanId: previousPlan.id,
|
|
405
|
+
metadata: {
|
|
406
|
+
...previousPlan.metadata,
|
|
407
|
+
revision,
|
|
408
|
+
supersedesPlanId: previousPlan.id
|
|
409
|
+
},
|
|
410
|
+
createdAt: now,
|
|
411
|
+
updatedAt: now
|
|
412
|
+
});
|
|
413
|
+
const approvals = goal.approvals.map(
|
|
414
|
+
(approval) => approval.id === previousPlan.approvalId ? {
|
|
415
|
+
...approval,
|
|
416
|
+
title: "Approve revised agent plan",
|
|
417
|
+
reason: "OpenPond requires approval of the revised plan before mutating durable profile agent source.",
|
|
418
|
+
payload: {
|
|
419
|
+
...approval.payload,
|
|
420
|
+
planId: nextPlan.id,
|
|
421
|
+
previousPlanId: previousPlan.id,
|
|
422
|
+
objective: nextPlan.objective,
|
|
423
|
+
defaultActionKey: nextPlan.defaultChatAction.key,
|
|
424
|
+
sourcePlan: nextPlan.sourcePlan,
|
|
425
|
+
checks: nextPlan.checks,
|
|
426
|
+
revision
|
|
427
|
+
},
|
|
428
|
+
status: "pending",
|
|
429
|
+
decidedAt: null,
|
|
430
|
+
decisionNote: null
|
|
431
|
+
} : approval
|
|
432
|
+
);
|
|
433
|
+
return {
|
|
434
|
+
...goal,
|
|
435
|
+
status: "awaiting_approval",
|
|
436
|
+
approvals,
|
|
437
|
+
createPipeline: CreatePipelineSnapshotSchema.parse({
|
|
438
|
+
...pipeline,
|
|
439
|
+
state: "awaiting_plan_approval",
|
|
440
|
+
plan: nextPlan,
|
|
441
|
+
blockedReason: null,
|
|
442
|
+
metadata: {
|
|
443
|
+
...pipeline.metadata,
|
|
444
|
+
previousPlanId: previousPlan.id
|
|
445
|
+
},
|
|
446
|
+
updatedAt: now
|
|
447
|
+
}),
|
|
448
|
+
updatedAt: now
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
function decideCreatePipelinePlan(goal, approvalId, decision, decisionNote) {
|
|
452
|
+
const pipeline = goal.createPipeline;
|
|
453
|
+
if (!pipeline?.plan || pipeline.plan.approvalId !== approvalId) return goal;
|
|
454
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
455
|
+
const nextState = decision === "approved" ? "applying_source" : decision === "cancelled" ? "cancelled" : "blocked";
|
|
456
|
+
return {
|
|
457
|
+
...goal,
|
|
458
|
+
createPipeline: CreatePipelineSnapshotSchema.parse({
|
|
459
|
+
...pipeline,
|
|
460
|
+
state: nextState,
|
|
461
|
+
plan: {
|
|
462
|
+
...pipeline.plan,
|
|
463
|
+
status: decision,
|
|
464
|
+
approvedAt: decision === "approved" ? now : pipeline.plan.approvedAt,
|
|
465
|
+
metadata: {
|
|
466
|
+
...pipeline.plan.metadata,
|
|
467
|
+
decision,
|
|
468
|
+
...decisionNote ? { decisionNote } : {}
|
|
469
|
+
},
|
|
470
|
+
updatedAt: now
|
|
471
|
+
},
|
|
472
|
+
blockedReason: decision === "rejected" ? "Create plan rejected before source mutation." : decision === "cancelled" ? "Create plan cancelled before source mutation." : null,
|
|
473
|
+
updatedAt: now
|
|
474
|
+
})
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// src/goal/events.ts
|
|
479
|
+
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
480
|
+
function createGoalEvent(input) {
|
|
481
|
+
return {
|
|
482
|
+
id: `event_${randomUUID3()}`,
|
|
483
|
+
goalId: input.goalId,
|
|
484
|
+
iterationId: input.iterationId ?? null,
|
|
485
|
+
kind: input.kind,
|
|
486
|
+
summary: input.summary,
|
|
487
|
+
payload: input.payload ?? {},
|
|
488
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
function serializeGoalEventRecord(event) {
|
|
492
|
+
return JSON.stringify(event);
|
|
493
|
+
}
|
|
494
|
+
async function recordGoalEvent(event, targets) {
|
|
495
|
+
await targets.localState?.appendEvent(event.goalId, event);
|
|
496
|
+
await targets.hostedClient?.appendEvent(event.goalId, event);
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
// src/goal/runner.ts
|
|
500
|
+
import { randomUUID as randomUUID8 } from "node:crypto";
|
|
501
|
+
|
|
502
|
+
// src/goal/budgets.ts
|
|
503
|
+
function goalIterationBudgetRemaining(goal) {
|
|
504
|
+
const startedIterations = goal.events.filter(
|
|
505
|
+
(event) => event.kind === "iteration.started"
|
|
506
|
+
).length;
|
|
507
|
+
return Math.max(0, goal.budget.maxIterations - startedIterations);
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
// src/goal/prompts.ts
|
|
511
|
+
var GENERIC_CODING_PROMPT = `# Generic Coding Goal
|
|
512
|
+
|
|
513
|
+
You are working on a source-backed coding goal. Make scoped source edits, run the configured checks, ask structured questions when blocked, and return a reviewable result.`;
|
|
514
|
+
var CREATE_AGENT_PROMPT = `# Create OpenPond Agent Goal
|
|
515
|
+
|
|
516
|
+
Create a source-backed OpenPond agent from the user's prompt. Ask structured questions for missing setup decisions, run the project-local OpenPond agent SDK commands through the openpond_agent_* tools, and leave a reviewable source update.
|
|
517
|
+
|
|
518
|
+
Do not invoke openpond-agent through shell, npx, pnpm dlx, or yarn dlx.
|
|
519
|
+
|
|
520
|
+
Create-agent and edit-agent Goals use a create pipeline. The pipeline request, plan, and workflow capture are durable Goal state under the configured local Goal storage root, normally ~/.openpond/goals/<goalId>; they are operational run history, not profile source to commit. Source mutation is allowed only after the create_plan approval is approved. When the Goal metadata contains an approved create pipeline plan, follow that plan, preserve or create a default chat action, keep openpond_chat routed to that default action, and run the required SDK checks before claiming the agent is ready.
|
|
521
|
+
|
|
522
|
+
## Create Plan Action Shape
|
|
523
|
+
|
|
524
|
+
When you author or revise a create plan, decide the public action shape from the user request, captured conversation context, attachments, prior tool/action evidence, existing profile catalog, and answered questions. Do not use hardcoded business examples or hidden keyword lists.
|
|
525
|
+
|
|
526
|
+
Record the decision as metadata.actionShape on the create plan:
|
|
527
|
+
|
|
528
|
+
{
|
|
529
|
+
"mode": "chat | direct_action | chat_and_direct_actions",
|
|
530
|
+
"label": "Short user-visible shape label",
|
|
531
|
+
"detail": "Why this shape fits the request",
|
|
532
|
+
"defaultActionKey": "chat or the generated default chat action id",
|
|
533
|
+
"directActionHint": "The repeatable direct action to expose, or null",
|
|
534
|
+
"artifactPolicy": "What run summaries, traces, and output artifacts should be persisted"
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
Use chat when the request is mainly conversational: answer questions, help with analysis, route follow-ups, or operate through normal OpenPond chat. Use direct_action when the request is a repeatable runnable operation with clear inputs and outputs, such as producing a report, transforming an artifact, running a workflow, exporting/importing data, or triggering a deterministic task without needing a conversation. Use chat_and_direct_actions when both a normal chat surface and one or more repeatable catalog actions are useful. If the source data, setup, output artifact, or invocation shape is ambiguous enough to change source design, ask a focused questions_ask question before requesting approval instead of guessing.
|
|
538
|
+
|
|
539
|
+
If an existing plan says metadata.actionShapeDecisionSource is default_chat_fallback, treat that shape as a conservative app fallback rather than a model decision; refine the generated actions from the approved objective and captured context when the requested behavior clearly needs a direct action.
|
|
540
|
+
|
|
541
|
+
Hosted create-agent goals may start from a generated OpenPond SDK project with package.json, openpond.yaml, and agent/** already present. Treat that as the scaffold to edit. Do not exhaust tool rounds reading every source file. Once you understand the scaffold and answered questions, update the minimal source files needed, run openpond_agent_default_checks, and then return a final no-tool response. If checks fail, use the check output and trace refs as repair input, edit the source or eval that is wrong, and rerun checks before returning a final response. Generated evals must test the behavior the agent actually implements; if you add a new intent/action, wire the default chat path or the eval input so the intent and result contract match.
|
|
542
|
+
|
|
543
|
+
Do not inspect the openpond-agent-sdk package internals, vendored SDK source, or generated .openpond outputs unless an OpenPond agent check explicitly reports an error in those files. Use the existing agent/** files and the openpond_agent_* tools as the contract.
|
|
544
|
+
|
|
545
|
+
For SharePoint or other unavailable external systems, do not spend multiple rounds searching for CLIs, SDK packages, credentials, or network access. If the user gives enough target details but real external access is unavailable, create a local draft agent behavior that detects the request, explains the missing external access honestly, and includes eval coverage for that intent. Then run openpond_agent_default_checks and return a final answer with the external access blocker. Do not claim that an external read/write succeeded.`;
|
|
546
|
+
var UPDATE_AGENT_PROMPT = `# Update OpenPond Agent Goal
|
|
547
|
+
|
|
548
|
+
Update an existing source-backed OpenPond agent from the user's request and evidence refs. Run before/after checks through the openpond_agent_* tools and leave a reviewable source update. Do not invoke openpond-agent through shell, npx, pnpm dlx, or yarn dlx.`;
|
|
549
|
+
function resolveGoalPromptPack(goal) {
|
|
550
|
+
if (goal.promptPack === "openpond_agent_create_v1") {
|
|
551
|
+
return {
|
|
552
|
+
id: goal.promptPack,
|
|
553
|
+
title: "Create OpenPond Agent Goal",
|
|
554
|
+
instructions: CREATE_AGENT_PROMPT
|
|
555
|
+
};
|
|
556
|
+
}
|
|
557
|
+
if (goal.promptPack === "openpond_agent_update_v1") {
|
|
558
|
+
return {
|
|
559
|
+
id: goal.promptPack,
|
|
560
|
+
title: "Update OpenPond Agent Goal",
|
|
561
|
+
instructions: UPDATE_AGENT_PROMPT
|
|
562
|
+
};
|
|
563
|
+
}
|
|
564
|
+
return {
|
|
565
|
+
id: goal.promptPack || "generic_coding_v1",
|
|
566
|
+
title: "Generic Coding Goal",
|
|
567
|
+
instructions: GENERIC_CODING_PROMPT
|
|
568
|
+
};
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
// src/goal/redaction.ts
|
|
572
|
+
var SECRET_PATTERNS = [
|
|
573
|
+
/\bopk_[A-Za-z0-9_-]{12,}\b/g,
|
|
574
|
+
/\bsk-[A-Za-z0-9_-]{12,}\b/g,
|
|
575
|
+
/\bghp_[A-Za-z0-9_]{12,}\b/g,
|
|
576
|
+
/\bgithub_pat_[A-Za-z0-9_]{12,}\b/g,
|
|
577
|
+
/\bOPENPOND_[A-Z0-9_]*(?:API_KEY|TOKEN|SECRET)\s*=\s*[^\s]+/g,
|
|
578
|
+
/\b(?:API_KEY|TOKEN|SECRET|PASSWORD)\s*=\s*[^\s]+/gi
|
|
579
|
+
];
|
|
580
|
+
var REDACTED = "[redacted]";
|
|
581
|
+
function redactString(value) {
|
|
582
|
+
return SECRET_PATTERNS.reduce(
|
|
583
|
+
(current, pattern) => current.replace(pattern, REDACTED),
|
|
584
|
+
value
|
|
585
|
+
);
|
|
586
|
+
}
|
|
587
|
+
function truncateForEvent(value, maxLength = 4e3) {
|
|
588
|
+
const redacted = redactString(value);
|
|
589
|
+
if (redacted.length <= maxLength) return redacted;
|
|
590
|
+
return `${redacted.slice(0, maxLength)}
|
|
591
|
+
[truncated ${redacted.length - maxLength} chars]`;
|
|
592
|
+
}
|
|
593
|
+
function redactJson(value) {
|
|
594
|
+
if (typeof value === "string") return redactString(value);
|
|
595
|
+
if (Array.isArray(value)) return value.map((entry) => redactJson(entry));
|
|
596
|
+
if (!value || typeof value !== "object") return value;
|
|
597
|
+
return Object.fromEntries(
|
|
598
|
+
Object.entries(value).map(([key, entry]) => [
|
|
599
|
+
key,
|
|
600
|
+
redactJson(entry)
|
|
601
|
+
])
|
|
602
|
+
);
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
// src/goal/llm.ts
|
|
606
|
+
function buildGoalLlmMessages(goal) {
|
|
607
|
+
const promptPack = resolveGoalPromptPack(goal);
|
|
608
|
+
return [
|
|
609
|
+
{
|
|
610
|
+
role: "system",
|
|
611
|
+
content: [
|
|
612
|
+
"You are the OpenPond Goal runner. Produce executable tool calls, ask structured questions when blocked, and stay within the provided execution policy.",
|
|
613
|
+
"",
|
|
614
|
+
promptPack.instructions,
|
|
615
|
+
"",
|
|
616
|
+
"Runner rules:",
|
|
617
|
+
"- Use files_write for source edits instead of describing edits.",
|
|
618
|
+
"- Use questions_ask for required user input.",
|
|
619
|
+
"- Use checks_run or configured verification commands before claiming completion.",
|
|
620
|
+
"- Hosted coding sandboxes include node, npm, pnpm, and common POSIX utilities; follow the target repository's lockfile when choosing install/test commands, use command -v for PATH checks, and do not block on standalone toolchain probes once a needed package manager command works.",
|
|
621
|
+
"- For OpenPond agent goals, run SDK actions through openpond_agent_* tools. Do not invoke openpond-agent through shell, npx, pnpm dlx, or yarn dlx.",
|
|
622
|
+
"- For OpenPond agent goals, after source edits use openpond_agent_default_checks unless a narrower SDK command is clearly enough; do not spend the remaining rounds on broad inspection.",
|
|
623
|
+
"- After required edits and checks are complete, return a concise final response with no tool calls.",
|
|
624
|
+
"- Do not keep inspecting once you have enough context to make the requested change.",
|
|
625
|
+
"- Do not claim external integration reads or writes succeeded without tool evidence."
|
|
626
|
+
].join("\n")
|
|
627
|
+
},
|
|
628
|
+
{
|
|
629
|
+
role: "user",
|
|
630
|
+
content: buildGoalUserContext(goal)
|
|
631
|
+
}
|
|
632
|
+
];
|
|
633
|
+
}
|
|
634
|
+
function buildGoalUserContext(goal) {
|
|
635
|
+
const answeredQuestions = goal.questions.filter((question) => question.answeredAt).map((question) => {
|
|
636
|
+
const answer = goal.answers.find((item) => item.questionId === question.id);
|
|
637
|
+
return {
|
|
638
|
+
question: question.title,
|
|
639
|
+
optionId: answer?.optionId ?? null,
|
|
640
|
+
answer: answer?.freeformText ?? null,
|
|
641
|
+
value: answer?.value ?? {}
|
|
642
|
+
};
|
|
643
|
+
});
|
|
644
|
+
const openQuestions = goal.questions.filter((question) => !question.answeredAt).map((question) => ({
|
|
645
|
+
title: question.title,
|
|
646
|
+
reason: question.reason,
|
|
647
|
+
required: question.required,
|
|
648
|
+
options: question.options,
|
|
649
|
+
freeformAllowed: question.freeformAllowed
|
|
650
|
+
}));
|
|
651
|
+
const approvals = goal.approvals.map((approval) => ({
|
|
652
|
+
kind: approval.kind,
|
|
653
|
+
status: approval.status,
|
|
654
|
+
title: approval.title,
|
|
655
|
+
reason: approval.reason,
|
|
656
|
+
decisionNote: approval.decisionNote,
|
|
657
|
+
decidedAt: approval.decidedAt
|
|
658
|
+
}));
|
|
659
|
+
const createPipeline = goal.createPipeline ? {
|
|
660
|
+
id: goal.createPipeline.id,
|
|
661
|
+
state: goal.createPipeline.state,
|
|
662
|
+
request: {
|
|
663
|
+
id: goal.createPipeline.request.id,
|
|
664
|
+
operation: goal.createPipeline.request.operation,
|
|
665
|
+
surface: goal.createPipeline.request.surface,
|
|
666
|
+
command: goal.createPipeline.request.command,
|
|
667
|
+
sourceAuthority: goal.createPipeline.request.adapter.sourceAuthority,
|
|
668
|
+
confirmationPolicy: goal.createPipeline.request.adapter.confirmationPolicy
|
|
669
|
+
},
|
|
670
|
+
plan: goal.createPipeline.plan ? {
|
|
671
|
+
id: goal.createPipeline.plan.id,
|
|
672
|
+
status: goal.createPipeline.plan.status,
|
|
673
|
+
summary: goal.createPipeline.plan.summary,
|
|
674
|
+
defaultChatAction: goal.createPipeline.plan.defaultChatAction,
|
|
675
|
+
sourcePlan: goal.createPipeline.plan.sourcePlan,
|
|
676
|
+
requirements: goal.createPipeline.plan.requirements,
|
|
677
|
+
checks: goal.createPipeline.plan.checks,
|
|
678
|
+
metadata: {
|
|
679
|
+
actionShape: goal.createPipeline.plan.metadata?.actionShape ?? null,
|
|
680
|
+
actionShapeDecisionSource: goal.createPipeline.plan.metadata?.actionShapeDecisionSource ?? null
|
|
681
|
+
},
|
|
682
|
+
approvedAt: goal.createPipeline.plan.approvedAt
|
|
683
|
+
} : null,
|
|
684
|
+
workflowCapture: goal.createPipeline.workflowCapture ? {
|
|
685
|
+
id: goal.createPipeline.workflowCapture.id,
|
|
686
|
+
tools: goal.createPipeline.workflowCapture.tools,
|
|
687
|
+
apps: goal.createPipeline.workflowCapture.apps,
|
|
688
|
+
targetRepoAssumptions: goal.createPipeline.workflowCapture.targetRepoAssumptions
|
|
689
|
+
} : null
|
|
690
|
+
} : null;
|
|
691
|
+
return [
|
|
692
|
+
`Objective: ${goal.objective}`,
|
|
693
|
+
"",
|
|
694
|
+
"Goal metadata:",
|
|
695
|
+
JSON.stringify(
|
|
696
|
+
{
|
|
697
|
+
goalId: goal.id,
|
|
698
|
+
kind: goal.kind,
|
|
699
|
+
profile: goal.profile,
|
|
700
|
+
promptPack: goal.promptPack,
|
|
701
|
+
status: goal.status,
|
|
702
|
+
constraints: goal.constraints,
|
|
703
|
+
evidenceRefs: goal.evidenceRefs,
|
|
704
|
+
executionPolicy: goal.executionPolicy,
|
|
705
|
+
verification: goal.verification,
|
|
706
|
+
createPipeline,
|
|
707
|
+
answeredQuestions,
|
|
708
|
+
openQuestions,
|
|
709
|
+
approvals
|
|
710
|
+
},
|
|
711
|
+
null,
|
|
712
|
+
2
|
|
713
|
+
)
|
|
714
|
+
].join("\n");
|
|
715
|
+
}
|
|
716
|
+
async function callHostedGoalLlm(params) {
|
|
717
|
+
const request = {
|
|
718
|
+
goalId: params.goal.id,
|
|
719
|
+
iterationId: params.iterationId,
|
|
720
|
+
promptPack: params.goal.promptPack,
|
|
721
|
+
messages: params.messages ?? buildGoalLlmMessages(params.goal),
|
|
722
|
+
tools: params.tools,
|
|
723
|
+
metadata: params.metadata
|
|
724
|
+
};
|
|
725
|
+
const response = await params.hostedClient.callLlm(request);
|
|
726
|
+
if (response.usage) {
|
|
727
|
+
await recordGoalEvent(
|
|
728
|
+
createGoalEvent({
|
|
729
|
+
goalId: params.goal.id,
|
|
730
|
+
iterationId: params.iterationId,
|
|
731
|
+
kind: "model.usage",
|
|
732
|
+
summary: "Goal LLM call completed",
|
|
733
|
+
payload: redactJson({ usage: response.usage })
|
|
734
|
+
}),
|
|
735
|
+
{ localState: params.localState, hostedClient: params.hostedClient }
|
|
736
|
+
);
|
|
737
|
+
}
|
|
738
|
+
return response;
|
|
739
|
+
}
|
|
740
|
+
function resolveLocalGoalLlmConfig(env = typeof process === "undefined" ? {} : process.env) {
|
|
741
|
+
const token = env.OPENPOND_GOAL_OPCHAT_API_KEY?.trim() || env.OPENPOND_OPCHAT_API_KEY?.trim() || env.OPENPOND_API_KEY?.trim();
|
|
742
|
+
if (!token) return null;
|
|
743
|
+
return {
|
|
744
|
+
apiBaseUrl: resolveOpChatApiBaseUrl({ env }),
|
|
745
|
+
token,
|
|
746
|
+
model: env.OPENPOND_GOAL_MODEL?.trim() || env.OPENPOND_OPCHAT_MODEL?.trim() || "openpond-chat"
|
|
747
|
+
};
|
|
748
|
+
}
|
|
749
|
+
async function callLocalGoalLlm(params) {
|
|
750
|
+
const config = resolveLocalGoalLlmConfig(params.env);
|
|
751
|
+
if (!config) return null;
|
|
752
|
+
const completion = await sendHostedChatTurn({
|
|
753
|
+
apiBaseUrl: config.apiBaseUrl,
|
|
754
|
+
token: config.token,
|
|
755
|
+
model: config.model,
|
|
756
|
+
messages: params.messages ?? buildGoalLlmMessages(params.goal),
|
|
757
|
+
tools: params.tools,
|
|
758
|
+
toolChoice: params.tools?.length ? "auto" : void 0,
|
|
759
|
+
temperature: 0.2,
|
|
760
|
+
metadata: {
|
|
761
|
+
source: "openpond-code-goal",
|
|
762
|
+
goalId: params.goal.id,
|
|
763
|
+
promptPack: params.goal.promptPack,
|
|
764
|
+
...params.metadata ?? {}
|
|
765
|
+
}
|
|
766
|
+
});
|
|
767
|
+
const response = goalLlmResponseFromHostedChatCompletion(completion, {
|
|
768
|
+
afterToolResults: (params.messages ?? []).some(
|
|
769
|
+
(message) => message.role === "tool"
|
|
770
|
+
)
|
|
771
|
+
});
|
|
772
|
+
if (response.usage) {
|
|
773
|
+
await recordGoalEvent(
|
|
774
|
+
createGoalEvent({
|
|
775
|
+
goalId: params.goal.id,
|
|
776
|
+
iterationId: params.iterationId,
|
|
777
|
+
kind: "model.usage",
|
|
778
|
+
summary: "Local Goal LLM call completed",
|
|
779
|
+
payload: redactJson({ usage: response.usage })
|
|
780
|
+
}),
|
|
781
|
+
{ localState: params.localState }
|
|
782
|
+
);
|
|
783
|
+
}
|
|
784
|
+
return response;
|
|
785
|
+
}
|
|
786
|
+
function goalLlmResponseFromHostedChatCompletion(completion, options = {}) {
|
|
787
|
+
const choice = completion.choices?.[0];
|
|
788
|
+
const message = choice?.message;
|
|
789
|
+
const content = typeof message?.content === "string" ? message.content.trim() : "";
|
|
790
|
+
const toolCalls = (message?.tool_calls ?? []).flatMap((toolCall) => {
|
|
791
|
+
const id = typeof toolCall.id === "string" ? toolCall.id : "";
|
|
792
|
+
const name = typeof toolCall.function?.name === "string" ? toolCall.function.name : "";
|
|
793
|
+
if (!id || !name) return [];
|
|
794
|
+
return [
|
|
795
|
+
{
|
|
796
|
+
id,
|
|
797
|
+
name,
|
|
798
|
+
arguments: parseToolArguments(toolCall.function?.arguments)
|
|
799
|
+
}
|
|
800
|
+
];
|
|
801
|
+
});
|
|
802
|
+
return {
|
|
803
|
+
status: toolCalls.length > 0 || options.afterToolResults ? "ok" : "blocked",
|
|
804
|
+
summary: toolCalls.length > 0 ? `Goal planner returned ${toolCalls.length} tool call${toolCalls.length === 1 ? "" : "s"}.` : content || "Goal planner returned no executable tool calls.",
|
|
805
|
+
message: {
|
|
806
|
+
role: "assistant",
|
|
807
|
+
content
|
|
808
|
+
},
|
|
809
|
+
...toolCalls.length > 0 ? { toolCalls } : {},
|
|
810
|
+
...completion.usage ? {
|
|
811
|
+
usage: {
|
|
812
|
+
inputTokens: completion.usage.prompt_tokens ?? completion.usage.input_tokens,
|
|
813
|
+
outputTokens: completion.usage.completion_tokens ?? completion.usage.output_tokens,
|
|
814
|
+
totalTokens: completion.usage.total_tokens
|
|
815
|
+
}
|
|
816
|
+
} : {}
|
|
817
|
+
};
|
|
818
|
+
}
|
|
819
|
+
function parseToolArguments(value) {
|
|
820
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
821
|
+
return value;
|
|
822
|
+
}
|
|
823
|
+
if (typeof value !== "string" || !value.trim()) return {};
|
|
824
|
+
try {
|
|
825
|
+
const parsed = JSON.parse(value);
|
|
826
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
|
|
827
|
+
} catch {
|
|
828
|
+
return {};
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
// src/goal/result.ts
|
|
833
|
+
function createGoalRunResult(input) {
|
|
834
|
+
return {
|
|
835
|
+
goalId: input.goal.id,
|
|
836
|
+
status: input.status,
|
|
837
|
+
summary: input.summary,
|
|
838
|
+
events: input.events
|
|
839
|
+
};
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
// src/goal/local-create-pipeline.ts
|
|
843
|
+
var MODEL_BACKED_LOCAL_CREATE_REQUIRED_REASON = "Approved local Create plans require model-backed SDK source application; no source mutation was performed.";
|
|
844
|
+
function shouldRunLocalCreatePipeline(goal, mode) {
|
|
845
|
+
const pipeline = goal.createPipeline;
|
|
846
|
+
return Boolean(
|
|
847
|
+
mode === "local" && pipeline && pipeline.state === "applying_source" && pipeline.request.adapter.kind === "local"
|
|
848
|
+
);
|
|
849
|
+
}
|
|
850
|
+
async function runLocalCreatePipeline(input) {
|
|
851
|
+
const localState = input.localState;
|
|
852
|
+
if (!localState) {
|
|
853
|
+
const blocked2 = createGoalEvent({
|
|
854
|
+
goalId: input.goal.id,
|
|
855
|
+
iterationId: input.iterationId,
|
|
856
|
+
kind: "goal.blocked",
|
|
857
|
+
summary: "Local create pipeline requires local Goal state",
|
|
858
|
+
payload: { reason: "missing_local_state" }
|
|
859
|
+
});
|
|
860
|
+
return createGoalRunResult({
|
|
861
|
+
goal: input.goal,
|
|
862
|
+
status: "blocked",
|
|
863
|
+
summary: blocked2.summary,
|
|
864
|
+
events: [...input.startedEvents, blocked2]
|
|
865
|
+
});
|
|
866
|
+
}
|
|
867
|
+
const current = await localState.get(input.goal.id) ?? input.goal;
|
|
868
|
+
const pipeline = current.createPipeline;
|
|
869
|
+
if (!pipeline || pipeline.request.adapter.kind !== "local") {
|
|
870
|
+
const blocked2 = createGoalEvent({
|
|
871
|
+
goalId: current.id,
|
|
872
|
+
iterationId: input.iterationId,
|
|
873
|
+
kind: "goal.blocked",
|
|
874
|
+
summary: "Goal does not have a local create pipeline to run",
|
|
875
|
+
payload: { reason: "missing_local_create_pipeline" }
|
|
876
|
+
});
|
|
877
|
+
await recordGoalEvent(blocked2, { localState });
|
|
878
|
+
await localState.update({ ...current, status: "blocked" });
|
|
879
|
+
return createGoalRunResult({
|
|
880
|
+
goal: current,
|
|
881
|
+
status: "blocked",
|
|
882
|
+
summary: blocked2.summary,
|
|
883
|
+
events: [...input.startedEvents, blocked2]
|
|
884
|
+
});
|
|
885
|
+
}
|
|
886
|
+
const events = [...input.startedEvents];
|
|
887
|
+
const blockedTransition = await transitionLocalCreatePipeline({
|
|
888
|
+
localState,
|
|
889
|
+
goalId: current.id,
|
|
890
|
+
iterationId: input.iterationId,
|
|
891
|
+
state: "blocked",
|
|
892
|
+
summary: MODEL_BACKED_LOCAL_CREATE_REQUIRED_REASON,
|
|
893
|
+
blockedReason: MODEL_BACKED_LOCAL_CREATE_REQUIRED_REASON
|
|
894
|
+
});
|
|
895
|
+
events.push(blockedTransition.event);
|
|
896
|
+
await localState.update({
|
|
897
|
+
...blockedTransition.goal,
|
|
898
|
+
status: "blocked"
|
|
899
|
+
});
|
|
900
|
+
const blocked = createGoalEvent({
|
|
901
|
+
goalId: current.id,
|
|
902
|
+
iterationId: input.iterationId,
|
|
903
|
+
kind: "goal.blocked",
|
|
904
|
+
summary: MODEL_BACKED_LOCAL_CREATE_REQUIRED_REASON,
|
|
905
|
+
payload: {
|
|
906
|
+
pipelineId: pipeline.id,
|
|
907
|
+
operation: pipeline.request.operation,
|
|
908
|
+
reason: "model_backed_source_application_required",
|
|
909
|
+
sourcePath: pipeline.request.adapter.sourcePath ?? null,
|
|
910
|
+
repoPath: pipeline.request.adapter.repoPath ?? null
|
|
911
|
+
}
|
|
912
|
+
});
|
|
913
|
+
await recordGoalEvent(blocked, { localState });
|
|
914
|
+
events.push(blocked);
|
|
915
|
+
return createGoalRunResult({
|
|
916
|
+
goal: blockedTransition.goal,
|
|
917
|
+
status: "blocked",
|
|
918
|
+
summary: blocked.summary,
|
|
919
|
+
events
|
|
920
|
+
});
|
|
921
|
+
}
|
|
922
|
+
async function transitionLocalCreatePipeline(input) {
|
|
923
|
+
const current = await input.localState.get(input.goalId);
|
|
924
|
+
if (!current?.createPipeline) {
|
|
925
|
+
throw new Error(`goal has no create pipeline: ${input.goalId}`);
|
|
926
|
+
}
|
|
927
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
928
|
+
const previousState = current.createPipeline.state;
|
|
929
|
+
const nextPipeline = CreatePipelineSnapshotSchema.parse({
|
|
930
|
+
...current.createPipeline,
|
|
931
|
+
state: input.state,
|
|
932
|
+
checkRefs: mergeRefs(current.createPipeline.checkRefs, input.checkRefs ?? []),
|
|
933
|
+
blockedReason: input.blockedReason === void 0 ? current.createPipeline.blockedReason : input.blockedReason,
|
|
934
|
+
updatedAt: now
|
|
935
|
+
});
|
|
936
|
+
const goal = await input.localState.update({
|
|
937
|
+
...current,
|
|
938
|
+
createPipeline: nextPipeline,
|
|
939
|
+
updatedAt: now
|
|
940
|
+
});
|
|
941
|
+
const event = createGoalEvent({
|
|
942
|
+
goalId: input.goalId,
|
|
943
|
+
iterationId: input.iterationId,
|
|
944
|
+
kind: "create_pipeline.status_changed",
|
|
945
|
+
summary: input.summary,
|
|
946
|
+
payload: {
|
|
947
|
+
fromState: previousState,
|
|
948
|
+
toState: input.state,
|
|
949
|
+
pipelineId: nextPipeline.id,
|
|
950
|
+
blockedReason: nextPipeline.blockedReason,
|
|
951
|
+
checkRefs: nextPipeline.checkRefs
|
|
952
|
+
}
|
|
953
|
+
});
|
|
954
|
+
await recordGoalEvent(event, { localState: input.localState });
|
|
955
|
+
return {
|
|
956
|
+
goal: await input.localState.get(input.goalId) ?? goal,
|
|
957
|
+
event
|
|
958
|
+
};
|
|
959
|
+
}
|
|
960
|
+
function mergeRefs(existing, next) {
|
|
961
|
+
return Array.from(new Set([...existing, ...next].filter(Boolean)));
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
// src/goal/policy.ts
|
|
965
|
+
function assertGoalCanStartIteration(goal) {
|
|
966
|
+
if (goal.status === "cancelled" || goal.status === "completed" || goal.status === "failed") {
|
|
967
|
+
throw new Error(`goal is terminal: ${goal.status}`);
|
|
968
|
+
}
|
|
969
|
+
if (goal.status === "paused") throw new Error("goal is paused");
|
|
970
|
+
if (goal.status === "awaiting_user_input") {
|
|
971
|
+
throw new Error("goal is awaiting required user input");
|
|
972
|
+
}
|
|
973
|
+
if (goal.status === "awaiting_approval") {
|
|
974
|
+
throw new Error("goal is awaiting approval");
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
// src/goal/profiles/generic-coding.ts
|
|
979
|
+
var GENERIC_CODING_PROFILE = {
|
|
980
|
+
id: "generic_coding",
|
|
981
|
+
promptPack: "generic_coding_v1",
|
|
982
|
+
defaultVerificationCommands: [],
|
|
983
|
+
toolNames: [
|
|
984
|
+
"shell",
|
|
985
|
+
"files",
|
|
986
|
+
"checks",
|
|
987
|
+
"artifacts",
|
|
988
|
+
"questions",
|
|
989
|
+
"approvals",
|
|
990
|
+
"source"
|
|
991
|
+
]
|
|
992
|
+
};
|
|
993
|
+
|
|
994
|
+
// src/goal/profiles/openpond-agent.ts
|
|
995
|
+
var OPENPOND_AGENT_PROFILE = {
|
|
996
|
+
id: "openpond_agent",
|
|
997
|
+
promptPack: "openpond_agent_create_v1",
|
|
998
|
+
defaultVerificationCommands: [],
|
|
999
|
+
toolNames: [
|
|
1000
|
+
"shell",
|
|
1001
|
+
"files",
|
|
1002
|
+
"checks",
|
|
1003
|
+
"artifacts",
|
|
1004
|
+
"questions",
|
|
1005
|
+
"approvals",
|
|
1006
|
+
"source",
|
|
1007
|
+
"openpond_agent_sdk"
|
|
1008
|
+
]
|
|
1009
|
+
};
|
|
1010
|
+
|
|
1011
|
+
// src/goal/profiles/index.ts
|
|
1012
|
+
function getGoalProfileDescriptor(goal) {
|
|
1013
|
+
if (goal.profile === "openpond_agent") {
|
|
1014
|
+
return {
|
|
1015
|
+
...OPENPOND_AGENT_PROFILE,
|
|
1016
|
+
promptPack: goal.promptPack
|
|
1017
|
+
};
|
|
1018
|
+
}
|
|
1019
|
+
return {
|
|
1020
|
+
...GENERIC_CODING_PROFILE,
|
|
1021
|
+
promptPack: goal.promptPack
|
|
1022
|
+
};
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
// src/goal/tools/checks.ts
|
|
1026
|
+
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
1027
|
+
|
|
1028
|
+
// src/goal/limits.ts
|
|
1029
|
+
import { Buffer } from "node:buffer";
|
|
1030
|
+
var MAX_GOAL_FILE_READ_BYTES = 1024 * 1024;
|
|
1031
|
+
var MAX_GOAL_COMMAND_LOG_ARTIFACT_BYTES = 256 * 1024;
|
|
1032
|
+
function assertGoalFileReadSize(path, size) {
|
|
1033
|
+
if (size <= MAX_GOAL_FILE_READ_BYTES) return;
|
|
1034
|
+
throw new Error(
|
|
1035
|
+
`file is too large to read through goal tools: ${path} (${size} bytes, max ${MAX_GOAL_FILE_READ_BYTES} bytes)`
|
|
1036
|
+
);
|
|
1037
|
+
}
|
|
1038
|
+
function truncateGoalTextArtifact(content, maxBytes = MAX_GOAL_COMMAND_LOG_ARTIFACT_BYTES) {
|
|
1039
|
+
if (Buffer.byteLength(content, "utf-8") <= maxBytes) {
|
|
1040
|
+
return { content, truncated: false };
|
|
1041
|
+
}
|
|
1042
|
+
const marker = `
|
|
1043
|
+
[truncated after ${maxBytes} bytes]
|
|
1044
|
+
`;
|
|
1045
|
+
const bodyBytes = Math.max(0, maxBytes - Buffer.byteLength(marker, "utf-8"));
|
|
1046
|
+
return {
|
|
1047
|
+
content: `${Buffer.from(content).subarray(0, bodyBytes).toString("utf8")}${marker}`,
|
|
1048
|
+
truncated: true
|
|
1049
|
+
};
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
// src/goal/tools/artifacts.ts
|
|
1053
|
+
import { Buffer as Buffer2 } from "node:buffer";
|
|
1054
|
+
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
1055
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
1056
|
+
import os2 from "node:os";
|
|
1057
|
+
import { join as join2 } from "node:path";
|
|
1058
|
+
function safeFileName(value) {
|
|
1059
|
+
return value.replace(/[^A-Za-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || "artifact";
|
|
1060
|
+
}
|
|
1061
|
+
function createGoalArtifact(input) {
|
|
1062
|
+
const content = redactString(input.content);
|
|
1063
|
+
return {
|
|
1064
|
+
id: `artifact_${randomUUID4()}`,
|
|
1065
|
+
goalId: input.goalId,
|
|
1066
|
+
iterationId: input.iterationId ?? null,
|
|
1067
|
+
kind: input.kind,
|
|
1068
|
+
name: input.name,
|
|
1069
|
+
mimeType: input.mimeType ?? "text/plain",
|
|
1070
|
+
content,
|
|
1071
|
+
bytes: Buffer2.byteLength(content, "utf-8"),
|
|
1072
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1073
|
+
};
|
|
1074
|
+
}
|
|
1075
|
+
async function recordGoalArtifact(params) {
|
|
1076
|
+
const artifact = params.artifact;
|
|
1077
|
+
const ref = params.hostedClient ? await params.hostedClient.uploadArtifact(artifact) : await writeLocalArtifact(artifact, params.storageRoot ?? params.workspace);
|
|
1078
|
+
await recordGoalEvent(
|
|
1079
|
+
createGoalEvent({
|
|
1080
|
+
goalId: artifact.goalId,
|
|
1081
|
+
iterationId: artifact.iterationId,
|
|
1082
|
+
kind: "artifact.created",
|
|
1083
|
+
summary: `Artifact created: ${artifact.name}`,
|
|
1084
|
+
payload: {
|
|
1085
|
+
artifactRef: ref.ref,
|
|
1086
|
+
artifactId: ref.id,
|
|
1087
|
+
kind: ref.kind,
|
|
1088
|
+
name: ref.name,
|
|
1089
|
+
bytes: ref.bytes
|
|
1090
|
+
}
|
|
1091
|
+
}),
|
|
1092
|
+
{ localState: params.localState, hostedClient: params.hostedClient }
|
|
1093
|
+
);
|
|
1094
|
+
return ref;
|
|
1095
|
+
}
|
|
1096
|
+
async function writeLocalArtifact(artifact, storageRoot) {
|
|
1097
|
+
const root = storageRoot || process.cwd();
|
|
1098
|
+
const dir = join2(root, ".openpond", "goals", artifact.goalId, "artifacts");
|
|
1099
|
+
await mkdir(dir, { recursive: true });
|
|
1100
|
+
const fileName = `${artifact.id}-${safeFileName(artifact.name)}`;
|
|
1101
|
+
const path = join2(dir, fileName);
|
|
1102
|
+
await writeFile(path, artifact.content, "utf-8");
|
|
1103
|
+
const relativeRef = `.openpond/goals/${artifact.goalId}/artifacts/${fileName}`;
|
|
1104
|
+
return {
|
|
1105
|
+
id: artifact.id,
|
|
1106
|
+
ref: root === os2.homedir() ? `~/${relativeRef}` : relativeRef,
|
|
1107
|
+
kind: artifact.kind,
|
|
1108
|
+
name: artifact.name,
|
|
1109
|
+
mimeType: artifact.mimeType,
|
|
1110
|
+
bytes: artifact.bytes
|
|
1111
|
+
};
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
// src/goal/tools/shell.ts
|
|
1115
|
+
var DEFAULT_GOAL_SHELL_TIMEOUT_SECONDS = 120;
|
|
1116
|
+
async function runGoalShellCommand(params) {
|
|
1117
|
+
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1118
|
+
const redactedCommand = redactString(params.command);
|
|
1119
|
+
await recordGoalEvent(
|
|
1120
|
+
createGoalEvent({
|
|
1121
|
+
goalId: params.goalId,
|
|
1122
|
+
iterationId: params.iterationId,
|
|
1123
|
+
kind: "command.started",
|
|
1124
|
+
summary: `Command started: ${redactedCommand}`,
|
|
1125
|
+
payload: { command: redactedCommand, cwd: params.cwd }
|
|
1126
|
+
}),
|
|
1127
|
+
{ localState: params.localState, hostedClient: params.hostedClient }
|
|
1128
|
+
);
|
|
1129
|
+
const start = Date.now();
|
|
1130
|
+
const result = await runShellCommand(params.command, {
|
|
1131
|
+
cwd: params.cwd,
|
|
1132
|
+
env: params.env,
|
|
1133
|
+
timeoutSeconds: params.timeoutSeconds ?? DEFAULT_GOAL_SHELL_TIMEOUT_SECONDS
|
|
1134
|
+
});
|
|
1135
|
+
const completedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1136
|
+
const artifactRefs = [];
|
|
1137
|
+
const commandResult = {
|
|
1138
|
+
command: params.command,
|
|
1139
|
+
cwd: params.cwd,
|
|
1140
|
+
code: result.code,
|
|
1141
|
+
timedOut: result.timedOut,
|
|
1142
|
+
durationMs: Date.now() - start,
|
|
1143
|
+
stdoutTail: truncateForEvent(result.stdout),
|
|
1144
|
+
stderrTail: truncateForEvent(result.stderr),
|
|
1145
|
+
artifactRefs,
|
|
1146
|
+
startedAt,
|
|
1147
|
+
completedAt
|
|
1148
|
+
};
|
|
1149
|
+
const combinedOutput = [
|
|
1150
|
+
result.stdout ? `stdout:
|
|
1151
|
+
${result.stdout}` : "",
|
|
1152
|
+
result.stderr ? `stderr:
|
|
1153
|
+
${result.stderr}` : ""
|
|
1154
|
+
].filter(Boolean).join("\n\n");
|
|
1155
|
+
if (combinedOutput.trim()) {
|
|
1156
|
+
const artifactContent = truncateGoalTextArtifact(redactString(combinedOutput));
|
|
1157
|
+
const artifact = createGoalArtifact({
|
|
1158
|
+
goalId: params.goalId,
|
|
1159
|
+
iterationId: params.iterationId,
|
|
1160
|
+
kind: "command_log",
|
|
1161
|
+
name: `${redactedCommand.replace(/\s+/g, "_").slice(0, 80)}.log`,
|
|
1162
|
+
content: artifactContent.content
|
|
1163
|
+
});
|
|
1164
|
+
const ref = await recordGoalArtifact({
|
|
1165
|
+
artifact,
|
|
1166
|
+
workspace: params.workspace,
|
|
1167
|
+
storageRoot: params.storageRoot,
|
|
1168
|
+
localState: params.localState,
|
|
1169
|
+
hostedClient: params.hostedClient
|
|
1170
|
+
});
|
|
1171
|
+
artifactRefs.push(ref.ref);
|
|
1172
|
+
}
|
|
1173
|
+
await recordGoalEvent(
|
|
1174
|
+
createGoalEvent({
|
|
1175
|
+
goalId: params.goalId,
|
|
1176
|
+
iterationId: params.iterationId,
|
|
1177
|
+
kind: "command.completed",
|
|
1178
|
+
summary: result.code === 0 ? `Command completed: ${redactedCommand}` : `Command failed: ${redactedCommand}`,
|
|
1179
|
+
payload: {
|
|
1180
|
+
...commandResult,
|
|
1181
|
+
command: redactedCommand,
|
|
1182
|
+
artifactRefs
|
|
1183
|
+
}
|
|
1184
|
+
}),
|
|
1185
|
+
{ localState: params.localState, hostedClient: params.hostedClient }
|
|
1186
|
+
);
|
|
1187
|
+
return commandResult;
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
// src/goal/tools/checks.ts
|
|
1191
|
+
async function runGoalVerificationChecks(params) {
|
|
1192
|
+
const checks = [];
|
|
1193
|
+
for (const command of params.goal.verification.commands) {
|
|
1194
|
+
const redactedCommand = redactString(command);
|
|
1195
|
+
const result = await runGoalShellCommand({
|
|
1196
|
+
goalId: params.goal.id,
|
|
1197
|
+
iterationId: params.iterationId,
|
|
1198
|
+
command,
|
|
1199
|
+
cwd: params.cwd,
|
|
1200
|
+
workspace: params.workspace,
|
|
1201
|
+
storageRoot: params.storageRoot,
|
|
1202
|
+
localState: params.localState,
|
|
1203
|
+
hostedClient: params.hostedClient
|
|
1204
|
+
});
|
|
1205
|
+
const status = result.code === 0 && !result.timedOut ? "passed" : "failed";
|
|
1206
|
+
const check = {
|
|
1207
|
+
id: `check_${randomUUID5()}`,
|
|
1208
|
+
goalId: params.goal.id,
|
|
1209
|
+
iterationId: params.iterationId,
|
|
1210
|
+
name: redactedCommand,
|
|
1211
|
+
command: redactedCommand,
|
|
1212
|
+
status,
|
|
1213
|
+
code: result.code,
|
|
1214
|
+
timedOut: result.timedOut,
|
|
1215
|
+
durationMs: result.durationMs,
|
|
1216
|
+
summary: status === "passed" ? `Check passed: ${redactedCommand}` : `Check failed: ${redactedCommand}`,
|
|
1217
|
+
artifactRefs: result.artifactRefs,
|
|
1218
|
+
startedAt: result.startedAt,
|
|
1219
|
+
completedAt: result.completedAt
|
|
1220
|
+
};
|
|
1221
|
+
checks.push(check);
|
|
1222
|
+
await recordGoalEvent(
|
|
1223
|
+
createGoalEvent({
|
|
1224
|
+
goalId: params.goal.id,
|
|
1225
|
+
iterationId: params.iterationId,
|
|
1226
|
+
kind: "check.completed",
|
|
1227
|
+
summary: check.summary,
|
|
1228
|
+
payload: check
|
|
1229
|
+
}),
|
|
1230
|
+
{ localState: params.localState, hostedClient: params.hostedClient }
|
|
1231
|
+
);
|
|
1232
|
+
if (status !== "passed") break;
|
|
1233
|
+
}
|
|
1234
|
+
return checks;
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
// src/goal/tools/registry.ts
|
|
1238
|
+
var OPENPOND_AGENT_SDK_COMMANDS = [
|
|
1239
|
+
"inspect",
|
|
1240
|
+
"build",
|
|
1241
|
+
"validate",
|
|
1242
|
+
"eval",
|
|
1243
|
+
"traces"
|
|
1244
|
+
];
|
|
1245
|
+
var GOAL_TOOL_REGISTRY = [
|
|
1246
|
+
{
|
|
1247
|
+
family: "files",
|
|
1248
|
+
canonicalName: "files.read",
|
|
1249
|
+
aliases: ["files_read", "files.read"],
|
|
1250
|
+
schema: tool(
|
|
1251
|
+
"files_read",
|
|
1252
|
+
"Read a UTF-8 file from the goal workspace.",
|
|
1253
|
+
{
|
|
1254
|
+
path: stringSchema("Workspace-relative path to read.")
|
|
1255
|
+
},
|
|
1256
|
+
["path"]
|
|
1257
|
+
)
|
|
1258
|
+
},
|
|
1259
|
+
{
|
|
1260
|
+
family: "files",
|
|
1261
|
+
canonicalName: "files.write",
|
|
1262
|
+
aliases: ["files_write", "files.write"],
|
|
1263
|
+
schema: tool(
|
|
1264
|
+
"files_write",
|
|
1265
|
+
"Write a UTF-8 file inside the goal workspace.",
|
|
1266
|
+
{
|
|
1267
|
+
path: stringSchema("Workspace-relative path to write."),
|
|
1268
|
+
content: stringSchema("Complete file content to write.")
|
|
1269
|
+
},
|
|
1270
|
+
["path", "content"]
|
|
1271
|
+
)
|
|
1272
|
+
},
|
|
1273
|
+
{
|
|
1274
|
+
family: "files",
|
|
1275
|
+
canonicalName: "files.list",
|
|
1276
|
+
aliases: ["files_list", "files.list"],
|
|
1277
|
+
schema: tool(
|
|
1278
|
+
"files_list",
|
|
1279
|
+
"List direct children of a workspace directory.",
|
|
1280
|
+
{
|
|
1281
|
+
path: {
|
|
1282
|
+
...stringSchema("Workspace-relative directory path. Defaults to root."),
|
|
1283
|
+
default: "."
|
|
1284
|
+
}
|
|
1285
|
+
}
|
|
1286
|
+
)
|
|
1287
|
+
},
|
|
1288
|
+
{
|
|
1289
|
+
family: "shell",
|
|
1290
|
+
canonicalName: "shell.run",
|
|
1291
|
+
aliases: ["shell", "shell_run", "shell.run"],
|
|
1292
|
+
schema: tool(
|
|
1293
|
+
"shell_run",
|
|
1294
|
+
"Run a shell command inside the goal workspace.",
|
|
1295
|
+
{
|
|
1296
|
+
command: stringSchema("Command to execute."),
|
|
1297
|
+
cwd: {
|
|
1298
|
+
...stringSchema("Workspace-relative directory. Defaults to root."),
|
|
1299
|
+
default: "."
|
|
1300
|
+
},
|
|
1301
|
+
timeoutSeconds: {
|
|
1302
|
+
type: "number",
|
|
1303
|
+
minimum: 1,
|
|
1304
|
+
maximum: 600,
|
|
1305
|
+
description: "Optional command timeout in seconds."
|
|
1306
|
+
}
|
|
1307
|
+
},
|
|
1308
|
+
["command"]
|
|
1309
|
+
)
|
|
1310
|
+
},
|
|
1311
|
+
{
|
|
1312
|
+
family: "questions",
|
|
1313
|
+
canonicalName: "questions.ask",
|
|
1314
|
+
aliases: ["questions", "questions_ask", "questions.ask"],
|
|
1315
|
+
schema: tool(
|
|
1316
|
+
"questions_ask",
|
|
1317
|
+
"Ask a structured user question and pause if required.",
|
|
1318
|
+
{
|
|
1319
|
+
title: stringSchema("Short question title."),
|
|
1320
|
+
reason: stringSchema("Why the answer is needed."),
|
|
1321
|
+
required: {
|
|
1322
|
+
type: "boolean",
|
|
1323
|
+
description: "Whether work must pause until answered.",
|
|
1324
|
+
default: true
|
|
1325
|
+
},
|
|
1326
|
+
freeformAllowed: {
|
|
1327
|
+
type: "boolean",
|
|
1328
|
+
description: "Whether the user may type a custom answer.",
|
|
1329
|
+
default: true
|
|
1330
|
+
},
|
|
1331
|
+
options: {
|
|
1332
|
+
type: "array",
|
|
1333
|
+
description: "Optional answer choices.",
|
|
1334
|
+
items: {
|
|
1335
|
+
type: "object",
|
|
1336
|
+
properties: {
|
|
1337
|
+
id: stringSchema("Stable option id."),
|
|
1338
|
+
label: stringSchema("User-visible option label."),
|
|
1339
|
+
description: stringSchema("Optional option detail.")
|
|
1340
|
+
},
|
|
1341
|
+
required: ["id", "label"],
|
|
1342
|
+
additionalProperties: false
|
|
1343
|
+
}
|
|
1344
|
+
}
|
|
1345
|
+
},
|
|
1346
|
+
["title", "reason"]
|
|
1347
|
+
)
|
|
1348
|
+
},
|
|
1349
|
+
{
|
|
1350
|
+
family: "approvals",
|
|
1351
|
+
canonicalName: "approvals.request",
|
|
1352
|
+
aliases: [
|
|
1353
|
+
"approvals",
|
|
1354
|
+
"approval_request",
|
|
1355
|
+
"approvals_request",
|
|
1356
|
+
"approvals.request"
|
|
1357
|
+
],
|
|
1358
|
+
schema: tool(
|
|
1359
|
+
"approvals_request",
|
|
1360
|
+
"Request user approval for publish, integration writes, secret/env changes, budget escalation, or other external side effects.",
|
|
1361
|
+
{
|
|
1362
|
+
kind: {
|
|
1363
|
+
type: "string",
|
|
1364
|
+
enum: [
|
|
1365
|
+
"create_plan",
|
|
1366
|
+
"deploy_publish",
|
|
1367
|
+
"integration_write",
|
|
1368
|
+
"secret_or_env_change",
|
|
1369
|
+
"budget_escalation",
|
|
1370
|
+
"external_effect"
|
|
1371
|
+
],
|
|
1372
|
+
description: "The type of approval gate needed."
|
|
1373
|
+
},
|
|
1374
|
+
title: stringSchema("Short approval title shown to the user."),
|
|
1375
|
+
reason: stringSchema("Why approval is required before continuing."),
|
|
1376
|
+
payload: {
|
|
1377
|
+
type: "object",
|
|
1378
|
+
description: "Optional compact approval metadata.",
|
|
1379
|
+
additionalProperties: true
|
|
1380
|
+
}
|
|
1381
|
+
},
|
|
1382
|
+
["kind", "title", "reason"]
|
|
1383
|
+
)
|
|
1384
|
+
},
|
|
1385
|
+
{
|
|
1386
|
+
family: "checks",
|
|
1387
|
+
canonicalName: "checks.run",
|
|
1388
|
+
aliases: ["checks", "checks_run", "checks.run"],
|
|
1389
|
+
schema: tool(
|
|
1390
|
+
"checks_run",
|
|
1391
|
+
"Run the goal verification commands.",
|
|
1392
|
+
{
|
|
1393
|
+
stopOnFailure: {
|
|
1394
|
+
type: "boolean",
|
|
1395
|
+
description: "Reserved; checks currently stop on first failure.",
|
|
1396
|
+
default: true
|
|
1397
|
+
}
|
|
1398
|
+
}
|
|
1399
|
+
)
|
|
1400
|
+
},
|
|
1401
|
+
{
|
|
1402
|
+
family: "artifacts",
|
|
1403
|
+
canonicalName: "artifacts.create",
|
|
1404
|
+
aliases: ["artifacts", "artifacts_create", "artifacts.create"],
|
|
1405
|
+
schema: tool(
|
|
1406
|
+
"artifacts_create",
|
|
1407
|
+
"Create a goal artifact from text content.",
|
|
1408
|
+
{
|
|
1409
|
+
kind: {
|
|
1410
|
+
type: "string",
|
|
1411
|
+
enum: ["command_log", "check_log", "patch", "trace", "manifest", "result"]
|
|
1412
|
+
},
|
|
1413
|
+
name: stringSchema("Artifact file name."),
|
|
1414
|
+
content: stringSchema("Artifact text content."),
|
|
1415
|
+
mimeType: {
|
|
1416
|
+
...stringSchema("Artifact MIME type."),
|
|
1417
|
+
default: "text/plain"
|
|
1418
|
+
}
|
|
1419
|
+
},
|
|
1420
|
+
["kind", "name", "content"]
|
|
1421
|
+
)
|
|
1422
|
+
},
|
|
1423
|
+
{
|
|
1424
|
+
family: "source",
|
|
1425
|
+
canonicalName: "source.finalize",
|
|
1426
|
+
aliases: ["source", "source_finalize", "source.finalize"],
|
|
1427
|
+
schema: tool(
|
|
1428
|
+
"source_finalize",
|
|
1429
|
+
"Commit and push checked source changes when policy allows.",
|
|
1430
|
+
{
|
|
1431
|
+
checksPassed: {
|
|
1432
|
+
type: "boolean",
|
|
1433
|
+
description: "Whether required checks passed."
|
|
1434
|
+
},
|
|
1435
|
+
defaultBranch: {
|
|
1436
|
+
...stringSchema("Default branch to push to, when known."),
|
|
1437
|
+
default: ""
|
|
1438
|
+
}
|
|
1439
|
+
},
|
|
1440
|
+
["checksPassed"]
|
|
1441
|
+
)
|
|
1442
|
+
},
|
|
1443
|
+
...OPENPOND_AGENT_SDK_COMMANDS.map((sdkCommand) => ({
|
|
1444
|
+
family: "openpond_agent_sdk",
|
|
1445
|
+
canonicalName: `openpond_agent.${sdkCommand}`,
|
|
1446
|
+
aliases: [`openpond_agent_${sdkCommand}`],
|
|
1447
|
+
schema: tool(
|
|
1448
|
+
`openpond_agent_${sdkCommand}`,
|
|
1449
|
+
`Run project-local openpond-agent ${sdkCommand}.`,
|
|
1450
|
+
{
|
|
1451
|
+
args: stringArraySchema("Additional CLI args."),
|
|
1452
|
+
json: {
|
|
1453
|
+
type: "boolean",
|
|
1454
|
+
description: "Request JSON output when supported.",
|
|
1455
|
+
default: sdkCommand === "inspect" || sdkCommand === "eval" || sdkCommand === "traces"
|
|
1456
|
+
}
|
|
1457
|
+
},
|
|
1458
|
+
[]
|
|
1459
|
+
)
|
|
1460
|
+
})),
|
|
1461
|
+
{
|
|
1462
|
+
family: "openpond_agent_sdk",
|
|
1463
|
+
canonicalName: "openpond_agent.run",
|
|
1464
|
+
aliases: ["openpond_agent_run"],
|
|
1465
|
+
schema: tool(
|
|
1466
|
+
"openpond_agent_run",
|
|
1467
|
+
"Run a project-local openpond-agent action.",
|
|
1468
|
+
{
|
|
1469
|
+
args: stringArraySchema("CLI args, such as --action and --input."),
|
|
1470
|
+
json: {
|
|
1471
|
+
type: "boolean",
|
|
1472
|
+
description: "Request JSON output when supported.",
|
|
1473
|
+
default: true
|
|
1474
|
+
}
|
|
1475
|
+
},
|
|
1476
|
+
[]
|
|
1477
|
+
)
|
|
1478
|
+
},
|
|
1479
|
+
{
|
|
1480
|
+
family: "openpond_agent_sdk",
|
|
1481
|
+
canonicalName: "openpond_agent.default_checks",
|
|
1482
|
+
aliases: [
|
|
1483
|
+
"openpond_agent_default_checks",
|
|
1484
|
+
"openpond_agent.default_checks"
|
|
1485
|
+
],
|
|
1486
|
+
schema: tool(
|
|
1487
|
+
"openpond_agent_default_checks",
|
|
1488
|
+
"Run inspect, build, validate, and eval through the project-local SDK.",
|
|
1489
|
+
{}
|
|
1490
|
+
)
|
|
1491
|
+
}
|
|
1492
|
+
];
|
|
1493
|
+
var ALIAS_TO_CANONICAL = new Map(
|
|
1494
|
+
GOAL_TOOL_REGISTRY.flatMap((registration) => [
|
|
1495
|
+
[registration.canonicalName, registration.canonicalName],
|
|
1496
|
+
...registration.aliases.map(
|
|
1497
|
+
(alias) => [alias, registration.canonicalName]
|
|
1498
|
+
)
|
|
1499
|
+
])
|
|
1500
|
+
);
|
|
1501
|
+
function buildGoalLlmToolSchemas(toolNames) {
|
|
1502
|
+
const enabled = new Set(toolNames);
|
|
1503
|
+
return GOAL_TOOL_REGISTRY.flatMap(
|
|
1504
|
+
(registration) => enabled.has(registration.family) && registration.schema ? [registration.schema] : []
|
|
1505
|
+
);
|
|
1506
|
+
}
|
|
1507
|
+
function normalizeGoalToolName(name, args) {
|
|
1508
|
+
const raw = name.trim();
|
|
1509
|
+
const canonical = ALIAS_TO_CANONICAL.get(raw);
|
|
1510
|
+
if (canonical) return canonical;
|
|
1511
|
+
const sdkPrefix = "openpond_agent_";
|
|
1512
|
+
if (raw.startsWith(sdkPrefix)) {
|
|
1513
|
+
return `openpond_agent.${raw.slice(sdkPrefix.length)}`;
|
|
1514
|
+
}
|
|
1515
|
+
if (raw === "files") {
|
|
1516
|
+
const action = optionalString(args, "action");
|
|
1517
|
+
return action ? `files.${action}` : raw;
|
|
1518
|
+
}
|
|
1519
|
+
if (raw === "openpond_agent_sdk") {
|
|
1520
|
+
const command = optionalString(args, "command");
|
|
1521
|
+
return command ? `openpond_agent.${command}` : raw;
|
|
1522
|
+
}
|
|
1523
|
+
return raw;
|
|
1524
|
+
}
|
|
1525
|
+
function tool(name, description, properties, required = []) {
|
|
1526
|
+
return {
|
|
1527
|
+
type: "function",
|
|
1528
|
+
function: {
|
|
1529
|
+
name,
|
|
1530
|
+
description,
|
|
1531
|
+
parameters: {
|
|
1532
|
+
type: "object",
|
|
1533
|
+
properties,
|
|
1534
|
+
required,
|
|
1535
|
+
additionalProperties: false
|
|
1536
|
+
}
|
|
1537
|
+
}
|
|
1538
|
+
};
|
|
1539
|
+
}
|
|
1540
|
+
function stringSchema(description) {
|
|
1541
|
+
return { type: "string", description };
|
|
1542
|
+
}
|
|
1543
|
+
function stringArraySchema(description) {
|
|
1544
|
+
return {
|
|
1545
|
+
type: "array",
|
|
1546
|
+
description,
|
|
1547
|
+
items: { type: "string" },
|
|
1548
|
+
default: []
|
|
1549
|
+
};
|
|
1550
|
+
}
|
|
1551
|
+
function optionalString(args, key) {
|
|
1552
|
+
const value = args[key];
|
|
1553
|
+
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
1554
|
+
}
|
|
1555
|
+
|
|
1556
|
+
// src/goal/tools/dispatch.ts
|
|
1557
|
+
import { Buffer as Buffer3 } from "node:buffer";
|
|
1558
|
+
import { randomUUID as randomUUID7 } from "node:crypto";
|
|
1559
|
+
import { isAbsolute as isAbsolute2, relative as relative2, resolve as resolve3 } from "node:path";
|
|
1560
|
+
|
|
1561
|
+
// src/goal/approvals.ts
|
|
1562
|
+
function createGoalApprovalRequest(input) {
|
|
1563
|
+
return {
|
|
1564
|
+
goalId: input.goal.id,
|
|
1565
|
+
kind: input.kind,
|
|
1566
|
+
title: input.title,
|
|
1567
|
+
reason: input.reason,
|
|
1568
|
+
payload: input.payload ?? {},
|
|
1569
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1570
|
+
};
|
|
1571
|
+
}
|
|
1572
|
+
|
|
1573
|
+
// src/goal/tools/files.ts
|
|
1574
|
+
import { constants as fsConstants } from "node:fs";
|
|
1575
|
+
import { access, mkdir as mkdir2, readFile, readdir, stat, writeFile as writeFile2 } from "node:fs/promises";
|
|
1576
|
+
import { dirname, isAbsolute, relative, resolve as resolve2 } from "node:path";
|
|
1577
|
+
function resolveWorkspacePath(workspace, filePath) {
|
|
1578
|
+
const resolved = resolve2(workspace, filePath);
|
|
1579
|
+
const rel = relative(workspace, resolved);
|
|
1580
|
+
if (rel.startsWith("..") || isAbsolute(rel)) {
|
|
1581
|
+
throw new Error(`path is outside workspace: ${filePath}`);
|
|
1582
|
+
}
|
|
1583
|
+
return resolved;
|
|
1584
|
+
}
|
|
1585
|
+
async function readGoalPath(params) {
|
|
1586
|
+
const absolute = resolveWorkspacePath(params.workspace, params.path);
|
|
1587
|
+
const info = await stat(absolute);
|
|
1588
|
+
if (info.isDirectory()) {
|
|
1589
|
+
const entries = await readdir(absolute, { withFileTypes: true });
|
|
1590
|
+
return {
|
|
1591
|
+
path: params.path,
|
|
1592
|
+
type: "directory",
|
|
1593
|
+
entries: entries.map((entry) => entry.name).sort()
|
|
1594
|
+
};
|
|
1595
|
+
}
|
|
1596
|
+
assertGoalFileReadSize(params.path, info.size);
|
|
1597
|
+
return {
|
|
1598
|
+
path: params.path,
|
|
1599
|
+
type: "file",
|
|
1600
|
+
content: await readFile(absolute, "utf-8")
|
|
1601
|
+
};
|
|
1602
|
+
}
|
|
1603
|
+
async function writeGoalFile(params) {
|
|
1604
|
+
const absolute = resolveWorkspacePath(params.workspace, params.path);
|
|
1605
|
+
const created = !await fileExists(absolute);
|
|
1606
|
+
await mkdir2(dirname(absolute), { recursive: true });
|
|
1607
|
+
await writeFile2(absolute, params.content, "utf-8");
|
|
1608
|
+
return { path: params.path, created };
|
|
1609
|
+
}
|
|
1610
|
+
async function listGoalFiles(params) {
|
|
1611
|
+
const root = resolveWorkspacePath(params.workspace, params.path || ".");
|
|
1612
|
+
const entries = await readdir(root, { withFileTypes: true });
|
|
1613
|
+
return entries.map((entry) => entry.name).sort();
|
|
1614
|
+
}
|
|
1615
|
+
async function fileExists(path) {
|
|
1616
|
+
return access(path, fsConstants.F_OK).then(() => true).catch(() => false);
|
|
1617
|
+
}
|
|
1618
|
+
|
|
1619
|
+
// src/goal/tools/questions.ts
|
|
1620
|
+
import { randomUUID as randomUUID6 } from "node:crypto";
|
|
1621
|
+
async function askGoalQuestion(params) {
|
|
1622
|
+
const question = {
|
|
1623
|
+
id: `question_${randomUUID6()}`,
|
|
1624
|
+
goalId: params.goalId,
|
|
1625
|
+
title: params.title,
|
|
1626
|
+
reason: params.reason,
|
|
1627
|
+
required: params.required ?? true,
|
|
1628
|
+
options: params.options ?? [],
|
|
1629
|
+
freeformAllowed: params.freeformAllowed ?? true,
|
|
1630
|
+
answeredAt: null
|
|
1631
|
+
};
|
|
1632
|
+
await params.localState?.addQuestion(params.goalId, question);
|
|
1633
|
+
await params.hostedClient?.createQuestion(params.goalId, question);
|
|
1634
|
+
await recordGoalEvent(
|
|
1635
|
+
createGoalEvent({
|
|
1636
|
+
goalId: params.goalId,
|
|
1637
|
+
iterationId: params.iterationId,
|
|
1638
|
+
kind: "question.created",
|
|
1639
|
+
summary: `Question created: ${question.title}`,
|
|
1640
|
+
payload: {
|
|
1641
|
+
questionId: question.id,
|
|
1642
|
+
required: question.required,
|
|
1643
|
+
options: question.options,
|
|
1644
|
+
freeformAllowed: question.freeformAllowed
|
|
1645
|
+
}
|
|
1646
|
+
}),
|
|
1647
|
+
{ localState: params.localState, hostedClient: params.hostedClient }
|
|
1648
|
+
);
|
|
1649
|
+
return question;
|
|
1650
|
+
}
|
|
1651
|
+
|
|
1652
|
+
// src/goal/tools/sdk-agent.ts
|
|
1653
|
+
import { existsSync } from "node:fs";
|
|
1654
|
+
import { createRequire } from "node:module";
|
|
1655
|
+
import { join as join3 } from "node:path";
|
|
1656
|
+
function resolveProjectLocalAgentCommand(cwd) {
|
|
1657
|
+
const packageDistCli = join3(
|
|
1658
|
+
cwd,
|
|
1659
|
+
"node_modules",
|
|
1660
|
+
"openpond-agent-sdk",
|
|
1661
|
+
"dist",
|
|
1662
|
+
"cli.js"
|
|
1663
|
+
);
|
|
1664
|
+
if (existsSync(packageDistCli)) return { command: process.execPath, args: [packageDistCli] };
|
|
1665
|
+
const localBin = join3(cwd, "node_modules", ".bin", "openpond-agent");
|
|
1666
|
+
if (existsSync(localBin)) return { command: localBin, args: [] };
|
|
1667
|
+
const packageCli = join3(
|
|
1668
|
+
cwd,
|
|
1669
|
+
"node_modules",
|
|
1670
|
+
"openpond-agent-sdk",
|
|
1671
|
+
"src",
|
|
1672
|
+
"cli.ts"
|
|
1673
|
+
);
|
|
1674
|
+
if (existsSync(packageCli)) {
|
|
1675
|
+
const packageJson = join3(cwd, "node_modules", "openpond-agent-sdk", "package.json");
|
|
1676
|
+
const tsxCli = createRequire(packageJson).resolve("tsx/cli");
|
|
1677
|
+
return { command: process.execPath, args: [tsxCli, packageCli] };
|
|
1678
|
+
}
|
|
1679
|
+
return null;
|
|
1680
|
+
}
|
|
1681
|
+
async function runAgentSdkCommand(params) {
|
|
1682
|
+
if (params.sdkCommand === "inspect" && !resolveProjectLocalAgentCommand(params.cwd) && !existsSync(join3(params.cwd, "package.json"))) {
|
|
1683
|
+
return uninitializedAgentInspectResult(params.cwd);
|
|
1684
|
+
}
|
|
1685
|
+
const command = await ensureProjectLocalAgentCommand(params);
|
|
1686
|
+
const args = [
|
|
1687
|
+
...command.args,
|
|
1688
|
+
params.sdkCommand,
|
|
1689
|
+
...params.args ?? [],
|
|
1690
|
+
"--cwd",
|
|
1691
|
+
params.cwd
|
|
1692
|
+
];
|
|
1693
|
+
if (params.json) args.push("--json");
|
|
1694
|
+
return runGoalShellCommand({
|
|
1695
|
+
goalId: params.goalId,
|
|
1696
|
+
iterationId: params.iterationId,
|
|
1697
|
+
command: [command.command, ...args].map(shellEscape).join(" "),
|
|
1698
|
+
cwd: params.cwd,
|
|
1699
|
+
workspace: params.workspace,
|
|
1700
|
+
storageRoot: params.storageRoot,
|
|
1701
|
+
localState: params.localState,
|
|
1702
|
+
hostedClient: params.hostedClient
|
|
1703
|
+
});
|
|
1704
|
+
}
|
|
1705
|
+
function uninitializedAgentInspectResult(cwd) {
|
|
1706
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1707
|
+
return {
|
|
1708
|
+
command: "openpond-agent inspect --json",
|
|
1709
|
+
cwd,
|
|
1710
|
+
code: 0,
|
|
1711
|
+
timedOut: false,
|
|
1712
|
+
durationMs: 0,
|
|
1713
|
+
stdoutTail: JSON.stringify({
|
|
1714
|
+
status: "not_initialized",
|
|
1715
|
+
summary: "No OpenPond agent project was found yet. Create package.json and source files before running SDK validation.",
|
|
1716
|
+
packageJsonFound: false,
|
|
1717
|
+
projectLocalOpenpondAgentFound: false
|
|
1718
|
+
}),
|
|
1719
|
+
stderrTail: "",
|
|
1720
|
+
artifactRefs: [],
|
|
1721
|
+
startedAt: now,
|
|
1722
|
+
completedAt: now
|
|
1723
|
+
};
|
|
1724
|
+
}
|
|
1725
|
+
async function runDefaultAgentSdkChecks(params) {
|
|
1726
|
+
const commands = [
|
|
1727
|
+
"inspect",
|
|
1728
|
+
"build",
|
|
1729
|
+
"validate",
|
|
1730
|
+
"eval",
|
|
1731
|
+
"traces"
|
|
1732
|
+
];
|
|
1733
|
+
const results = [];
|
|
1734
|
+
for (const sdkCommand of commands) {
|
|
1735
|
+
const result = await runAgentSdkCommand({
|
|
1736
|
+
...params,
|
|
1737
|
+
sdkCommand,
|
|
1738
|
+
json: sdkCommand === "inspect" || sdkCommand === "eval" || sdkCommand === "traces"
|
|
1739
|
+
});
|
|
1740
|
+
results.push(result);
|
|
1741
|
+
if (result.code !== 0 || result.timedOut) break;
|
|
1742
|
+
}
|
|
1743
|
+
return results;
|
|
1744
|
+
}
|
|
1745
|
+
async function ensureProjectLocalAgentCommand(params) {
|
|
1746
|
+
const existing = resolveProjectLocalAgentCommand(params.cwd);
|
|
1747
|
+
if (existing) return existing;
|
|
1748
|
+
const installCommand = dependencyInstallCommand(params.cwd);
|
|
1749
|
+
if (!installCommand) {
|
|
1750
|
+
throw new Error(
|
|
1751
|
+
"project-local openpond-agent is missing and no package.json was found"
|
|
1752
|
+
);
|
|
1753
|
+
}
|
|
1754
|
+
const setup = await runGoalShellCommand({
|
|
1755
|
+
goalId: params.goalId,
|
|
1756
|
+
iterationId: params.iterationId,
|
|
1757
|
+
command: installCommand,
|
|
1758
|
+
cwd: params.cwd,
|
|
1759
|
+
timeoutSeconds: 600,
|
|
1760
|
+
workspace: params.workspace,
|
|
1761
|
+
storageRoot: params.storageRoot,
|
|
1762
|
+
localState: params.localState,
|
|
1763
|
+
hostedClient: params.hostedClient
|
|
1764
|
+
});
|
|
1765
|
+
if (setup.code !== 0 || setup.timedOut) {
|
|
1766
|
+
throw new Error(`dependency setup failed before openpond-agent: ${installCommand}`);
|
|
1767
|
+
}
|
|
1768
|
+
const installed = resolveProjectLocalAgentCommand(params.cwd);
|
|
1769
|
+
if (!installed) {
|
|
1770
|
+
throw new Error("project-local openpond-agent was not created by dependency setup");
|
|
1771
|
+
}
|
|
1772
|
+
return installed;
|
|
1773
|
+
}
|
|
1774
|
+
function dependencyInstallCommand(cwd) {
|
|
1775
|
+
if (!existsSync(join3(cwd, "package.json"))) return null;
|
|
1776
|
+
if (existsSync(join3(cwd, "pnpm-lock.yaml"))) return "pnpm install";
|
|
1777
|
+
if (existsSync(join3(cwd, "yarn.lock"))) return "yarn install";
|
|
1778
|
+
if (existsSync(join3(cwd, "package-lock.json"))) return "npm install";
|
|
1779
|
+
return "npm install";
|
|
1780
|
+
}
|
|
1781
|
+
function shellEscape(value) {
|
|
1782
|
+
if (/^[A-Za-z0-9_./:=@+-]+$/.test(value)) return value;
|
|
1783
|
+
return `'${value.replace(/'/g, "'\\''")}'`;
|
|
1784
|
+
}
|
|
1785
|
+
|
|
1786
|
+
// src/goal/tools/source.ts
|
|
1787
|
+
async function finalizeCheckedSourceUpdate(params) {
|
|
1788
|
+
if (params.goal.executionPolicy.sourceUpdates !== "auto_commit_push_default_branch") {
|
|
1789
|
+
return blocked("source update policy is propose-only");
|
|
1790
|
+
}
|
|
1791
|
+
if (!params.checksPassed) return blocked("required checks have not passed");
|
|
1792
|
+
if (params.hostedClient) {
|
|
1793
|
+
return {
|
|
1794
|
+
status: "skipped",
|
|
1795
|
+
branch: null,
|
|
1796
|
+
commitMessage: null,
|
|
1797
|
+
summary: "Source finalization delegated to OpenPond control plane"
|
|
1798
|
+
};
|
|
1799
|
+
}
|
|
1800
|
+
const status = await runGoalShellCommand({
|
|
1801
|
+
goalId: params.goal.id,
|
|
1802
|
+
iterationId: params.iterationId,
|
|
1803
|
+
command: "git status --porcelain -uall",
|
|
1804
|
+
cwd: params.cwd,
|
|
1805
|
+
workspace: params.workspace,
|
|
1806
|
+
storageRoot: params.storageRoot,
|
|
1807
|
+
localState: params.localState,
|
|
1808
|
+
hostedClient: params.hostedClient
|
|
1809
|
+
});
|
|
1810
|
+
if (!status.stdoutTail.trim()) {
|
|
1811
|
+
return {
|
|
1812
|
+
status: "skipped",
|
|
1813
|
+
branch: null,
|
|
1814
|
+
commitMessage: null,
|
|
1815
|
+
summary: "No source changes to commit"
|
|
1816
|
+
};
|
|
1817
|
+
}
|
|
1818
|
+
const unsafePath = unsafeSourceFinalizationPathFromStatus(status.stdoutTail);
|
|
1819
|
+
if (unsafePath) {
|
|
1820
|
+
return blocked(`source update includes disallowed path: ${unsafePath}`);
|
|
1821
|
+
}
|
|
1822
|
+
const branch = params.defaultBranch?.trim() || await resolveDefaultBranch({
|
|
1823
|
+
...params
|
|
1824
|
+
});
|
|
1825
|
+
if (!branch) return blocked("could not resolve project default branch");
|
|
1826
|
+
const message = [
|
|
1827
|
+
`OpenPond Goal: ${params.goal.objective.slice(0, 72)}`,
|
|
1828
|
+
"",
|
|
1829
|
+
`Goal: ${params.goal.id}`,
|
|
1830
|
+
`Iteration: ${params.iterationId}`
|
|
1831
|
+
].join("\n");
|
|
1832
|
+
const add = await runGoalShellCommand({
|
|
1833
|
+
goalId: params.goal.id,
|
|
1834
|
+
iterationId: params.iterationId,
|
|
1835
|
+
command: "git add -A",
|
|
1836
|
+
cwd: params.cwd,
|
|
1837
|
+
workspace: params.workspace,
|
|
1838
|
+
storageRoot: params.storageRoot,
|
|
1839
|
+
localState: params.localState,
|
|
1840
|
+
hostedClient: params.hostedClient
|
|
1841
|
+
});
|
|
1842
|
+
if (add.code !== 0) return blocked("git add failed");
|
|
1843
|
+
const commit = await runGoalShellCommand({
|
|
1844
|
+
goalId: params.goal.id,
|
|
1845
|
+
iterationId: params.iterationId,
|
|
1846
|
+
command: `git -c user.name='OpenPond Goal' -c user.email=goals@openpond.ai commit -m ${shellEscape2(message)}`,
|
|
1847
|
+
cwd: params.cwd,
|
|
1848
|
+
workspace: params.workspace,
|
|
1849
|
+
storageRoot: params.storageRoot,
|
|
1850
|
+
localState: params.localState,
|
|
1851
|
+
hostedClient: params.hostedClient
|
|
1852
|
+
});
|
|
1853
|
+
if (commit.code !== 0) return blocked("git commit failed");
|
|
1854
|
+
const push = await runGoalShellCommand({
|
|
1855
|
+
goalId: params.goal.id,
|
|
1856
|
+
iterationId: params.iterationId,
|
|
1857
|
+
command: `git push origin HEAD:${shellEscape2(branch)}`,
|
|
1858
|
+
cwd: params.cwd,
|
|
1859
|
+
workspace: params.workspace,
|
|
1860
|
+
storageRoot: params.storageRoot,
|
|
1861
|
+
localState: params.localState,
|
|
1862
|
+
hostedClient: params.hostedClient
|
|
1863
|
+
});
|
|
1864
|
+
if (push.code !== 0) return blocked("git push failed");
|
|
1865
|
+
const result = {
|
|
1866
|
+
status: "committed",
|
|
1867
|
+
branch,
|
|
1868
|
+
commitMessage: message,
|
|
1869
|
+
summary: `Committed and pushed checked source update to ${branch}`
|
|
1870
|
+
};
|
|
1871
|
+
await recordGoalEvent(
|
|
1872
|
+
createGoalEvent({
|
|
1873
|
+
goalId: params.goal.id,
|
|
1874
|
+
iterationId: params.iterationId,
|
|
1875
|
+
kind: "source.updated",
|
|
1876
|
+
summary: result.summary,
|
|
1877
|
+
payload: {
|
|
1878
|
+
branch,
|
|
1879
|
+
policy: params.goal.executionPolicy.sourceUpdates
|
|
1880
|
+
}
|
|
1881
|
+
}),
|
|
1882
|
+
{ localState: params.localState, hostedClient: params.hostedClient }
|
|
1883
|
+
);
|
|
1884
|
+
return result;
|
|
1885
|
+
function blocked(summary) {
|
|
1886
|
+
return {
|
|
1887
|
+
status: "blocked",
|
|
1888
|
+
branch: null,
|
|
1889
|
+
commitMessage: null,
|
|
1890
|
+
summary
|
|
1891
|
+
};
|
|
1892
|
+
}
|
|
1893
|
+
}
|
|
1894
|
+
function unsafeSourceFinalizationPathFromStatus(statusOutput) {
|
|
1895
|
+
if (statusOutput.includes("[truncated ")) return "git status output was truncated";
|
|
1896
|
+
for (const line of statusOutput.split(/\r?\n/)) {
|
|
1897
|
+
const paths = porcelainStatusPaths(line);
|
|
1898
|
+
const unsafe = paths.find(isUnsafeSourceFinalizationPath);
|
|
1899
|
+
if (unsafe) return unsafe;
|
|
1900
|
+
}
|
|
1901
|
+
return null;
|
|
1902
|
+
}
|
|
1903
|
+
function porcelainStatusPaths(line) {
|
|
1904
|
+
if (line.length < 4) return [];
|
|
1905
|
+
const raw = line.slice(3).trim();
|
|
1906
|
+
if (!raw) return [];
|
|
1907
|
+
return raw.split(" -> ").map((path) => path.replace(/^"|"$/g, "").trim()).filter(Boolean);
|
|
1908
|
+
}
|
|
1909
|
+
function isUnsafeSourceFinalizationPath(path) {
|
|
1910
|
+
return path === ".env" || path.startsWith(".env.") || path.includes("/.env") || path === "node_modules" || path.startsWith("node_modules/") || path.includes("/node_modules/") || path === ".git" || path.startsWith(".git/") || path.includes("/.git/");
|
|
1911
|
+
}
|
|
1912
|
+
async function resolveDefaultBranch(params) {
|
|
1913
|
+
const remoteHead = await runGoalShellCommand({
|
|
1914
|
+
goalId: params.goal.id,
|
|
1915
|
+
iterationId: params.iterationId,
|
|
1916
|
+
command: "git symbolic-ref --short refs/remotes/origin/HEAD",
|
|
1917
|
+
cwd: params.cwd,
|
|
1918
|
+
workspace: params.workspace,
|
|
1919
|
+
storageRoot: params.storageRoot,
|
|
1920
|
+
localState: params.localState,
|
|
1921
|
+
hostedClient: params.hostedClient
|
|
1922
|
+
});
|
|
1923
|
+
const branch = remoteHead.stdoutTail.trim().replace(/^origin\//, "");
|
|
1924
|
+
if (remoteHead.code === 0 && branch) return branch;
|
|
1925
|
+
const current = await runGoalShellCommand({
|
|
1926
|
+
goalId: params.goal.id,
|
|
1927
|
+
iterationId: params.iterationId,
|
|
1928
|
+
command: "git branch --show-current",
|
|
1929
|
+
cwd: params.cwd,
|
|
1930
|
+
workspace: params.workspace,
|
|
1931
|
+
storageRoot: params.storageRoot,
|
|
1932
|
+
localState: params.localState,
|
|
1933
|
+
hostedClient: params.hostedClient
|
|
1934
|
+
});
|
|
1935
|
+
const currentBranch = current.stdoutTail.trim();
|
|
1936
|
+
return current.code === 0 && currentBranch ? currentBranch : null;
|
|
1937
|
+
}
|
|
1938
|
+
function shellEscape2(value) {
|
|
1939
|
+
if (/^[A-Za-z0-9_./:=@+-]+$/.test(value)) return value;
|
|
1940
|
+
return `'${value.replace(/'/g, "'\\''")}'`;
|
|
1941
|
+
}
|
|
1942
|
+
|
|
1943
|
+
// src/goal/tools/dispatch.ts
|
|
1944
|
+
async function runGoalLlmToolCall(context, toolCall) {
|
|
1945
|
+
const normalizedName = normalizeGoalToolName(toolCall.name, toolCall.arguments);
|
|
1946
|
+
try {
|
|
1947
|
+
return await runKnownTool(context, toolCall, normalizedName);
|
|
1948
|
+
} catch (error) {
|
|
1949
|
+
const summary = error instanceof Error ? error.message : `Tool failed: ${normalizedName}`;
|
|
1950
|
+
await recordGoalEvent(
|
|
1951
|
+
createGoalEvent({
|
|
1952
|
+
goalId: context.goal.id,
|
|
1953
|
+
iterationId: context.iterationId,
|
|
1954
|
+
kind: "goal.blocked",
|
|
1955
|
+
summary,
|
|
1956
|
+
payload: {
|
|
1957
|
+
toolCallId: toolCall.id,
|
|
1958
|
+
toolName: normalizedName
|
|
1959
|
+
}
|
|
1960
|
+
}),
|
|
1961
|
+
context
|
|
1962
|
+
);
|
|
1963
|
+
return {
|
|
1964
|
+
toolCallId: toolCall.id,
|
|
1965
|
+
name: normalizedName,
|
|
1966
|
+
status: "blocked",
|
|
1967
|
+
summary
|
|
1968
|
+
};
|
|
1969
|
+
}
|
|
1970
|
+
}
|
|
1971
|
+
async function runKnownTool(context, toolCall, normalizedName) {
|
|
1972
|
+
const args = toolCall.arguments;
|
|
1973
|
+
if (normalizedName === "files.read") {
|
|
1974
|
+
const path = requireString(args, "path");
|
|
1975
|
+
const result = await readGoalPath({ workspace: context.workspace, path });
|
|
1976
|
+
if (result.type === "directory") {
|
|
1977
|
+
return ok(toolCall, normalizedName, `Path is a directory; listed entries: ${path}`, {
|
|
1978
|
+
path,
|
|
1979
|
+
type: "directory",
|
|
1980
|
+
entries: result.entries,
|
|
1981
|
+
next: "Use files.read with one of the listed file paths, or files.list for another directory."
|
|
1982
|
+
});
|
|
1983
|
+
}
|
|
1984
|
+
const content = result.content;
|
|
1985
|
+
return ok(toolCall, normalizedName, `Read file: ${path}`, {
|
|
1986
|
+
path,
|
|
1987
|
+
type: "file",
|
|
1988
|
+
contentTail: truncateForEvent(content)
|
|
1989
|
+
});
|
|
1990
|
+
}
|
|
1991
|
+
if (normalizedName === "files.write") {
|
|
1992
|
+
const path = requireString(args, "path");
|
|
1993
|
+
const content = requireRawString(args, "content");
|
|
1994
|
+
const policyBlock = hostedFileWritePolicyBlock(context, path);
|
|
1995
|
+
if (policyBlock) {
|
|
1996
|
+
await recordGoalEvent(
|
|
1997
|
+
createGoalEvent({
|
|
1998
|
+
goalId: context.goal.id,
|
|
1999
|
+
iterationId: context.iterationId,
|
|
2000
|
+
kind: "command.completed",
|
|
2001
|
+
summary: policyBlock.summary,
|
|
2002
|
+
payload: {
|
|
2003
|
+
path: policyBlock.path,
|
|
2004
|
+
reason: policyBlock.reason,
|
|
2005
|
+
skipped: true,
|
|
2006
|
+
next: policyBlock.next
|
|
2007
|
+
}
|
|
2008
|
+
}),
|
|
2009
|
+
context
|
|
2010
|
+
);
|
|
2011
|
+
return ok(toolCall, normalizedName, policyBlock.summary, {
|
|
2012
|
+
path: policyBlock.path,
|
|
2013
|
+
reason: policyBlock.reason,
|
|
2014
|
+
skipped: true,
|
|
2015
|
+
next: policyBlock.next
|
|
2016
|
+
});
|
|
2017
|
+
}
|
|
2018
|
+
const result = await writeGoalFile({ workspace: context.workspace, path, content });
|
|
2019
|
+
await recordGoalEvent(
|
|
2020
|
+
createGoalEvent({
|
|
2021
|
+
goalId: context.goal.id,
|
|
2022
|
+
iterationId: context.iterationId,
|
|
2023
|
+
kind: "source.updated",
|
|
2024
|
+
summary: `${result.created ? "Created" : "Updated"} file: ${path}`,
|
|
2025
|
+
payload: {
|
|
2026
|
+
path,
|
|
2027
|
+
created: result.created,
|
|
2028
|
+
bytes: Buffer3.byteLength(content, "utf-8")
|
|
2029
|
+
}
|
|
2030
|
+
}),
|
|
2031
|
+
context
|
|
2032
|
+
);
|
|
2033
|
+
return ok(toolCall, normalizedName, `${result.created ? "Created" : "Updated"} file: ${path}`, result);
|
|
2034
|
+
}
|
|
2035
|
+
if (normalizedName === "files.list") {
|
|
2036
|
+
const path = optionalString2(args, "path") || ".";
|
|
2037
|
+
const entries = await listGoalFiles({ workspace: context.workspace, path });
|
|
2038
|
+
return ok(toolCall, normalizedName, `Listed files: ${path}`, {
|
|
2039
|
+
path,
|
|
2040
|
+
entries
|
|
2041
|
+
});
|
|
2042
|
+
}
|
|
2043
|
+
if (normalizedName === "shell.run") {
|
|
2044
|
+
const command = requireString(args, "command");
|
|
2045
|
+
const policyBlock = hostedShellCommandPolicyBlock(context, command);
|
|
2046
|
+
if (policyBlock) {
|
|
2047
|
+
await recordGoalEvent(
|
|
2048
|
+
createGoalEvent({
|
|
2049
|
+
goalId: context.goal.id,
|
|
2050
|
+
iterationId: context.iterationId,
|
|
2051
|
+
kind: "command.completed",
|
|
2052
|
+
summary: policyBlock.summary,
|
|
2053
|
+
payload: {
|
|
2054
|
+
command: policyBlock.command,
|
|
2055
|
+
reason: policyBlock.reason,
|
|
2056
|
+
skipped: true,
|
|
2057
|
+
next: policyBlock.next
|
|
2058
|
+
}
|
|
2059
|
+
}),
|
|
2060
|
+
context
|
|
2061
|
+
);
|
|
2062
|
+
return {
|
|
2063
|
+
toolCallId: toolCall.id,
|
|
2064
|
+
name: normalizedName,
|
|
2065
|
+
status: "ok",
|
|
2066
|
+
summary: policyBlock.summary,
|
|
2067
|
+
output: {
|
|
2068
|
+
reason: policyBlock.reason,
|
|
2069
|
+
skipped: true,
|
|
2070
|
+
next: policyBlock.next
|
|
2071
|
+
}
|
|
2072
|
+
};
|
|
2073
|
+
}
|
|
2074
|
+
const cwd = resolveToolCwd(context.workspace, optionalString2(args, "cwd"));
|
|
2075
|
+
const result = await runGoalShellCommand({
|
|
2076
|
+
goalId: context.goal.id,
|
|
2077
|
+
iterationId: context.iterationId,
|
|
2078
|
+
command,
|
|
2079
|
+
cwd,
|
|
2080
|
+
timeoutSeconds: optionalNumber(args, "timeoutSeconds") ?? void 0,
|
|
2081
|
+
workspace: context.workspace,
|
|
2082
|
+
storageRoot: context.storageRoot,
|
|
2083
|
+
localState: context.localState,
|
|
2084
|
+
hostedClient: context.hostedClient
|
|
2085
|
+
});
|
|
2086
|
+
const passed = result.code === 0 && !result.timedOut;
|
|
2087
|
+
return {
|
|
2088
|
+
toolCallId: toolCall.id,
|
|
2089
|
+
name: normalizedName,
|
|
2090
|
+
status: passed ? "ok" : "blocked",
|
|
2091
|
+
summary: passed ? `Command completed: ${command}` : `Command failed: ${command}`,
|
|
2092
|
+
output: {
|
|
2093
|
+
code: result.code,
|
|
2094
|
+
timedOut: result.timedOut,
|
|
2095
|
+
stdoutTail: result.stdoutTail,
|
|
2096
|
+
stderrTail: result.stderrTail
|
|
2097
|
+
}
|
|
2098
|
+
};
|
|
2099
|
+
}
|
|
2100
|
+
if (normalizedName === "questions.ask") {
|
|
2101
|
+
const question = await askGoalQuestion({
|
|
2102
|
+
goalId: context.goal.id,
|
|
2103
|
+
iterationId: context.iterationId,
|
|
2104
|
+
title: requireString(args, "title"),
|
|
2105
|
+
reason: requireString(args, "reason"),
|
|
2106
|
+
required: optionalBoolean(args, "required") ?? true,
|
|
2107
|
+
freeformAllowed: optionalBoolean(args, "freeformAllowed") ?? true,
|
|
2108
|
+
options: normalizeQuestionOptions(args.options),
|
|
2109
|
+
localState: context.localState,
|
|
2110
|
+
hostedClient: context.hostedClient
|
|
2111
|
+
});
|
|
2112
|
+
return {
|
|
2113
|
+
toolCallId: toolCall.id,
|
|
2114
|
+
name: normalizedName,
|
|
2115
|
+
status: question.required ? "needs_user_input" : "ok",
|
|
2116
|
+
summary: `Question created: ${question.title}`,
|
|
2117
|
+
output: { questionId: question.id, required: question.required }
|
|
2118
|
+
};
|
|
2119
|
+
}
|
|
2120
|
+
if (normalizedName === "approvals.request") {
|
|
2121
|
+
const kind = normalizeApprovalKind(requireString(args, "kind"));
|
|
2122
|
+
const request = createGoalApprovalRequest({
|
|
2123
|
+
goal: context.goal,
|
|
2124
|
+
kind,
|
|
2125
|
+
title: requireString(args, "title"),
|
|
2126
|
+
reason: requireString(args, "reason"),
|
|
2127
|
+
payload: optionalRecord(args, "payload")
|
|
2128
|
+
});
|
|
2129
|
+
if (context.hostedClient) {
|
|
2130
|
+
await context.hostedClient.requestApproval(request);
|
|
2131
|
+
} else {
|
|
2132
|
+
await recordGoalEvent(
|
|
2133
|
+
createGoalEvent({
|
|
2134
|
+
goalId: context.goal.id,
|
|
2135
|
+
iterationId: context.iterationId,
|
|
2136
|
+
kind: "approval.requested",
|
|
2137
|
+
summary: request.title,
|
|
2138
|
+
payload: {
|
|
2139
|
+
...request.payload,
|
|
2140
|
+
kind: request.kind,
|
|
2141
|
+
reason: request.reason
|
|
2142
|
+
}
|
|
2143
|
+
}),
|
|
2144
|
+
context
|
|
2145
|
+
);
|
|
2146
|
+
if (context.localState) {
|
|
2147
|
+
const current = await context.localState.get(context.goal.id);
|
|
2148
|
+
await context.localState.update({
|
|
2149
|
+
...current ?? context.goal,
|
|
2150
|
+
status: "awaiting_approval",
|
|
2151
|
+
approvals: [
|
|
2152
|
+
...(current ?? context.goal).approvals ?? [],
|
|
2153
|
+
{
|
|
2154
|
+
...request,
|
|
2155
|
+
id: `approval_${randomUUID7()}`,
|
|
2156
|
+
status: "pending",
|
|
2157
|
+
decidedAt: null,
|
|
2158
|
+
decisionNote: null
|
|
2159
|
+
}
|
|
2160
|
+
],
|
|
2161
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2162
|
+
});
|
|
2163
|
+
}
|
|
2164
|
+
}
|
|
2165
|
+
return {
|
|
2166
|
+
toolCallId: toolCall.id,
|
|
2167
|
+
name: normalizedName,
|
|
2168
|
+
status: "needs_approval",
|
|
2169
|
+
summary: `Approval requested: ${request.title}`,
|
|
2170
|
+
output: {
|
|
2171
|
+
kind: request.kind
|
|
2172
|
+
}
|
|
2173
|
+
};
|
|
2174
|
+
}
|
|
2175
|
+
if (normalizedName === "checks.run") {
|
|
2176
|
+
const checks = await runGoalVerificationChecks({
|
|
2177
|
+
goal: context.goal,
|
|
2178
|
+
iterationId: context.iterationId,
|
|
2179
|
+
cwd: context.workspace,
|
|
2180
|
+
workspace: context.workspace,
|
|
2181
|
+
storageRoot: context.storageRoot,
|
|
2182
|
+
localState: context.localState,
|
|
2183
|
+
hostedClient: context.hostedClient
|
|
2184
|
+
});
|
|
2185
|
+
const failed = checks.find((check) => check.status !== "passed");
|
|
2186
|
+
return {
|
|
2187
|
+
toolCallId: toolCall.id,
|
|
2188
|
+
name: normalizedName,
|
|
2189
|
+
status: failed ? "blocked" : "ok",
|
|
2190
|
+
summary: failed ? "Goal checks failed" : "Goal checks passed",
|
|
2191
|
+
output: { checks: compactChecks(checks) },
|
|
2192
|
+
checksPassed: !failed
|
|
2193
|
+
};
|
|
2194
|
+
}
|
|
2195
|
+
if (normalizedName === "artifacts.create") {
|
|
2196
|
+
const artifact = createGoalArtifact({
|
|
2197
|
+
goalId: context.goal.id,
|
|
2198
|
+
iterationId: context.iterationId,
|
|
2199
|
+
kind: normalizeArtifactKind(optionalString2(args, "kind")),
|
|
2200
|
+
name: requireString(args, "name"),
|
|
2201
|
+
mimeType: optionalString2(args, "mimeType") || "text/plain",
|
|
2202
|
+
content: requireRawString(args, "content")
|
|
2203
|
+
});
|
|
2204
|
+
const ref = await recordGoalArtifact({
|
|
2205
|
+
artifact,
|
|
2206
|
+
workspace: context.workspace,
|
|
2207
|
+
storageRoot: context.storageRoot,
|
|
2208
|
+
localState: context.localState,
|
|
2209
|
+
hostedClient: context.hostedClient
|
|
2210
|
+
});
|
|
2211
|
+
return ok(toolCall, normalizedName, `Artifact created: ${artifact.name}`, {
|
|
2212
|
+
artifactRef: ref.ref,
|
|
2213
|
+
artifactId: ref.id,
|
|
2214
|
+
bytes: ref.bytes
|
|
2215
|
+
});
|
|
2216
|
+
}
|
|
2217
|
+
if (normalizedName === "source.finalize") {
|
|
2218
|
+
const result = await finalizeCheckedSourceUpdate({
|
|
2219
|
+
goal: context.goal,
|
|
2220
|
+
iterationId: context.iterationId,
|
|
2221
|
+
cwd: context.workspace,
|
|
2222
|
+
checksPassed: optionalBoolean(args, "checksPassed") ?? false,
|
|
2223
|
+
defaultBranch: optionalString2(args, "defaultBranch") || null,
|
|
2224
|
+
workspace: context.workspace,
|
|
2225
|
+
storageRoot: context.storageRoot,
|
|
2226
|
+
localState: context.localState,
|
|
2227
|
+
hostedClient: context.hostedClient
|
|
2228
|
+
});
|
|
2229
|
+
return {
|
|
2230
|
+
toolCallId: toolCall.id,
|
|
2231
|
+
name: normalizedName,
|
|
2232
|
+
status: result.status === "blocked" ? "blocked" : "ok",
|
|
2233
|
+
summary: result.summary,
|
|
2234
|
+
output: result
|
|
2235
|
+
};
|
|
2236
|
+
}
|
|
2237
|
+
if (normalizedName.startsWith("openpond_agent.")) {
|
|
2238
|
+
return runAgentTool(context, toolCall, normalizedName);
|
|
2239
|
+
}
|
|
2240
|
+
throw new Error(`Unsupported goal tool: ${toolCall.name}`);
|
|
2241
|
+
}
|
|
2242
|
+
function hostedFileWritePolicyBlock(context, filePath) {
|
|
2243
|
+
if (!context.hostedClient) return null;
|
|
2244
|
+
const path = normalizedWorkspaceRelativePath(context.workspace, filePath);
|
|
2245
|
+
if (!path) return null;
|
|
2246
|
+
if (isHostedProtectedWritePath(path)) {
|
|
2247
|
+
return {
|
|
2248
|
+
path,
|
|
2249
|
+
reason: "hosted_protected_source_path",
|
|
2250
|
+
summary: `Skipped write to protected hosted path: ${path}`,
|
|
2251
|
+
next: "Use normal source files for edits. Do not write generated metadata, dependency directories, git internals, or secret/env files."
|
|
2252
|
+
};
|
|
2253
|
+
}
|
|
2254
|
+
if (context.goal.profile === "openpond_agent" && path === "openpond.yaml") {
|
|
2255
|
+
return {
|
|
2256
|
+
path,
|
|
2257
|
+
reason: "hosted_openpond_agent_manifest_write_disallowed",
|
|
2258
|
+
summary: "Skipped write to generated OpenPond agent manifest: openpond.yaml",
|
|
2259
|
+
next: "For hosted OpenPond-agent goals, edit agent/** source files and run openpond_agent_* tools. Do not change openpond.yaml directly."
|
|
2260
|
+
};
|
|
2261
|
+
}
|
|
2262
|
+
return null;
|
|
2263
|
+
}
|
|
2264
|
+
function normalizedWorkspaceRelativePath(workspace, filePath) {
|
|
2265
|
+
const resolved = resolve3(workspace, filePath);
|
|
2266
|
+
const rel = relative2(workspace, resolved);
|
|
2267
|
+
if (!rel || rel.startsWith("..") || isAbsolute2(rel)) return null;
|
|
2268
|
+
return rel.replace(/\\/g, "/");
|
|
2269
|
+
}
|
|
2270
|
+
function isHostedProtectedWritePath(path) {
|
|
2271
|
+
return path === ".env" || path.startsWith(".env.") || path.includes("/.env") || path === ".git" || path.startsWith(".git/") || path === ".openpond" || path.startsWith(".openpond/") || path === "node_modules" || path.startsWith("node_modules/");
|
|
2272
|
+
}
|
|
2273
|
+
function hostedShellCommandPolicyBlock(context, command) {
|
|
2274
|
+
if (!context.hostedClient || context.goal.profile !== "openpond_agent") {
|
|
2275
|
+
return null;
|
|
2276
|
+
}
|
|
2277
|
+
if (!/\bopenpond-agent\b/.test(command)) return null;
|
|
2278
|
+
return {
|
|
2279
|
+
command,
|
|
2280
|
+
reason: "hosted_openpond_agent_shell_disallowed",
|
|
2281
|
+
summary: "Skipped direct OpenPond agent shell command; use openpond_agent_* tools",
|
|
2282
|
+
next: "Call openpond_agent_inspect, openpond_agent_build, openpond_agent_validate, openpond_agent_eval, openpond_agent_traces, openpond_agent_run, or openpond_agent_default_checks."
|
|
2283
|
+
};
|
|
2284
|
+
}
|
|
2285
|
+
async function runAgentTool(context, toolCall, normalizedName) {
|
|
2286
|
+
if (normalizedName === "openpond_agent.default_checks") {
|
|
2287
|
+
const results = await runDefaultAgentSdkChecks({
|
|
2288
|
+
goalId: context.goal.id,
|
|
2289
|
+
iterationId: context.iterationId,
|
|
2290
|
+
cwd: context.workspace,
|
|
2291
|
+
workspace: context.workspace,
|
|
2292
|
+
storageRoot: context.storageRoot,
|
|
2293
|
+
localState: context.localState,
|
|
2294
|
+
hostedClient: context.hostedClient
|
|
2295
|
+
});
|
|
2296
|
+
const failed = results.find((result2) => result2.code !== 0 || result2.timedOut);
|
|
2297
|
+
return {
|
|
2298
|
+
toolCallId: toolCall.id,
|
|
2299
|
+
name: normalizedName,
|
|
2300
|
+
status: failed ? "blocked" : "ok",
|
|
2301
|
+
summary: failed ? "OpenPond agent SDK checks failed" : "OpenPond agent SDK checks passed",
|
|
2302
|
+
output: {
|
|
2303
|
+
commands: results.map((result2) => ({
|
|
2304
|
+
command: result2.command,
|
|
2305
|
+
code: result2.code,
|
|
2306
|
+
timedOut: result2.timedOut
|
|
2307
|
+
}))
|
|
2308
|
+
},
|
|
2309
|
+
checksPassed: !failed
|
|
2310
|
+
};
|
|
2311
|
+
}
|
|
2312
|
+
const sdkCommand = normalizedName.replace("openpond_agent.", "");
|
|
2313
|
+
if (!isAgentSdkCommand(sdkCommand)) {
|
|
2314
|
+
throw new Error(`Unsupported OpenPond agent SDK command: ${sdkCommand}`);
|
|
2315
|
+
}
|
|
2316
|
+
const result = await runAgentSdkCommand({
|
|
2317
|
+
goalId: context.goal.id,
|
|
2318
|
+
iterationId: context.iterationId,
|
|
2319
|
+
cwd: context.workspace,
|
|
2320
|
+
sdkCommand,
|
|
2321
|
+
args: optionalStringArray(toolCall.arguments, "args"),
|
|
2322
|
+
json: optionalBoolean(toolCall.arguments, "json") ?? shouldDefaultJson(sdkCommand),
|
|
2323
|
+
workspace: context.workspace,
|
|
2324
|
+
storageRoot: context.storageRoot,
|
|
2325
|
+
localState: context.localState,
|
|
2326
|
+
hostedClient: context.hostedClient
|
|
2327
|
+
});
|
|
2328
|
+
const passed = result.code === 0 && !result.timedOut;
|
|
2329
|
+
return {
|
|
2330
|
+
toolCallId: toolCall.id,
|
|
2331
|
+
name: normalizedName,
|
|
2332
|
+
status: passed ? "ok" : "blocked",
|
|
2333
|
+
summary: passed ? `openpond-agent ${sdkCommand} completed` : `openpond-agent ${sdkCommand} failed`,
|
|
2334
|
+
output: {
|
|
2335
|
+
code: result.code,
|
|
2336
|
+
timedOut: result.timedOut,
|
|
2337
|
+
stdoutTail: result.stdoutTail,
|
|
2338
|
+
stderrTail: result.stderrTail
|
|
2339
|
+
}
|
|
2340
|
+
};
|
|
2341
|
+
}
|
|
2342
|
+
function ok(toolCall, name, summary, output) {
|
|
2343
|
+
return {
|
|
2344
|
+
toolCallId: toolCall.id,
|
|
2345
|
+
name,
|
|
2346
|
+
status: "ok",
|
|
2347
|
+
summary,
|
|
2348
|
+
output
|
|
2349
|
+
};
|
|
2350
|
+
}
|
|
2351
|
+
function requireString(args, key) {
|
|
2352
|
+
const value = optionalString2(args, key);
|
|
2353
|
+
if (!value) throw new Error(`Tool argument ${key} must be a non-empty string`);
|
|
2354
|
+
return value;
|
|
2355
|
+
}
|
|
2356
|
+
function requireRawString(args, key) {
|
|
2357
|
+
const value = args[key];
|
|
2358
|
+
if (typeof value !== "string") {
|
|
2359
|
+
throw new Error(`Tool argument ${key} must be a string`);
|
|
2360
|
+
}
|
|
2361
|
+
return value;
|
|
2362
|
+
}
|
|
2363
|
+
function optionalString2(args, key) {
|
|
2364
|
+
const value = args[key];
|
|
2365
|
+
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
2366
|
+
}
|
|
2367
|
+
function optionalNumber(args, key) {
|
|
2368
|
+
const value = args[key];
|
|
2369
|
+
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
2370
|
+
}
|
|
2371
|
+
function optionalBoolean(args, key) {
|
|
2372
|
+
const value = args[key];
|
|
2373
|
+
return typeof value === "boolean" ? value : null;
|
|
2374
|
+
}
|
|
2375
|
+
function optionalStringArray(args, key) {
|
|
2376
|
+
const value = args[key];
|
|
2377
|
+
return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
2378
|
+
}
|
|
2379
|
+
function optionalRecord(args, key) {
|
|
2380
|
+
const value = args[key];
|
|
2381
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return {};
|
|
2382
|
+
return value;
|
|
2383
|
+
}
|
|
2384
|
+
function normalizeQuestionOptions(value) {
|
|
2385
|
+
if (!Array.isArray(value)) return [];
|
|
2386
|
+
return value.flatMap((item) => {
|
|
2387
|
+
if (!item || typeof item !== "object") return [];
|
|
2388
|
+
const record = item;
|
|
2389
|
+
const id = optionalString2(record, "id");
|
|
2390
|
+
const label = optionalString2(record, "label");
|
|
2391
|
+
if (!id || !label) return [];
|
|
2392
|
+
const description = optionalString2(record, "description");
|
|
2393
|
+
return [{ id, label, ...description ? { description } : {} }];
|
|
2394
|
+
});
|
|
2395
|
+
}
|
|
2396
|
+
function normalizeApprovalKind(value) {
|
|
2397
|
+
const allowed = /* @__PURE__ */ new Set([
|
|
2398
|
+
"create_plan",
|
|
2399
|
+
"deploy_publish",
|
|
2400
|
+
"integration_write",
|
|
2401
|
+
"secret_or_env_change",
|
|
2402
|
+
"budget_escalation",
|
|
2403
|
+
"external_effect"
|
|
2404
|
+
]);
|
|
2405
|
+
if (!allowed.has(value)) {
|
|
2406
|
+
throw new Error(`Unsupported approval kind: ${value}`);
|
|
2407
|
+
}
|
|
2408
|
+
return value;
|
|
2409
|
+
}
|
|
2410
|
+
function normalizeArtifactKind(value) {
|
|
2411
|
+
const allowed = /* @__PURE__ */ new Set([
|
|
2412
|
+
"command_log",
|
|
2413
|
+
"check_log",
|
|
2414
|
+
"patch",
|
|
2415
|
+
"trace",
|
|
2416
|
+
"manifest",
|
|
2417
|
+
"result"
|
|
2418
|
+
]);
|
|
2419
|
+
return value && allowed.has(value) ? value : "result";
|
|
2420
|
+
}
|
|
2421
|
+
function compactChecks(checks) {
|
|
2422
|
+
return checks.map((check) => ({
|
|
2423
|
+
id: check.id,
|
|
2424
|
+
name: check.name,
|
|
2425
|
+
status: check.status,
|
|
2426
|
+
code: check.code,
|
|
2427
|
+
timedOut: check.timedOut
|
|
2428
|
+
}));
|
|
2429
|
+
}
|
|
2430
|
+
function resolveToolCwd(workspace, cwd) {
|
|
2431
|
+
if (!cwd || cwd === ".") return workspace;
|
|
2432
|
+
const resolved = resolve3(workspace, cwd);
|
|
2433
|
+
const rel = relative2(workspace, resolved);
|
|
2434
|
+
if (rel.startsWith("..") || isAbsolute2(rel)) {
|
|
2435
|
+
throw new Error(`Tool cwd is outside workspace: ${cwd}`);
|
|
2436
|
+
}
|
|
2437
|
+
return resolved;
|
|
2438
|
+
}
|
|
2439
|
+
function isAgentSdkCommand(value) {
|
|
2440
|
+
return ["inspect", "build", "validate", "eval", "traces", "run"].includes(value);
|
|
2441
|
+
}
|
|
2442
|
+
function shouldDefaultJson(command) {
|
|
2443
|
+
return command === "inspect" || command === "eval" || command === "traces" || command === "run";
|
|
2444
|
+
}
|
|
2445
|
+
|
|
2446
|
+
// src/goal/runner.ts
|
|
2447
|
+
var MAX_GOAL_TOOL_ROUNDS = 24;
|
|
2448
|
+
async function runGoalIteration(params) {
|
|
2449
|
+
assertGoalCanStartIteration(params.config.goal);
|
|
2450
|
+
if (goalIterationBudgetRemaining(params.config.goal) <= 0) {
|
|
2451
|
+
const limited = createGoalEvent({
|
|
2452
|
+
goalId: params.config.goal.id,
|
|
2453
|
+
kind: "goal.blocked",
|
|
2454
|
+
summary: "Goal iteration budget exhausted",
|
|
2455
|
+
payload: {
|
|
2456
|
+
reason: "iteration_budget_exhausted",
|
|
2457
|
+
maxIterations: params.config.goal.budget.maxIterations
|
|
2458
|
+
}
|
|
2459
|
+
});
|
|
2460
|
+
await recordGoalEvent(limited, params);
|
|
2461
|
+
await updateGoalStatus(params, "budget_limited");
|
|
2462
|
+
return finishGoalRunResult(
|
|
2463
|
+
params,
|
|
2464
|
+
createGoalRunResult({
|
|
2465
|
+
goal: params.config.goal,
|
|
2466
|
+
status: "budget_limited",
|
|
2467
|
+
summary: limited.summary,
|
|
2468
|
+
events: [limited]
|
|
2469
|
+
})
|
|
2470
|
+
);
|
|
2471
|
+
}
|
|
2472
|
+
const iterationId = params.config.iterationId || `iteration_${randomUUID8()}`;
|
|
2473
|
+
const started = createGoalEvent({
|
|
2474
|
+
goalId: params.config.goal.id,
|
|
2475
|
+
iterationId,
|
|
2476
|
+
kind: "iteration.started",
|
|
2477
|
+
summary: "Goal iteration started",
|
|
2478
|
+
payload: {
|
|
2479
|
+
mode: params.config.mode,
|
|
2480
|
+
profile: params.config.goal.profile,
|
|
2481
|
+
promptPack: params.config.goal.promptPack
|
|
2482
|
+
}
|
|
2483
|
+
});
|
|
2484
|
+
await updateGoalStatus(params, "running");
|
|
2485
|
+
await recordGoalEvent(started, params);
|
|
2486
|
+
const events = [started];
|
|
2487
|
+
const profile = getGoalProfileDescriptor(params.config.goal);
|
|
2488
|
+
const workspace = params.config.workspace ?? process.cwd();
|
|
2489
|
+
const storageRoot = params.config.storageRoot ?? workspace;
|
|
2490
|
+
if (shouldRunLocalCreatePipeline(params.config.goal, params.config.mode)) {
|
|
2491
|
+
const result = await runLocalCreatePipeline({
|
|
2492
|
+
goal: params.config.goal,
|
|
2493
|
+
iterationId,
|
|
2494
|
+
workspace,
|
|
2495
|
+
localState: params.localState,
|
|
2496
|
+
startedEvents: events
|
|
2497
|
+
});
|
|
2498
|
+
return finishGoalRunResult(params, result);
|
|
2499
|
+
}
|
|
2500
|
+
let llmResponse = null;
|
|
2501
|
+
let toolResults = [];
|
|
2502
|
+
const toolSchemas = buildGoalLlmToolSchemas(profile.toolNames);
|
|
2503
|
+
try {
|
|
2504
|
+
const planner = await runGoalPlannerLoop({
|
|
2505
|
+
...params,
|
|
2506
|
+
iterationId,
|
|
2507
|
+
workspace,
|
|
2508
|
+
storageRoot,
|
|
2509
|
+
toolSchemas,
|
|
2510
|
+
profileMetadata: { profile: profile.id, tools: profile.toolNames }
|
|
2511
|
+
});
|
|
2512
|
+
llmResponse = planner.response;
|
|
2513
|
+
toolResults = planner.toolResults;
|
|
2514
|
+
} catch (error) {
|
|
2515
|
+
const blocked2 = createGoalEvent({
|
|
2516
|
+
goalId: params.config.goal.id,
|
|
2517
|
+
iterationId,
|
|
2518
|
+
kind: "goal.blocked",
|
|
2519
|
+
summary: params.config.mode === "hosted" ? "Hosted Goal LLM route is not available" : "Local Goal LLM route is not available",
|
|
2520
|
+
payload: {
|
|
2521
|
+
error: error instanceof Error ? error.message : "LLM route failed"
|
|
2522
|
+
}
|
|
2523
|
+
});
|
|
2524
|
+
await recordGoalEvent(blocked2, params);
|
|
2525
|
+
await updateGoalStatus(params, "blocked");
|
|
2526
|
+
return finishGoalRunResult(
|
|
2527
|
+
params,
|
|
2528
|
+
createGoalRunResult({
|
|
2529
|
+
goal: params.config.goal,
|
|
2530
|
+
status: "blocked",
|
|
2531
|
+
summary: blocked2.summary,
|
|
2532
|
+
events: [...events, blocked2]
|
|
2533
|
+
})
|
|
2534
|
+
);
|
|
2535
|
+
}
|
|
2536
|
+
if (llmResponse) {
|
|
2537
|
+
const pausedForQuestion = toolResults.find(
|
|
2538
|
+
(result) => result.status === "needs_user_input"
|
|
2539
|
+
);
|
|
2540
|
+
if (pausedForQuestion) {
|
|
2541
|
+
const paused = createGoalEvent({
|
|
2542
|
+
goalId: params.config.goal.id,
|
|
2543
|
+
iterationId,
|
|
2544
|
+
kind: "iteration.completed",
|
|
2545
|
+
summary: pausedForQuestion.summary,
|
|
2546
|
+
payload: {
|
|
2547
|
+
status: "awaiting_user_input",
|
|
2548
|
+
toolResults: compactToolResults(toolResults)
|
|
2549
|
+
}
|
|
2550
|
+
});
|
|
2551
|
+
await recordGoalEvent(paused, params);
|
|
2552
|
+
await updateGoalStatus(params, "awaiting_user_input");
|
|
2553
|
+
return finishGoalRunResult(
|
|
2554
|
+
params,
|
|
2555
|
+
createGoalRunResult({
|
|
2556
|
+
goal: params.config.goal,
|
|
2557
|
+
status: "awaiting_user_input",
|
|
2558
|
+
summary: paused.summary,
|
|
2559
|
+
events: [...events, paused]
|
|
2560
|
+
})
|
|
2561
|
+
);
|
|
2562
|
+
}
|
|
2563
|
+
const pausedForApproval = toolResults.find(
|
|
2564
|
+
(result) => result.status === "needs_approval"
|
|
2565
|
+
);
|
|
2566
|
+
if (pausedForApproval) {
|
|
2567
|
+
const paused = createGoalEvent({
|
|
2568
|
+
goalId: params.config.goal.id,
|
|
2569
|
+
iterationId,
|
|
2570
|
+
kind: "iteration.completed",
|
|
2571
|
+
summary: pausedForApproval.summary,
|
|
2572
|
+
payload: {
|
|
2573
|
+
status: "awaiting_approval",
|
|
2574
|
+
toolResults: compactToolResults(toolResults)
|
|
2575
|
+
}
|
|
2576
|
+
});
|
|
2577
|
+
await recordGoalEvent(paused, params);
|
|
2578
|
+
await updateGoalStatus(params, "awaiting_approval");
|
|
2579
|
+
return finishGoalRunResult(
|
|
2580
|
+
params,
|
|
2581
|
+
createGoalRunResult({
|
|
2582
|
+
goal: params.config.goal,
|
|
2583
|
+
status: "awaiting_approval",
|
|
2584
|
+
summary: paused.summary,
|
|
2585
|
+
events: [...events, paused]
|
|
2586
|
+
})
|
|
2587
|
+
);
|
|
2588
|
+
}
|
|
2589
|
+
const blockedTool = latestUnresolvedBlockedToolResult(toolResults);
|
|
2590
|
+
if (blockedTool) {
|
|
2591
|
+
const blocked2 = createGoalEvent({
|
|
2592
|
+
goalId: params.config.goal.id,
|
|
2593
|
+
iterationId,
|
|
2594
|
+
kind: "goal.blocked",
|
|
2595
|
+
summary: blockedTool.summary,
|
|
2596
|
+
payload: {
|
|
2597
|
+
toolResults: compactToolResults(toolResults)
|
|
2598
|
+
}
|
|
2599
|
+
});
|
|
2600
|
+
await recordGoalEvent(blocked2, params);
|
|
2601
|
+
await updateGoalStatus(params, "blocked");
|
|
2602
|
+
return finishGoalRunResult(
|
|
2603
|
+
params,
|
|
2604
|
+
createGoalRunResult({
|
|
2605
|
+
goal: params.config.goal,
|
|
2606
|
+
status: "blocked",
|
|
2607
|
+
summary: blocked2.summary,
|
|
2608
|
+
events: [...events, blocked2]
|
|
2609
|
+
})
|
|
2610
|
+
);
|
|
2611
|
+
}
|
|
2612
|
+
if (llmResponse.status === "needs_user_input" && (llmResponse.toolCalls?.length ?? 0) === 0) {
|
|
2613
|
+
const blocked2 = createGoalEvent({
|
|
2614
|
+
goalId: params.config.goal.id,
|
|
2615
|
+
iterationId,
|
|
2616
|
+
kind: "goal.blocked",
|
|
2617
|
+
summary: "Goal needs user input but no structured question was provided",
|
|
2618
|
+
payload: {
|
|
2619
|
+
responseSummary: llmResponse.summary ?? null
|
|
2620
|
+
}
|
|
2621
|
+
});
|
|
2622
|
+
await recordGoalEvent(blocked2, params);
|
|
2623
|
+
await updateGoalStatus(params, "blocked");
|
|
2624
|
+
return finishGoalRunResult(
|
|
2625
|
+
params,
|
|
2626
|
+
createGoalRunResult({
|
|
2627
|
+
goal: params.config.goal,
|
|
2628
|
+
status: "blocked",
|
|
2629
|
+
summary: blocked2.summary,
|
|
2630
|
+
events: [...events, blocked2]
|
|
2631
|
+
})
|
|
2632
|
+
);
|
|
2633
|
+
}
|
|
2634
|
+
if (llmResponse.status === "blocked" && (llmResponse.toolCalls?.length ?? 0) === 0) {
|
|
2635
|
+
const blocked2 = createGoalEvent({
|
|
2636
|
+
goalId: params.config.goal.id,
|
|
2637
|
+
iterationId,
|
|
2638
|
+
kind: "goal.blocked",
|
|
2639
|
+
summary: llmResponse.summary || "Goal LLM reported a blocker",
|
|
2640
|
+
payload: {
|
|
2641
|
+
message: llmResponse.message?.content ?? null
|
|
2642
|
+
}
|
|
2643
|
+
});
|
|
2644
|
+
await recordGoalEvent(blocked2, params);
|
|
2645
|
+
await updateGoalStatus(params, "blocked");
|
|
2646
|
+
return finishGoalRunResult(
|
|
2647
|
+
params,
|
|
2648
|
+
createGoalRunResult({
|
|
2649
|
+
goal: params.config.goal,
|
|
2650
|
+
status: "blocked",
|
|
2651
|
+
summary: blocked2.summary,
|
|
2652
|
+
events: [...events, blocked2]
|
|
2653
|
+
})
|
|
2654
|
+
);
|
|
2655
|
+
}
|
|
2656
|
+
}
|
|
2657
|
+
if (params.config.goal.verification.commands.length > 0) {
|
|
2658
|
+
const checks = await runGoalVerificationChecks({
|
|
2659
|
+
goal: params.config.goal,
|
|
2660
|
+
iterationId,
|
|
2661
|
+
cwd: workspace,
|
|
2662
|
+
workspace,
|
|
2663
|
+
storageRoot,
|
|
2664
|
+
localState: params.localState,
|
|
2665
|
+
hostedClient: params.hostedClient
|
|
2666
|
+
});
|
|
2667
|
+
const failed = checks.find((check) => check.status !== "passed");
|
|
2668
|
+
const completed = createGoalEvent({
|
|
2669
|
+
goalId: params.config.goal.id,
|
|
2670
|
+
iterationId,
|
|
2671
|
+
kind: "iteration.completed",
|
|
2672
|
+
summary: failed ? "Goal iteration completed with failed checks" : "Goal verification checks passed",
|
|
2673
|
+
payload: {
|
|
2674
|
+
checks: checks.map((check) => ({
|
|
2675
|
+
id: check.id,
|
|
2676
|
+
name: check.name,
|
|
2677
|
+
status: check.status,
|
|
2678
|
+
code: check.code
|
|
2679
|
+
}))
|
|
2680
|
+
}
|
|
2681
|
+
});
|
|
2682
|
+
await recordGoalEvent(completed, params);
|
|
2683
|
+
await updateGoalStatus(params, failed ? "blocked" : "completed");
|
|
2684
|
+
return finishGoalRunResult(
|
|
2685
|
+
params,
|
|
2686
|
+
createGoalRunResult({
|
|
2687
|
+
goal: params.config.goal,
|
|
2688
|
+
status: failed ? "blocked" : "completed",
|
|
2689
|
+
summary: completed.summary,
|
|
2690
|
+
events: [...events, completed]
|
|
2691
|
+
})
|
|
2692
|
+
);
|
|
2693
|
+
}
|
|
2694
|
+
if (toolResults.length > 0) {
|
|
2695
|
+
const completed = createGoalEvent({
|
|
2696
|
+
goalId: params.config.goal.id,
|
|
2697
|
+
iterationId,
|
|
2698
|
+
kind: "iteration.completed",
|
|
2699
|
+
summary: llmResponse?.summary || "Goal tool calls completed",
|
|
2700
|
+
payload: {
|
|
2701
|
+
toolResults: compactToolResults(toolResults),
|
|
2702
|
+
verificationConfigured: false
|
|
2703
|
+
}
|
|
2704
|
+
});
|
|
2705
|
+
await recordGoalEvent(completed, params);
|
|
2706
|
+
await updateGoalStatus(params, "completed");
|
|
2707
|
+
return finishGoalRunResult(
|
|
2708
|
+
params,
|
|
2709
|
+
createGoalRunResult({
|
|
2710
|
+
goal: params.config.goal,
|
|
2711
|
+
status: "completed",
|
|
2712
|
+
summary: completed.summary,
|
|
2713
|
+
events: [...events, completed]
|
|
2714
|
+
})
|
|
2715
|
+
);
|
|
2716
|
+
}
|
|
2717
|
+
const blocked = createGoalEvent({
|
|
2718
|
+
goalId: params.config.goal.id,
|
|
2719
|
+
iterationId,
|
|
2720
|
+
kind: "goal.blocked",
|
|
2721
|
+
summary: "Goal produced no tool calls or verification commands",
|
|
2722
|
+
payload: {
|
|
2723
|
+
reason: "no_executable_goal_plan",
|
|
2724
|
+
profile: profile.id,
|
|
2725
|
+
next: "configure a hosted Goal LLM route, local provider, tool calls, or verification commands"
|
|
2726
|
+
}
|
|
2727
|
+
});
|
|
2728
|
+
await recordGoalEvent(blocked, params);
|
|
2729
|
+
await updateGoalStatus(params, "blocked");
|
|
2730
|
+
return finishGoalRunResult(
|
|
2731
|
+
params,
|
|
2732
|
+
createGoalRunResult({
|
|
2733
|
+
goal: params.config.goal,
|
|
2734
|
+
status: "blocked",
|
|
2735
|
+
summary: blocked.summary,
|
|
2736
|
+
events: [started, blocked]
|
|
2737
|
+
})
|
|
2738
|
+
);
|
|
2739
|
+
}
|
|
2740
|
+
async function runGoalPlannerLoop(params) {
|
|
2741
|
+
if (params.config.mode === "hosted" && !params.hostedClient) {
|
|
2742
|
+
return { response: null, toolResults: [] };
|
|
2743
|
+
}
|
|
2744
|
+
const messages = buildGoalLlmMessages(params.config.goal);
|
|
2745
|
+
const toolResults = [];
|
|
2746
|
+
let response = null;
|
|
2747
|
+
for (let round = 0; round < MAX_GOAL_TOOL_ROUNDS; round += 1) {
|
|
2748
|
+
response = params.config.mode === "hosted" ? await callHostedGoalLlm({
|
|
2749
|
+
goal: params.config.goal,
|
|
2750
|
+
iterationId: params.iterationId,
|
|
2751
|
+
hostedClient: params.hostedClient,
|
|
2752
|
+
localState: params.localState,
|
|
2753
|
+
messages,
|
|
2754
|
+
tools: params.toolSchemas,
|
|
2755
|
+
metadata: { ...params.profileMetadata, round }
|
|
2756
|
+
}) : await callLocalGoalLlm({
|
|
2757
|
+
goal: params.config.goal,
|
|
2758
|
+
iterationId: params.iterationId,
|
|
2759
|
+
localState: params.localState,
|
|
2760
|
+
messages,
|
|
2761
|
+
tools: params.toolSchemas,
|
|
2762
|
+
metadata: { ...params.profileMetadata, round }
|
|
2763
|
+
});
|
|
2764
|
+
if (!response) return { response: null, toolResults };
|
|
2765
|
+
const toolCalls = response.toolCalls ?? [];
|
|
2766
|
+
if (toolCalls.length === 0) return { response, toolResults };
|
|
2767
|
+
const roundResults = await runLlmToolCalls({
|
|
2768
|
+
config: params.config,
|
|
2769
|
+
localState: params.localState,
|
|
2770
|
+
hostedClient: params.hostedClient,
|
|
2771
|
+
iterationId: params.iterationId,
|
|
2772
|
+
workspace: params.workspace,
|
|
2773
|
+
storageRoot: params.storageRoot,
|
|
2774
|
+
response
|
|
2775
|
+
});
|
|
2776
|
+
toolResults.push(...roundResults);
|
|
2777
|
+
messages.push(assistantToolCallMessage(response));
|
|
2778
|
+
messages.push(...roundResults.map(toolResultMessage));
|
|
2779
|
+
if (roundResults.some(
|
|
2780
|
+
(result) => result.status === "needs_user_input" || result.status === "needs_approval"
|
|
2781
|
+
)) {
|
|
2782
|
+
return { response, toolResults };
|
|
2783
|
+
}
|
|
2784
|
+
}
|
|
2785
|
+
return {
|
|
2786
|
+
response: {
|
|
2787
|
+
status: "blocked",
|
|
2788
|
+
summary: "Goal planner exceeded tool round budget",
|
|
2789
|
+
message: {
|
|
2790
|
+
role: "assistant",
|
|
2791
|
+
content: "Goal planner exceeded tool round budget."
|
|
2792
|
+
}
|
|
2793
|
+
},
|
|
2794
|
+
toolResults
|
|
2795
|
+
};
|
|
2796
|
+
}
|
|
2797
|
+
function latestUnresolvedBlockedToolResult(results) {
|
|
2798
|
+
const latest = results.at(-1);
|
|
2799
|
+
return latest?.status === "blocked" ? latest : null;
|
|
2800
|
+
}
|
|
2801
|
+
async function runLlmToolCalls(params) {
|
|
2802
|
+
const toolCalls = params.response.toolCalls ?? [];
|
|
2803
|
+
const results = [];
|
|
2804
|
+
for (const toolCall of toolCalls) {
|
|
2805
|
+
const result = await runGoalLlmToolCall(
|
|
2806
|
+
{
|
|
2807
|
+
goal: params.config.goal,
|
|
2808
|
+
iterationId: params.iterationId,
|
|
2809
|
+
workspace: params.workspace,
|
|
2810
|
+
storageRoot: params.storageRoot,
|
|
2811
|
+
localState: params.localState,
|
|
2812
|
+
hostedClient: params.hostedClient
|
|
2813
|
+
},
|
|
2814
|
+
toolCall
|
|
2815
|
+
);
|
|
2816
|
+
results.push(result);
|
|
2817
|
+
if (result.status !== "ok") break;
|
|
2818
|
+
}
|
|
2819
|
+
return results;
|
|
2820
|
+
}
|
|
2821
|
+
function assistantToolCallMessage(response) {
|
|
2822
|
+
return {
|
|
2823
|
+
role: "assistant",
|
|
2824
|
+
content: response.message?.content ?? response.summary ?? "",
|
|
2825
|
+
tool_calls: (response.toolCalls ?? []).map((toolCall) => ({
|
|
2826
|
+
id: toolCall.id,
|
|
2827
|
+
type: "function",
|
|
2828
|
+
function: {
|
|
2829
|
+
name: toolCall.name,
|
|
2830
|
+
arguments: JSON.stringify(toolCall.arguments)
|
|
2831
|
+
}
|
|
2832
|
+
}))
|
|
2833
|
+
};
|
|
2834
|
+
}
|
|
2835
|
+
function toolResultMessage(result) {
|
|
2836
|
+
return {
|
|
2837
|
+
role: "tool",
|
|
2838
|
+
tool_call_id: result.toolCallId,
|
|
2839
|
+
name: result.name,
|
|
2840
|
+
content: JSON.stringify({
|
|
2841
|
+
status: result.status,
|
|
2842
|
+
summary: result.summary,
|
|
2843
|
+
output: result.output ?? {},
|
|
2844
|
+
checksPassed: result.checksPassed ?? null
|
|
2845
|
+
})
|
|
2846
|
+
};
|
|
2847
|
+
}
|
|
2848
|
+
function compactToolResults(results) {
|
|
2849
|
+
return results.map((result) => ({
|
|
2850
|
+
toolCallId: result.toolCallId,
|
|
2851
|
+
name: result.name,
|
|
2852
|
+
status: result.status,
|
|
2853
|
+
summary: result.summary,
|
|
2854
|
+
checksPassed: result.checksPassed
|
|
2855
|
+
}));
|
|
2856
|
+
}
|
|
2857
|
+
async function finishGoalRunResult(params, result) {
|
|
2858
|
+
if (!params.localState?.writeResult) return result;
|
|
2859
|
+
const current = await params.localState.get(params.config.goal.id);
|
|
2860
|
+
const persisted = current ? { ...result, events: current.events } : result;
|
|
2861
|
+
await params.localState.writeResult(params.config.goal.id, persisted);
|
|
2862
|
+
return persisted;
|
|
2863
|
+
}
|
|
2864
|
+
async function updateGoalStatus(params, status) {
|
|
2865
|
+
await params.hostedClient?.updateStatus(params.config.goal.id, status);
|
|
2866
|
+
if (params.localState) {
|
|
2867
|
+
const current = await params.localState.get(params.config.goal.id);
|
|
2868
|
+
await params.localState.update({
|
|
2869
|
+
...current ?? params.config.goal,
|
|
2870
|
+
status,
|
|
2871
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2872
|
+
});
|
|
2873
|
+
}
|
|
2874
|
+
}
|
|
2875
|
+
|
|
2876
|
+
// src/goal/state/local.ts
|
|
2877
|
+
import { mkdir as mkdir3, readFile as readFile2, readdir as readdir2, writeFile as writeFile3 } from "node:fs/promises";
|
|
2878
|
+
import { join as join4 } from "node:path";
|
|
2879
|
+
|
|
2880
|
+
// src/goal/schemas/event.ts
|
|
2881
|
+
var goalEventRecordSchema = external_exports.object({
|
|
2882
|
+
id: external_exports.string().min(1),
|
|
2883
|
+
goalId: external_exports.string().min(1),
|
|
2884
|
+
iterationId: external_exports.string().min(1).nullable(),
|
|
2885
|
+
kind: external_exports.enum([
|
|
2886
|
+
"goal.created",
|
|
2887
|
+
"goal.status_changed",
|
|
2888
|
+
"iteration.started",
|
|
2889
|
+
"iteration.completed",
|
|
2890
|
+
"question.created",
|
|
2891
|
+
"question.answered",
|
|
2892
|
+
"approval.requested",
|
|
2893
|
+
"approval.decided",
|
|
2894
|
+
"create_pipeline.created",
|
|
2895
|
+
"create_pipeline.status_changed",
|
|
2896
|
+
"create_plan.created",
|
|
2897
|
+
"workflow_capture.created",
|
|
2898
|
+
"command.started",
|
|
2899
|
+
"command.completed",
|
|
2900
|
+
"check.completed",
|
|
2901
|
+
"source.updated",
|
|
2902
|
+
"artifact.created",
|
|
2903
|
+
"model.usage",
|
|
2904
|
+
"goal.blocked",
|
|
2905
|
+
"goal.failed"
|
|
2906
|
+
]),
|
|
2907
|
+
summary: external_exports.string().min(1),
|
|
2908
|
+
payload: external_exports.record(external_exports.string(), external_exports.unknown()),
|
|
2909
|
+
createdAt: external_exports.string().min(1)
|
|
2910
|
+
});
|
|
2911
|
+
function assertGoalEventRecord(event) {
|
|
2912
|
+
return goalEventRecordSchema.parse(event);
|
|
2913
|
+
}
|
|
2914
|
+
|
|
2915
|
+
// src/goal/schemas/question.ts
|
|
2916
|
+
var goalQuestionOptionRecordSchema = external_exports.object({
|
|
2917
|
+
id: external_exports.string().min(1),
|
|
2918
|
+
label: external_exports.string().min(1),
|
|
2919
|
+
description: external_exports.string().min(1).optional()
|
|
2920
|
+
});
|
|
2921
|
+
var goalQuestionRecordSchema = external_exports.object({
|
|
2922
|
+
id: external_exports.string().min(1),
|
|
2923
|
+
goalId: external_exports.string().min(1),
|
|
2924
|
+
title: external_exports.string().min(1),
|
|
2925
|
+
reason: external_exports.string().min(1),
|
|
2926
|
+
required: external_exports.boolean(),
|
|
2927
|
+
options: external_exports.array(goalQuestionOptionRecordSchema),
|
|
2928
|
+
freeformAllowed: external_exports.boolean(),
|
|
2929
|
+
answeredAt: external_exports.string().min(1).nullable()
|
|
2930
|
+
});
|
|
2931
|
+
var goalAnswerRecordSchema = external_exports.object({
|
|
2932
|
+
id: external_exports.string().min(1),
|
|
2933
|
+
goalId: external_exports.string().min(1),
|
|
2934
|
+
questionId: external_exports.string().min(1),
|
|
2935
|
+
optionId: external_exports.string().min(1).nullable(),
|
|
2936
|
+
freeformText: external_exports.string().min(1).nullable(),
|
|
2937
|
+
value: external_exports.record(external_exports.string(), external_exports.unknown()),
|
|
2938
|
+
createdAt: external_exports.string().min(1)
|
|
2939
|
+
});
|
|
2940
|
+
var goalQuestionSnapshotSchema = external_exports.object({
|
|
2941
|
+
questions: external_exports.array(goalQuestionRecordSchema),
|
|
2942
|
+
answers: external_exports.array(goalAnswerRecordSchema)
|
|
2943
|
+
});
|
|
2944
|
+
function assertGoalQuestionSnapshot(snapshot) {
|
|
2945
|
+
return goalQuestionSnapshotSchema.parse(snapshot);
|
|
2946
|
+
}
|
|
2947
|
+
|
|
2948
|
+
// src/goal/schemas/result.ts
|
|
2949
|
+
var goalRunResultRecordSchema = external_exports.object({
|
|
2950
|
+
goalId: external_exports.string().min(1),
|
|
2951
|
+
status: external_exports.enum([
|
|
2952
|
+
"queued",
|
|
2953
|
+
"running",
|
|
2954
|
+
"awaiting_user_input",
|
|
2955
|
+
"awaiting_approval",
|
|
2956
|
+
"paused",
|
|
2957
|
+
"blocked",
|
|
2958
|
+
"completed",
|
|
2959
|
+
"failed",
|
|
2960
|
+
"cancelled",
|
|
2961
|
+
"budget_limited"
|
|
2962
|
+
]),
|
|
2963
|
+
summary: external_exports.string().min(1),
|
|
2964
|
+
events: external_exports.array(goalEventRecordSchema)
|
|
2965
|
+
});
|
|
2966
|
+
function assertGoalRunResultRecord(result) {
|
|
2967
|
+
return goalRunResultRecordSchema.parse(result);
|
|
2968
|
+
}
|
|
2969
|
+
|
|
2970
|
+
// src/goal/state/local.ts
|
|
2971
|
+
var GOAL_STATE_FILE = "state.json";
|
|
2972
|
+
var GOAL_EVENTS_FILE = "events.jsonl";
|
|
2973
|
+
var GOAL_QUESTIONS_FILE = "questions.json";
|
|
2974
|
+
var GOAL_RESULT_FILE = "result.json";
|
|
2975
|
+
var CREATE_PIPELINE_FILE = "create-pipeline.json";
|
|
2976
|
+
var CREATE_PLAN_FILE = "create-plan.json";
|
|
2977
|
+
var WORKFLOW_CAPTURE_FILE = "workflow-capture.json";
|
|
2978
|
+
var LocalGoalStateAdapter = class {
|
|
2979
|
+
constructor(storageRoot) {
|
|
2980
|
+
this.storageRoot = storageRoot;
|
|
2981
|
+
}
|
|
2982
|
+
storageRoot;
|
|
2983
|
+
async create(goal) {
|
|
2984
|
+
await this.write(goal);
|
|
2985
|
+
return goal;
|
|
2986
|
+
}
|
|
2987
|
+
async get(goalId) {
|
|
2988
|
+
try {
|
|
2989
|
+
const raw = await readFile2(this.goalStatePath(goalId), "utf-8");
|
|
2990
|
+
return JSON.parse(raw);
|
|
2991
|
+
} catch {
|
|
2992
|
+
return null;
|
|
2993
|
+
}
|
|
2994
|
+
}
|
|
2995
|
+
async findGoalByQuestionId(questionId) {
|
|
2996
|
+
const root = this.goalRoot();
|
|
2997
|
+
let entries = [];
|
|
2998
|
+
try {
|
|
2999
|
+
entries = await readdir2(root);
|
|
3000
|
+
} catch {
|
|
3001
|
+
return null;
|
|
3002
|
+
}
|
|
3003
|
+
for (const entry of entries) {
|
|
3004
|
+
const goal = await this.get(entry);
|
|
3005
|
+
if (goal?.questions.some((question) => question.id === questionId)) {
|
|
3006
|
+
return goal;
|
|
3007
|
+
}
|
|
3008
|
+
}
|
|
3009
|
+
return null;
|
|
3010
|
+
}
|
|
3011
|
+
async update(goal) {
|
|
3012
|
+
const next = { ...goal, updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
3013
|
+
await this.write(next);
|
|
3014
|
+
return next;
|
|
3015
|
+
}
|
|
3016
|
+
async appendEvent(goalId, event) {
|
|
3017
|
+
const goal = await this.requireGoal(goalId);
|
|
3018
|
+
const next = {
|
|
3019
|
+
...goal,
|
|
3020
|
+
events: [...goal.events, event],
|
|
3021
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3022
|
+
};
|
|
3023
|
+
await this.write(next);
|
|
3024
|
+
return next;
|
|
3025
|
+
}
|
|
3026
|
+
async addQuestion(goalId, question) {
|
|
3027
|
+
const goal = await this.requireGoal(goalId);
|
|
3028
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3029
|
+
const createPipeline = goal.createPipeline ? CreatePipelineSnapshotSchema.parse({
|
|
3030
|
+
...goal.createPipeline,
|
|
3031
|
+
state: question.required ? "awaiting_questions" : goal.createPipeline.state,
|
|
3032
|
+
questionIds: Array.from(
|
|
3033
|
+
/* @__PURE__ */ new Set([...goal.createPipeline.questionIds ?? [], question.id])
|
|
3034
|
+
),
|
|
3035
|
+
updatedAt: now
|
|
3036
|
+
}) : void 0;
|
|
3037
|
+
const next = {
|
|
3038
|
+
...goal,
|
|
3039
|
+
status: question.required ? "awaiting_user_input" : goal.status,
|
|
3040
|
+
questions: [...goal.questions, question],
|
|
3041
|
+
...createPipeline ? { createPipeline } : {},
|
|
3042
|
+
updatedAt: now
|
|
3043
|
+
};
|
|
3044
|
+
await this.write(next);
|
|
3045
|
+
return next;
|
|
3046
|
+
}
|
|
3047
|
+
async answerQuestion(params) {
|
|
3048
|
+
const goal = await this.requireGoal(params.goalId);
|
|
3049
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3050
|
+
const questions = goal.questions.map(
|
|
3051
|
+
(question) => question.id === params.questionId ? { ...question, answeredAt: now } : question
|
|
3052
|
+
);
|
|
3053
|
+
const hasOpenRequiredQuestion = questions.some(
|
|
3054
|
+
(question) => question.required && !question.answeredAt
|
|
3055
|
+
);
|
|
3056
|
+
const createPipeline = goal.createPipeline && goal.createPipeline.state === "awaiting_questions" && !hasOpenRequiredQuestion ? CreatePipelineSnapshotSchema.parse({
|
|
3057
|
+
...goal.createPipeline,
|
|
3058
|
+
state: goal.createPipeline.plan?.status === "pending_approval" ? "awaiting_plan_approval" : "planning",
|
|
3059
|
+
updatedAt: now
|
|
3060
|
+
}) : goal.createPipeline;
|
|
3061
|
+
const next = {
|
|
3062
|
+
...goal,
|
|
3063
|
+
status: goal.status === "awaiting_user_input" && !hasOpenRequiredQuestion ? "queued" : goal.status,
|
|
3064
|
+
questions,
|
|
3065
|
+
answers: [...goal.answers, params.answer],
|
|
3066
|
+
...createPipeline ? { createPipeline } : {},
|
|
3067
|
+
updatedAt: now
|
|
3068
|
+
};
|
|
3069
|
+
await this.write(next);
|
|
3070
|
+
return next;
|
|
3071
|
+
}
|
|
3072
|
+
async writeResult(goalId, result) {
|
|
3073
|
+
await mkdir3(this.goalDir(goalId), { recursive: true });
|
|
3074
|
+
const validated = assertGoalRunResultRecord(result);
|
|
3075
|
+
await writeFile3(
|
|
3076
|
+
this.goalResultPath(goalId),
|
|
3077
|
+
`${JSON.stringify(validated, null, 2)}
|
|
3078
|
+
`,
|
|
3079
|
+
"utf-8"
|
|
3080
|
+
);
|
|
3081
|
+
}
|
|
3082
|
+
async requireGoal(goalId) {
|
|
3083
|
+
const goal = await this.get(goalId);
|
|
3084
|
+
if (!goal) throw new Error(`goal not found: ${goalId}`);
|
|
3085
|
+
return goal;
|
|
3086
|
+
}
|
|
3087
|
+
goalRoot() {
|
|
3088
|
+
return join4(this.storageRoot, ".openpond", "goals");
|
|
3089
|
+
}
|
|
3090
|
+
goalDir(goalId) {
|
|
3091
|
+
return join4(this.goalRoot(), goalId);
|
|
3092
|
+
}
|
|
3093
|
+
goalStatePath(goalId) {
|
|
3094
|
+
return join4(this.goalDir(goalId), GOAL_STATE_FILE);
|
|
3095
|
+
}
|
|
3096
|
+
goalEventsPath(goalId) {
|
|
3097
|
+
return join4(this.goalDir(goalId), GOAL_EVENTS_FILE);
|
|
3098
|
+
}
|
|
3099
|
+
goalQuestionsPath(goalId) {
|
|
3100
|
+
return join4(this.goalDir(goalId), GOAL_QUESTIONS_FILE);
|
|
3101
|
+
}
|
|
3102
|
+
goalResultPath(goalId) {
|
|
3103
|
+
return join4(this.goalDir(goalId), GOAL_RESULT_FILE);
|
|
3104
|
+
}
|
|
3105
|
+
createPipelinePath(goalId) {
|
|
3106
|
+
return join4(this.goalDir(goalId), CREATE_PIPELINE_FILE);
|
|
3107
|
+
}
|
|
3108
|
+
createPlanPath(goalId) {
|
|
3109
|
+
return join4(this.goalDir(goalId), CREATE_PLAN_FILE);
|
|
3110
|
+
}
|
|
3111
|
+
workflowCapturePath(goalId) {
|
|
3112
|
+
return join4(this.goalDir(goalId), WORKFLOW_CAPTURE_FILE);
|
|
3113
|
+
}
|
|
3114
|
+
async write(goal) {
|
|
3115
|
+
await mkdir3(this.goalDir(goal.id), { recursive: true });
|
|
3116
|
+
const events = goal.events.map(assertGoalEventRecord);
|
|
3117
|
+
const questionSnapshot = assertGoalQuestionSnapshot({
|
|
3118
|
+
questions: goal.questions,
|
|
3119
|
+
answers: goal.answers
|
|
3120
|
+
});
|
|
3121
|
+
await writeFile3(
|
|
3122
|
+
this.goalStatePath(goal.id),
|
|
3123
|
+
`${JSON.stringify(goal, null, 2)}
|
|
3124
|
+
`,
|
|
3125
|
+
"utf-8"
|
|
3126
|
+
);
|
|
3127
|
+
await writeFile3(
|
|
3128
|
+
this.goalEventsPath(goal.id),
|
|
3129
|
+
events.length > 0 ? `${events.map(serializeGoalEventRecord).join("\n")}
|
|
3130
|
+
` : "",
|
|
3131
|
+
"utf-8"
|
|
3132
|
+
);
|
|
3133
|
+
await writeFile3(
|
|
3134
|
+
this.goalQuestionsPath(goal.id),
|
|
3135
|
+
`${JSON.stringify(questionSnapshot, null, 2)}
|
|
3136
|
+
`,
|
|
3137
|
+
"utf-8"
|
|
3138
|
+
);
|
|
3139
|
+
if (goal.createPipeline) {
|
|
3140
|
+
await writeFile3(
|
|
3141
|
+
this.createPipelinePath(goal.id),
|
|
3142
|
+
`${JSON.stringify(goal.createPipeline, null, 2)}
|
|
3143
|
+
`,
|
|
3144
|
+
"utf-8"
|
|
3145
|
+
);
|
|
3146
|
+
if (goal.createPipeline.plan) {
|
|
3147
|
+
await writeFile3(
|
|
3148
|
+
this.createPlanPath(goal.id),
|
|
3149
|
+
`${JSON.stringify(goal.createPipeline.plan, null, 2)}
|
|
3150
|
+
`,
|
|
3151
|
+
"utf-8"
|
|
3152
|
+
);
|
|
3153
|
+
}
|
|
3154
|
+
if (goal.createPipeline.workflowCapture) {
|
|
3155
|
+
await writeFile3(
|
|
3156
|
+
this.workflowCapturePath(goal.id),
|
|
3157
|
+
`${JSON.stringify(goal.createPipeline.workflowCapture, null, 2)}
|
|
3158
|
+
`,
|
|
3159
|
+
"utf-8"
|
|
3160
|
+
);
|
|
3161
|
+
}
|
|
3162
|
+
}
|
|
3163
|
+
}
|
|
3164
|
+
};
|
|
3165
|
+
|
|
3166
|
+
// src/goal/state/hosted.ts
|
|
3167
|
+
function resolveHostedGoalCredential() {
|
|
3168
|
+
return process.env.OPENPOND_GOAL_API_KEY?.trim() || process.env.OPENPOND_API_KEY?.trim() || null;
|
|
3169
|
+
}
|
|
3170
|
+
function resolveHostedGoalApiUrl() {
|
|
3171
|
+
return process.env.OPENPOND_GOAL_API_URL?.trim().replace(/\/$/, "") || process.env.OPENPOND_API_URL?.trim().replace(/\/$/, "") || null;
|
|
3172
|
+
}
|
|
3173
|
+
var HostedGoalClient = class {
|
|
3174
|
+
constructor(apiUrl, apiKey) {
|
|
3175
|
+
this.apiUrl = apiUrl;
|
|
3176
|
+
this.apiKey = apiKey;
|
|
3177
|
+
}
|
|
3178
|
+
apiUrl;
|
|
3179
|
+
apiKey;
|
|
3180
|
+
request(path, init) {
|
|
3181
|
+
const headers = new Headers(init?.headers || {});
|
|
3182
|
+
headers.set("openpond-api-key", this.apiKey);
|
|
3183
|
+
return apiFetch(this.apiUrl, this.apiKey, path, {
|
|
3184
|
+
...init,
|
|
3185
|
+
headers
|
|
3186
|
+
});
|
|
3187
|
+
}
|
|
3188
|
+
async getRunConfig(goalId) {
|
|
3189
|
+
const response = await this.request(
|
|
3190
|
+
`/v1/goals/${encodeURIComponent(goalId)}/run-config`
|
|
3191
|
+
);
|
|
3192
|
+
return readApiJson(response, "goal run-config");
|
|
3193
|
+
}
|
|
3194
|
+
async appendEvent(goalId, event) {
|
|
3195
|
+
const response = await this.request(
|
|
3196
|
+
`/v1/goals/${encodeURIComponent(goalId)}/events`,
|
|
3197
|
+
{
|
|
3198
|
+
method: "POST",
|
|
3199
|
+
body: JSON.stringify({ event })
|
|
3200
|
+
}
|
|
3201
|
+
);
|
|
3202
|
+
await readApiJson(response, "goal event");
|
|
3203
|
+
}
|
|
3204
|
+
async answerQuestion(params) {
|
|
3205
|
+
const response = await this.request(
|
|
3206
|
+
`/v1/goals/${encodeURIComponent(params.goalId)}/questions/${encodeURIComponent(
|
|
3207
|
+
params.questionId
|
|
3208
|
+
)}/answers`,
|
|
3209
|
+
{
|
|
3210
|
+
method: "POST",
|
|
3211
|
+
body: JSON.stringify({ answer: params.answer })
|
|
3212
|
+
}
|
|
3213
|
+
);
|
|
3214
|
+
await readApiJson(response, "goal answer");
|
|
3215
|
+
}
|
|
3216
|
+
async createQuestion(goalId, question) {
|
|
3217
|
+
const response = await this.request(
|
|
3218
|
+
`/v1/goals/${encodeURIComponent(goalId)}/questions`,
|
|
3219
|
+
{
|
|
3220
|
+
method: "POST",
|
|
3221
|
+
body: JSON.stringify({ question })
|
|
3222
|
+
}
|
|
3223
|
+
);
|
|
3224
|
+
await readApiJson(response, "goal question");
|
|
3225
|
+
}
|
|
3226
|
+
async requestApproval(request) {
|
|
3227
|
+
const response = await this.request(
|
|
3228
|
+
`/v1/goals/${encodeURIComponent(request.goalId)}/approvals`,
|
|
3229
|
+
{
|
|
3230
|
+
method: "POST",
|
|
3231
|
+
body: JSON.stringify({ approval: request })
|
|
3232
|
+
}
|
|
3233
|
+
);
|
|
3234
|
+
await readApiJson(response, "goal approval request");
|
|
3235
|
+
}
|
|
3236
|
+
async updateStatus(goalId, status) {
|
|
3237
|
+
const response = await this.request(
|
|
3238
|
+
`/v1/goals/${encodeURIComponent(goalId)}`,
|
|
3239
|
+
{
|
|
3240
|
+
method: "PATCH",
|
|
3241
|
+
body: JSON.stringify({ status })
|
|
3242
|
+
}
|
|
3243
|
+
);
|
|
3244
|
+
await readApiJson(response, "goal status update");
|
|
3245
|
+
}
|
|
3246
|
+
async pause(goalId) {
|
|
3247
|
+
await this.postLifecycle(goalId, "pause");
|
|
3248
|
+
}
|
|
3249
|
+
async resume(goalId) {
|
|
3250
|
+
await this.postLifecycle(goalId, "resume");
|
|
3251
|
+
}
|
|
3252
|
+
async cancel(goalId) {
|
|
3253
|
+
await this.postLifecycle(goalId, "cancel");
|
|
3254
|
+
}
|
|
3255
|
+
async approve(goalId, decisionNote) {
|
|
3256
|
+
await this.postLifecycle(goalId, "approve", { decisionNote });
|
|
3257
|
+
}
|
|
3258
|
+
async reject(goalId, decisionNote) {
|
|
3259
|
+
await this.postLifecycle(goalId, "reject", { decisionNote });
|
|
3260
|
+
}
|
|
3261
|
+
async postLifecycle(goalId, action, body) {
|
|
3262
|
+
const response = await this.request(
|
|
3263
|
+
`/v1/goals/${encodeURIComponent(goalId)}/${action}`,
|
|
3264
|
+
{
|
|
3265
|
+
method: "POST",
|
|
3266
|
+
body: JSON.stringify(body ?? {})
|
|
3267
|
+
}
|
|
3268
|
+
);
|
|
3269
|
+
await readApiJson(response, `goal ${action}`);
|
|
3270
|
+
}
|
|
3271
|
+
async callLlm(request) {
|
|
3272
|
+
const response = await this.request(
|
|
3273
|
+
`/v1/goals/${encodeURIComponent(request.goalId)}/llm`,
|
|
3274
|
+
{
|
|
3275
|
+
method: "POST",
|
|
3276
|
+
body: JSON.stringify(request)
|
|
3277
|
+
}
|
|
3278
|
+
);
|
|
3279
|
+
return readApiJson(response, "goal llm call");
|
|
3280
|
+
}
|
|
3281
|
+
async uploadArtifact(artifact) {
|
|
3282
|
+
const response = await this.request(
|
|
3283
|
+
`/v1/goals/${encodeURIComponent(artifact.goalId)}/artifacts`,
|
|
3284
|
+
{
|
|
3285
|
+
method: "POST",
|
|
3286
|
+
body: JSON.stringify({ artifact })
|
|
3287
|
+
}
|
|
3288
|
+
);
|
|
3289
|
+
return readApiJson(response, "goal artifact upload");
|
|
3290
|
+
}
|
|
3291
|
+
};
|
|
3292
|
+
|
|
3293
|
+
// src/goal/cli.ts
|
|
3294
|
+
var DEFAULT_HOSTED_GOAL_LAUNCH_PATH = "/run/openpond/goal-run.json";
|
|
3295
|
+
function printGoalHelp() {
|
|
3296
|
+
console.log("OpenPond Goal commands");
|
|
3297
|
+
console.log("");
|
|
3298
|
+
console.log("Usage:");
|
|
3299
|
+
console.log(' openpond goal "<objective>" [--cwd <path>] [--goal-storage global|workspace]');
|
|
3300
|
+
console.log(" openpond goal run --goal-id <id> [--cwd <path>] [--goal-storage global|workspace]");
|
|
3301
|
+
console.log(' openpond goal create-agent "<agent idea>" [--cwd <path>] [--goal-storage global|workspace]');
|
|
3302
|
+
console.log(' openpond goal update-agent "<agent change>" [--agent-id <id>] [--cwd <path>] [--goal-storage global|workspace]');
|
|
3303
|
+
console.log(" openpond goal plan <goal-id> [--json|--edit <instructions>] [--cwd <path>] [--goal-storage global|workspace]");
|
|
3304
|
+
console.log(
|
|
3305
|
+
" openpond goal answer <question-id> --choice <choice-id>|--answer <text> [--goal-id <id>] [--cwd <path>] [--goal-storage global|workspace]"
|
|
3306
|
+
);
|
|
3307
|
+
console.log(" openpond goal approve <goal-id> [--note <text>]");
|
|
3308
|
+
console.log(" openpond goal reject <goal-id> [--note <text>]");
|
|
3309
|
+
console.log(" openpond goal pause <goal-id>");
|
|
3310
|
+
console.log(" openpond goal resume <goal-id>");
|
|
3311
|
+
console.log(" openpond goal cancel <goal-id>");
|
|
3312
|
+
console.log("");
|
|
3313
|
+
console.log("Hosted env:");
|
|
3314
|
+
console.log(" OPENPOND_GOAL_API_KEY");
|
|
3315
|
+
console.log(" OPENPOND_GOAL_API_URL");
|
|
3316
|
+
console.log(" OPENPOND_GOAL_ID");
|
|
3317
|
+
console.log(" OPENPOND_GOAL_OUTPUT=jsonl");
|
|
3318
|
+
}
|
|
3319
|
+
function optionString(options, key) {
|
|
3320
|
+
const value = options[key];
|
|
3321
|
+
return typeof value === "string" ? value.trim() : "";
|
|
3322
|
+
}
|
|
3323
|
+
function hasFlag(options, key) {
|
|
3324
|
+
const value = options[key];
|
|
3325
|
+
return value === true || value === "true";
|
|
3326
|
+
}
|
|
3327
|
+
function requireOption(options, key, usage) {
|
|
3328
|
+
const value = optionString(options, key);
|
|
3329
|
+
if (!value) throw new Error(usage);
|
|
3330
|
+
return value;
|
|
3331
|
+
}
|
|
3332
|
+
function objectiveFromRest(rest, usage) {
|
|
3333
|
+
const objective = rest.join(" ").trim();
|
|
3334
|
+
if (!objective) throw new Error(usage);
|
|
3335
|
+
return objective;
|
|
3336
|
+
}
|
|
3337
|
+
function arrayOption(options, key) {
|
|
3338
|
+
const value = optionString(options, key);
|
|
3339
|
+
return value ? [value] : [];
|
|
3340
|
+
}
|
|
3341
|
+
function goalKindFromCommand(command) {
|
|
3342
|
+
if (command === "create-agent") return "create_agent";
|
|
3343
|
+
if (command === "update-agent") return "update_agent";
|
|
3344
|
+
return "general_code_goal";
|
|
3345
|
+
}
|
|
3346
|
+
async function localGoalPaths(options) {
|
|
3347
|
+
const cwd = optionString(options, "cwd");
|
|
3348
|
+
const workspace = resolveGoalWorkspace(cwd);
|
|
3349
|
+
const storageLocation = await resolveGoalStorageLocation(optionString(options, "goalStorage"));
|
|
3350
|
+
const storageRoot = resolveGoalStorageRoot({ cwd, location: storageLocation });
|
|
3351
|
+
return {
|
|
3352
|
+
workspace,
|
|
3353
|
+
storageRoot,
|
|
3354
|
+
storageLocation,
|
|
3355
|
+
adapter: new LocalGoalStateAdapter(storageRoot)
|
|
3356
|
+
};
|
|
3357
|
+
}
|
|
3358
|
+
async function readHostedGoalLaunchConfig() {
|
|
3359
|
+
const launchPath = process.env.OPENPOND_GOAL_RUN_CONFIG_PATH?.trim() || DEFAULT_HOSTED_GOAL_LAUNCH_PATH;
|
|
3360
|
+
try {
|
|
3361
|
+
return JSON.parse(await readFile3(launchPath, "utf8"));
|
|
3362
|
+
} catch {
|
|
3363
|
+
return null;
|
|
3364
|
+
}
|
|
3365
|
+
}
|
|
3366
|
+
async function resolveGoalOutputMode(params) {
|
|
3367
|
+
if (hasFlag(params.options, "jsonl") || optionString(params.options, "output") === "jsonl") {
|
|
3368
|
+
return "jsonl";
|
|
3369
|
+
}
|
|
3370
|
+
const explicit = process.env.OPENPOND_GOAL_OUTPUT?.trim();
|
|
3371
|
+
if (explicit === "jsonl") return "jsonl";
|
|
3372
|
+
return params.launchConfig?.output?.mode === "jsonl" ? "jsonl" : "json";
|
|
3373
|
+
}
|
|
3374
|
+
function printGoalRunOutput(result, mode) {
|
|
3375
|
+
if (mode !== "jsonl") {
|
|
3376
|
+
console.log(JSON.stringify(result, null, 2));
|
|
3377
|
+
return;
|
|
3378
|
+
}
|
|
3379
|
+
for (const event of result.events) {
|
|
3380
|
+
console.log(JSON.stringify({ type: "goal_event", event }));
|
|
3381
|
+
}
|
|
3382
|
+
console.log(JSON.stringify({ type: "goal_result", result }));
|
|
3383
|
+
}
|
|
3384
|
+
async function createLocalGoal(params) {
|
|
3385
|
+
const local = await localGoalPaths(params.options);
|
|
3386
|
+
let state = normalizeGoalState(
|
|
3387
|
+
createGoalState({
|
|
3388
|
+
objective: params.objective,
|
|
3389
|
+
kind: params.kind,
|
|
3390
|
+
teamId: optionString(params.options, "teamId") || null,
|
|
3391
|
+
projectId: optionString(params.options, "projectId") || null,
|
|
3392
|
+
agentId: optionString(params.options, "agentId") || null,
|
|
3393
|
+
workItemId: optionString(params.options, "workItemId") || null,
|
|
3394
|
+
conversationId: optionString(params.options, "conversationId") || null,
|
|
3395
|
+
verification: {
|
|
3396
|
+
commands: arrayOption(params.options, "verify"),
|
|
3397
|
+
successCriteria: arrayOption(params.options, "successCriteria")
|
|
3398
|
+
}
|
|
3399
|
+
})
|
|
3400
|
+
);
|
|
3401
|
+
if (shouldCreatePipelineForGoal(params.kind)) {
|
|
3402
|
+
const command = params.createPipeline?.command ?? (params.kind === "update_agent" ? "/edit" : "/create");
|
|
3403
|
+
const surface = params.createPipeline?.surface ?? (params.kind === "update_agent" ? "direct_prompt_edit" : "direct_prompt_create");
|
|
3404
|
+
const { snapshot, approval } = createInitialCreatePipeline({
|
|
3405
|
+
goal: state,
|
|
3406
|
+
command,
|
|
3407
|
+
surface,
|
|
3408
|
+
profile: params.createPipeline?.profile ?? {
|
|
3409
|
+
activeProfile: optionString(params.options, "profile") || null,
|
|
3410
|
+
repoPath: null,
|
|
3411
|
+
sourcePath: local.workspace,
|
|
3412
|
+
localHead: null
|
|
3413
|
+
}
|
|
3414
|
+
});
|
|
3415
|
+
state = {
|
|
3416
|
+
...state,
|
|
3417
|
+
status: "awaiting_approval",
|
|
3418
|
+
approvals: [approval],
|
|
3419
|
+
createPipeline: snapshot,
|
|
3420
|
+
events: [
|
|
3421
|
+
...state.events,
|
|
3422
|
+
createGoalEvent({
|
|
3423
|
+
goalId: state.id,
|
|
3424
|
+
kind: "create_pipeline.created",
|
|
3425
|
+
summary: "Create pipeline initialized",
|
|
3426
|
+
payload: {
|
|
3427
|
+
pipelineId: snapshot.id,
|
|
3428
|
+
requestId: snapshot.request.id,
|
|
3429
|
+
state: snapshot.state,
|
|
3430
|
+
sourceAuthority: snapshot.request.adapter.sourceAuthority
|
|
3431
|
+
}
|
|
3432
|
+
}),
|
|
3433
|
+
createGoalEvent({
|
|
3434
|
+
goalId: state.id,
|
|
3435
|
+
kind: "create_plan.created",
|
|
3436
|
+
summary: "Create plan artifact created",
|
|
3437
|
+
payload: {
|
|
3438
|
+
planId: snapshot.plan?.id ?? null,
|
|
3439
|
+
approvalId: approval.id,
|
|
3440
|
+
status: snapshot.plan?.status ?? null
|
|
3441
|
+
}
|
|
3442
|
+
}),
|
|
3443
|
+
createGoalEvent({
|
|
3444
|
+
goalId: state.id,
|
|
3445
|
+
kind: "workflow_capture.created",
|
|
3446
|
+
summary: "Workflow capture artifact created",
|
|
3447
|
+
payload: {
|
|
3448
|
+
workflowCaptureId: snapshot.workflowCapture?.id ?? null
|
|
3449
|
+
}
|
|
3450
|
+
}),
|
|
3451
|
+
createGoalEvent({
|
|
3452
|
+
goalId: state.id,
|
|
3453
|
+
kind: "approval.requested",
|
|
3454
|
+
summary: approval.title,
|
|
3455
|
+
payload: {
|
|
3456
|
+
kind: approval.kind,
|
|
3457
|
+
reason: approval.reason,
|
|
3458
|
+
requestId: snapshot.request.id,
|
|
3459
|
+
planId: snapshot.plan?.id ?? null
|
|
3460
|
+
}
|
|
3461
|
+
})
|
|
3462
|
+
]
|
|
3463
|
+
};
|
|
3464
|
+
}
|
|
3465
|
+
await local.adapter.create(state);
|
|
3466
|
+
console.log(`Goal created: ${state.id}`);
|
|
3467
|
+
console.log(`State: ${goalStateDisplayPath({ storageRoot: local.storageRoot, goalId: state.id, fileName: "state.json" })}`);
|
|
3468
|
+
if (state.createPipeline?.plan) {
|
|
3469
|
+
console.log(`Plan: ${goalStateDisplayPath({ storageRoot: local.storageRoot, goalId: state.id, fileName: "create-plan.json" })}`);
|
|
3470
|
+
console.log(`Approval required: openpond goal approve ${state.id}`);
|
|
3471
|
+
}
|
|
3472
|
+
return state;
|
|
3473
|
+
}
|
|
3474
|
+
async function runGoal(params) {
|
|
3475
|
+
const launchConfig = await readHostedGoalLaunchConfig();
|
|
3476
|
+
const outputMode = await resolveGoalOutputMode({
|
|
3477
|
+
options: params.options,
|
|
3478
|
+
launchConfig
|
|
3479
|
+
});
|
|
3480
|
+
const credential = resolveHostedGoalCredential();
|
|
3481
|
+
const apiUrl = resolveHostedGoalApiUrl();
|
|
3482
|
+
if (credential && apiUrl) {
|
|
3483
|
+
const hosted = new HostedGoalClient(apiUrl, credential);
|
|
3484
|
+
const config = await hosted.getRunConfig(params.goalId);
|
|
3485
|
+
const result2 = await runGoalIteration({
|
|
3486
|
+
config: {
|
|
3487
|
+
...config,
|
|
3488
|
+
iterationId: launchConfig?.iterationId ?? config.iterationId ?? null
|
|
3489
|
+
},
|
|
3490
|
+
hostedClient: hosted
|
|
3491
|
+
});
|
|
3492
|
+
printGoalRunOutput(result2, outputMode);
|
|
3493
|
+
return;
|
|
3494
|
+
}
|
|
3495
|
+
const local = await localGoalPaths(params.options);
|
|
3496
|
+
const goal = await local.adapter.get(params.goalId);
|
|
3497
|
+
if (!goal) throw new Error(`goal not found: ${params.goalId}`);
|
|
3498
|
+
const result = await runGoalIteration({
|
|
3499
|
+
config: {
|
|
3500
|
+
goal,
|
|
3501
|
+
mode: "local",
|
|
3502
|
+
workspace: local.workspace,
|
|
3503
|
+
storageRoot: local.storageRoot,
|
|
3504
|
+
iterationId: null
|
|
3505
|
+
},
|
|
3506
|
+
localState: local.adapter
|
|
3507
|
+
});
|
|
3508
|
+
printGoalRunOutput(result, outputMode);
|
|
3509
|
+
}
|
|
3510
|
+
async function answerGoal(params) {
|
|
3511
|
+
const optionId = optionString(params.options, "choice") || null;
|
|
3512
|
+
const freeformText = optionString(params.options, "answer") || null;
|
|
3513
|
+
if (!optionId && !freeformText) {
|
|
3514
|
+
throw new Error("usage: goal answer <question-id> --choice <id>|--answer <text>");
|
|
3515
|
+
}
|
|
3516
|
+
const goalId = optionString(params.options, "goalId");
|
|
3517
|
+
const answer = {
|
|
3518
|
+
id: `answer_${randomUUID9()}`,
|
|
3519
|
+
goalId,
|
|
3520
|
+
questionId: params.questionId,
|
|
3521
|
+
optionId,
|
|
3522
|
+
freeformText,
|
|
3523
|
+
value: {},
|
|
3524
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3525
|
+
};
|
|
3526
|
+
const credential = resolveHostedGoalCredential();
|
|
3527
|
+
const apiUrl = resolveHostedGoalApiUrl();
|
|
3528
|
+
if (credential && apiUrl) {
|
|
3529
|
+
if (!goalId) {
|
|
3530
|
+
throw new Error("hosted goal answer requires --goal-id <goal-id>");
|
|
3531
|
+
}
|
|
3532
|
+
await new HostedGoalClient(apiUrl, credential).answerQuestion({
|
|
3533
|
+
goalId,
|
|
3534
|
+
questionId: params.questionId,
|
|
3535
|
+
answer
|
|
3536
|
+
});
|
|
3537
|
+
console.log(`Answered question: ${params.questionId}`);
|
|
3538
|
+
return;
|
|
3539
|
+
}
|
|
3540
|
+
const local = await localGoalPaths(params.options);
|
|
3541
|
+
const goal = goalId ? await local.adapter.get(goalId) : await local.adapter.findGoalByQuestionId(params.questionId);
|
|
3542
|
+
if (!goal) throw new Error(`goal not found for question: ${params.questionId}`);
|
|
3543
|
+
await local.adapter.answerQuestion({
|
|
3544
|
+
goalId: goal.id,
|
|
3545
|
+
questionId: params.questionId,
|
|
3546
|
+
answer: { ...answer, goalId: goal.id }
|
|
3547
|
+
});
|
|
3548
|
+
console.log(`Answered question: ${params.questionId}`);
|
|
3549
|
+
}
|
|
3550
|
+
async function updateGoalStatus2(params) {
|
|
3551
|
+
const credential = resolveHostedGoalCredential();
|
|
3552
|
+
const apiUrl = resolveHostedGoalApiUrl();
|
|
3553
|
+
if (credential && apiUrl) {
|
|
3554
|
+
await new HostedGoalClient(apiUrl, credential).updateStatus(
|
|
3555
|
+
params.goalId,
|
|
3556
|
+
params.status
|
|
3557
|
+
);
|
|
3558
|
+
console.log(`Goal ${params.goalId} ${params.status}`);
|
|
3559
|
+
return;
|
|
3560
|
+
}
|
|
3561
|
+
const local = await localGoalPaths(params.options);
|
|
3562
|
+
const goal = await local.adapter.get(params.goalId);
|
|
3563
|
+
if (!goal) throw new Error(`goal not found: ${params.goalId}`);
|
|
3564
|
+
await local.adapter.update({ ...goal, status: params.status });
|
|
3565
|
+
await local.adapter.appendEvent(
|
|
3566
|
+
goal.id,
|
|
3567
|
+
createGoalEvent({
|
|
3568
|
+
goalId: goal.id,
|
|
3569
|
+
kind: "goal.status_changed",
|
|
3570
|
+
summary: `Goal ${params.status}`,
|
|
3571
|
+
payload: {
|
|
3572
|
+
fromStatus: goal.status,
|
|
3573
|
+
toStatus: params.status
|
|
3574
|
+
}
|
|
3575
|
+
})
|
|
3576
|
+
);
|
|
3577
|
+
console.log(`Goal ${params.goalId} ${params.status}`);
|
|
3578
|
+
}
|
|
3579
|
+
async function applyGoalLifecycle(params) {
|
|
3580
|
+
const credential = resolveHostedGoalCredential();
|
|
3581
|
+
const apiUrl = resolveHostedGoalApiUrl();
|
|
3582
|
+
if (credential && apiUrl) {
|
|
3583
|
+
const client = new HostedGoalClient(apiUrl, credential);
|
|
3584
|
+
if (params.action === "approve") {
|
|
3585
|
+
await client.approve(params.goalId, optionString(params.options, "note"));
|
|
3586
|
+
} else if (params.action === "reject") {
|
|
3587
|
+
await client.reject(params.goalId, optionString(params.options, "note"));
|
|
3588
|
+
} else if (params.action === "pause") {
|
|
3589
|
+
await client.pause(params.goalId);
|
|
3590
|
+
} else if (params.action === "resume") {
|
|
3591
|
+
await client.resume(params.goalId);
|
|
3592
|
+
} else {
|
|
3593
|
+
await client.cancel(params.goalId);
|
|
3594
|
+
}
|
|
3595
|
+
const completedAction = {
|
|
3596
|
+
approve: "approved",
|
|
3597
|
+
reject: "rejected",
|
|
3598
|
+
pause: "paused",
|
|
3599
|
+
resume: "resumed",
|
|
3600
|
+
cancel: "cancelled"
|
|
3601
|
+
};
|
|
3602
|
+
console.log(`Goal ${params.goalId} ${completedAction[params.action]}`);
|
|
3603
|
+
return;
|
|
3604
|
+
}
|
|
3605
|
+
const statusByCommand = {
|
|
3606
|
+
pause: "paused",
|
|
3607
|
+
resume: "queued",
|
|
3608
|
+
cancel: "cancelled"
|
|
3609
|
+
};
|
|
3610
|
+
if (params.action === "approve" || params.action === "reject" || params.action === "cancel") {
|
|
3611
|
+
const local = await localGoalPaths(params.options);
|
|
3612
|
+
const goal = await local.adapter.get(params.goalId);
|
|
3613
|
+
if (!goal) throw new Error(`goal not found: ${params.goalId}`);
|
|
3614
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3615
|
+
const note = optionString(params.options, "note") || null;
|
|
3616
|
+
const pendingApproval = goal.approvals.find(
|
|
3617
|
+
(approval) => approval.status === "pending"
|
|
3618
|
+
);
|
|
3619
|
+
if (!pendingApproval && params.action === "cancel") {
|
|
3620
|
+
await updateGoalStatus2({
|
|
3621
|
+
options: params.options,
|
|
3622
|
+
goalId: params.goalId,
|
|
3623
|
+
status: "cancelled"
|
|
3624
|
+
});
|
|
3625
|
+
return;
|
|
3626
|
+
}
|
|
3627
|
+
if (!pendingApproval) {
|
|
3628
|
+
throw new Error(`goal has no pending approval: ${params.goalId}`);
|
|
3629
|
+
}
|
|
3630
|
+
const approvalStatus = params.action === "approve" ? "approved" : params.action === "reject" ? "rejected" : "cancelled";
|
|
3631
|
+
const goalStatus = params.action === "approve" ? "queued" : params.action === "reject" ? "blocked" : "cancelled";
|
|
3632
|
+
const approvals = goal.approvals.map(
|
|
3633
|
+
(approval) => approval.id === pendingApproval?.id ? {
|
|
3634
|
+
...approval,
|
|
3635
|
+
status: approvalStatus,
|
|
3636
|
+
decidedAt: now,
|
|
3637
|
+
decisionNote: note
|
|
3638
|
+
} : approval
|
|
3639
|
+
);
|
|
3640
|
+
let decidedGoal = {
|
|
3641
|
+
...goal,
|
|
3642
|
+
status: goalStatus,
|
|
3643
|
+
approvals,
|
|
3644
|
+
updatedAt: now
|
|
3645
|
+
};
|
|
3646
|
+
if (pendingApproval?.kind === "create_plan") {
|
|
3647
|
+
if (params.action === "approve") {
|
|
3648
|
+
decidedGoal = approveCreatePipelinePlan(decidedGoal, pendingApproval.id);
|
|
3649
|
+
} else if (params.action === "reject") {
|
|
3650
|
+
decidedGoal = rejectCreatePipelinePlan(decidedGoal, pendingApproval.id, note);
|
|
3651
|
+
} else {
|
|
3652
|
+
decidedGoal = cancelCreatePipelinePlan(decidedGoal, pendingApproval.id, note);
|
|
3653
|
+
}
|
|
3654
|
+
}
|
|
3655
|
+
await local.adapter.update(decidedGoal);
|
|
3656
|
+
await local.adapter.appendEvent(
|
|
3657
|
+
goal.id,
|
|
3658
|
+
createGoalEvent({
|
|
3659
|
+
goalId: goal.id,
|
|
3660
|
+
kind: "approval.decided",
|
|
3661
|
+
summary: pendingApproval ? `Approval ${approvalStatus}: ${pendingApproval.title}` : `Goal ${approvalStatus}`,
|
|
3662
|
+
payload: {
|
|
3663
|
+
approvalId: pendingApproval?.id ?? null,
|
|
3664
|
+
kind: pendingApproval?.kind ?? null,
|
|
3665
|
+
status: approvalStatus,
|
|
3666
|
+
decisionNote: note
|
|
3667
|
+
}
|
|
3668
|
+
})
|
|
3669
|
+
);
|
|
3670
|
+
if (pendingApproval?.kind === "create_plan") {
|
|
3671
|
+
const toState = params.action === "approve" ? "applying_source" : params.action === "reject" ? "blocked" : "cancelled";
|
|
3672
|
+
await local.adapter.appendEvent(
|
|
3673
|
+
goal.id,
|
|
3674
|
+
createGoalEvent({
|
|
3675
|
+
goalId: goal.id,
|
|
3676
|
+
kind: "create_pipeline.status_changed",
|
|
3677
|
+
summary: params.action === "approve" ? "Create plan approved" : params.action === "reject" ? "Create plan rejected" : "Create plan cancelled",
|
|
3678
|
+
payload: {
|
|
3679
|
+
fromState: goal.createPipeline?.state ?? null,
|
|
3680
|
+
toState,
|
|
3681
|
+
approvalId: pendingApproval.id
|
|
3682
|
+
}
|
|
3683
|
+
})
|
|
3684
|
+
);
|
|
3685
|
+
}
|
|
3686
|
+
console.log(`Goal ${params.goalId} ${approvalStatus}`);
|
|
3687
|
+
return;
|
|
3688
|
+
}
|
|
3689
|
+
await updateGoalStatus2({
|
|
3690
|
+
options: params.options,
|
|
3691
|
+
goalId: params.goalId,
|
|
3692
|
+
status: statusByCommand[params.action]
|
|
3693
|
+
});
|
|
3694
|
+
}
|
|
3695
|
+
async function printGoalPlan(params) {
|
|
3696
|
+
const credential = resolveHostedGoalCredential();
|
|
3697
|
+
const apiUrl = resolveHostedGoalApiUrl();
|
|
3698
|
+
if (credential && apiUrl) {
|
|
3699
|
+
const config = await new HostedGoalClient(apiUrl, credential).getRunConfig(params.goalId);
|
|
3700
|
+
printCreatePipeline(config.goal, hasFlag(params.options, "json"));
|
|
3701
|
+
return;
|
|
3702
|
+
}
|
|
3703
|
+
const local = await localGoalPaths(params.options);
|
|
3704
|
+
const goal = await local.adapter.get(params.goalId);
|
|
3705
|
+
if (!goal) throw new Error(`goal not found: ${params.goalId}`);
|
|
3706
|
+
printCreatePipeline(goal, hasFlag(params.options, "json"));
|
|
3707
|
+
}
|
|
3708
|
+
async function editGoalPlan(params) {
|
|
3709
|
+
const credential = resolveHostedGoalCredential();
|
|
3710
|
+
const apiUrl = resolveHostedGoalApiUrl();
|
|
3711
|
+
if (credential && apiUrl) {
|
|
3712
|
+
throw new Error("hosted create-plan editing is not available from this CLI yet");
|
|
3713
|
+
}
|
|
3714
|
+
const local = await localGoalPaths(params.options);
|
|
3715
|
+
const goal = await local.adapter.get(params.goalId);
|
|
3716
|
+
if (!goal) throw new Error(`goal not found: ${params.goalId}`);
|
|
3717
|
+
const previousPlanId = goal.createPipeline?.plan?.id ?? null;
|
|
3718
|
+
const revised = reviseCreatePipelinePlan(goal, { revision: params.revision });
|
|
3719
|
+
await local.adapter.update(revised);
|
|
3720
|
+
await local.adapter.appendEvent(
|
|
3721
|
+
goal.id,
|
|
3722
|
+
createGoalEvent({
|
|
3723
|
+
goalId: goal.id,
|
|
3724
|
+
kind: "create_plan.created",
|
|
3725
|
+
summary: "Create plan revised before source mutation",
|
|
3726
|
+
payload: {
|
|
3727
|
+
previousPlanId,
|
|
3728
|
+
planId: revised.createPipeline?.plan?.id ?? null,
|
|
3729
|
+
revision: params.revision
|
|
3730
|
+
}
|
|
3731
|
+
})
|
|
3732
|
+
);
|
|
3733
|
+
if (hasFlag(params.options, "json")) {
|
|
3734
|
+
console.log(JSON.stringify(revised.createPipeline, null, 2));
|
|
3735
|
+
return;
|
|
3736
|
+
}
|
|
3737
|
+
console.log(`Goal ${params.goalId} plan revised`);
|
|
3738
|
+
console.log(`Plan: ${revised.createPipeline?.plan?.id ?? "unknown"}`);
|
|
3739
|
+
}
|
|
3740
|
+
function printCreatePipeline(goal, json = false) {
|
|
3741
|
+
if (!goal?.createPipeline) {
|
|
3742
|
+
if (json) {
|
|
3743
|
+
console.log(JSON.stringify(null, null, 2));
|
|
3744
|
+
return;
|
|
3745
|
+
}
|
|
3746
|
+
console.log(`Goal ${goal?.id ?? "unknown"} has no create pipeline.`);
|
|
3747
|
+
return;
|
|
3748
|
+
}
|
|
3749
|
+
const pipeline = goal.createPipeline;
|
|
3750
|
+
const plan = pipeline.plan;
|
|
3751
|
+
if (json) {
|
|
3752
|
+
console.log(JSON.stringify(pipeline, null, 2));
|
|
3753
|
+
return;
|
|
3754
|
+
}
|
|
3755
|
+
console.log(`Goal: ${goal.id}`);
|
|
3756
|
+
console.log(`Pipeline: ${pipeline.state}`);
|
|
3757
|
+
console.log(`Request: ${pipeline.request.command} ${pipeline.request.operation}`);
|
|
3758
|
+
console.log(`Objective: ${pipeline.request.objective}`);
|
|
3759
|
+
if (!plan) return;
|
|
3760
|
+
console.log("");
|
|
3761
|
+
console.log(`Plan: ${plan.status}`);
|
|
3762
|
+
console.log(plan.summary);
|
|
3763
|
+
console.log("");
|
|
3764
|
+
console.log("Captured context:");
|
|
3765
|
+
console.log(` ${plan.capturedContextSummary}`);
|
|
3766
|
+
console.log("");
|
|
3767
|
+
console.log("Default chat action:");
|
|
3768
|
+
console.log(` ${plan.defaultChatAction.key ?? "none"}`);
|
|
3769
|
+
const actionShape = createPipelineActionShapeFromMetadata(plan.metadata);
|
|
3770
|
+
if (actionShape) {
|
|
3771
|
+
console.log("");
|
|
3772
|
+
console.log("Action shape:");
|
|
3773
|
+
console.log(` ${actionShape.label}: ${actionShape.detail}`);
|
|
3774
|
+
if (actionShape.directActionHint) {
|
|
3775
|
+
console.log(` Direct action: ${actionShape.directActionHint}`);
|
|
3776
|
+
}
|
|
3777
|
+
console.log(` Artifacts: ${actionShape.artifactPolicy}`);
|
|
3778
|
+
}
|
|
3779
|
+
console.log("");
|
|
3780
|
+
console.log("Source plan:");
|
|
3781
|
+
for (const item of plan.sourcePlan) {
|
|
3782
|
+
console.log(` - ${item.operation} ${item.path}: ${item.reason}`);
|
|
3783
|
+
}
|
|
3784
|
+
console.log("");
|
|
3785
|
+
console.log("Requirements:");
|
|
3786
|
+
if (plan.requirements.length === 0) {
|
|
3787
|
+
console.log(" - none");
|
|
3788
|
+
} else {
|
|
3789
|
+
for (const requirement of plan.requirements) {
|
|
3790
|
+
console.log(` - ${requirement.kind} ${requirement.name} (${requirement.status})${requirement.detail ? `: ${requirement.detail}` : ""}`);
|
|
3791
|
+
}
|
|
3792
|
+
}
|
|
3793
|
+
console.log("");
|
|
3794
|
+
console.log("Checks:");
|
|
3795
|
+
for (const check of plan.checks) {
|
|
3796
|
+
console.log(` - ${check.name}: ${check.command}`);
|
|
3797
|
+
}
|
|
3798
|
+
const pendingApproval = goal.approvals.find((approval) => approval.id === plan.approvalId);
|
|
3799
|
+
if (pendingApproval?.status === "pending") {
|
|
3800
|
+
console.log("");
|
|
3801
|
+
console.log(`Approve: openpond goal approve ${goal.id}`);
|
|
3802
|
+
console.log(`Reject: openpond goal reject ${goal.id}`);
|
|
3803
|
+
console.log(`Cancel: openpond goal cancel ${goal.id}`);
|
|
3804
|
+
}
|
|
3805
|
+
}
|
|
3806
|
+
async function runGoalCommand(options, rest) {
|
|
3807
|
+
const subcommand = rest[0];
|
|
3808
|
+
if (hasFlag(options, "help") || subcommand === "help") {
|
|
3809
|
+
printGoalHelp();
|
|
3810
|
+
return;
|
|
3811
|
+
}
|
|
3812
|
+
if (!subcommand || !["run", "create-agent", "update-agent", "plan", "answer", "approve", "reject", "pause", "resume", "cancel"].includes(subcommand)) {
|
|
3813
|
+
await createLocalGoal({
|
|
3814
|
+
options,
|
|
3815
|
+
objective: objectiveFromRest(rest, 'usage: goal "<objective>"'),
|
|
3816
|
+
kind: "general_code_goal"
|
|
3817
|
+
});
|
|
3818
|
+
return;
|
|
3819
|
+
}
|
|
3820
|
+
if (subcommand === "run") {
|
|
3821
|
+
await runGoal({
|
|
3822
|
+
options,
|
|
3823
|
+
goalId: requireOption(options, "goalId", "usage: goal run --goal-id <id>")
|
|
3824
|
+
});
|
|
3825
|
+
return;
|
|
3826
|
+
}
|
|
3827
|
+
if (subcommand === "plan") {
|
|
3828
|
+
const goalId2 = rest[1] || optionString(options, "goalId");
|
|
3829
|
+
if (!goalId2) throw new Error("usage: goal plan <goal-id> [--json|--edit <instructions>]");
|
|
3830
|
+
const revision = optionString(options, "edit") || optionString(options, "revision");
|
|
3831
|
+
if (revision) {
|
|
3832
|
+
await editGoalPlan({ options, goalId: goalId2, revision });
|
|
3833
|
+
return;
|
|
3834
|
+
}
|
|
3835
|
+
await printGoalPlan({ options, goalId: goalId2 });
|
|
3836
|
+
return;
|
|
3837
|
+
}
|
|
3838
|
+
if (subcommand === "create-agent" || subcommand === "update-agent") {
|
|
3839
|
+
if (subcommand === "update-agent" && !optionString(options, "agentId")) {
|
|
3840
|
+
throw new Error('usage: goal update-agent "<agent change>" --agent-id <id>');
|
|
3841
|
+
}
|
|
3842
|
+
await createLocalGoal({
|
|
3843
|
+
options,
|
|
3844
|
+
objective: objectiveFromRest(
|
|
3845
|
+
rest.slice(1),
|
|
3846
|
+
subcommand === "create-agent" ? 'usage: goal create-agent "<agent idea>"' : 'usage: goal update-agent "<agent change>" --agent-id <id>'
|
|
3847
|
+
),
|
|
3848
|
+
kind: goalKindFromCommand(subcommand)
|
|
3849
|
+
});
|
|
3850
|
+
return;
|
|
3851
|
+
}
|
|
3852
|
+
if (subcommand === "answer") {
|
|
3853
|
+
const questionId = rest[1];
|
|
3854
|
+
if (!questionId) {
|
|
3855
|
+
throw new Error("usage: goal answer <question-id> --choice <id>|--answer <text>");
|
|
3856
|
+
}
|
|
3857
|
+
await answerGoal({ options, questionId });
|
|
3858
|
+
return;
|
|
3859
|
+
}
|
|
3860
|
+
const goalId = rest[1] || optionString(options, "goalId");
|
|
3861
|
+
if (!goalId) {
|
|
3862
|
+
throw new Error(`usage: goal ${subcommand} <goal-id>`);
|
|
3863
|
+
}
|
|
3864
|
+
await applyGoalLifecycle({
|
|
3865
|
+
options,
|
|
3866
|
+
goalId,
|
|
3867
|
+
action: subcommand
|
|
3868
|
+
});
|
|
3869
|
+
}
|
|
3870
|
+
|
|
3871
|
+
export {
|
|
3872
|
+
printGoalHelp,
|
|
3873
|
+
createLocalGoal,
|
|
3874
|
+
runGoalCommand
|
|
3875
|
+
};
|