@tickernelz/paperclip-pro-adapter-omp-local 2026.925.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/LICENSE +22 -0
- package/dist/cli/format-event.d.ts +2 -0
- package/dist/cli/format-event.d.ts.map +1 -0
- package/dist/cli/format-event.js +56 -0
- package/dist/cli/format-event.js.map +1 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +2 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/metadata.d.ts +9 -0
- package/dist/metadata.d.ts.map +1 -0
- package/dist/metadata.js +44 -0
- package/dist/metadata.js.map +1 -0
- package/dist/server/config.d.ts +22 -0
- package/dist/server/config.d.ts.map +1 -0
- package/dist/server/config.js +661 -0
- package/dist/server/config.js.map +1 -0
- package/dist/server/execute.cwd.test.d.ts +2 -0
- package/dist/server/execute.cwd.test.d.ts.map +1 -0
- package/dist/server/execute.cwd.test.js +88 -0
- package/dist/server/execute.cwd.test.js.map +1 -0
- package/dist/server/execute.d.ts +8 -0
- package/dist/server/execute.d.ts.map +1 -0
- package/dist/server/execute.js +812 -0
- package/dist/server/execute.js.map +1 -0
- package/dist/server/execute.toggles.test.d.ts +2 -0
- package/dist/server/execute.toggles.test.d.ts.map +1 -0
- package/dist/server/execute.toggles.test.js +147 -0
- package/dist/server/execute.toggles.test.js.map +1 -0
- package/dist/server/failure.d.ts +17 -0
- package/dist/server/failure.d.ts.map +1 -0
- package/dist/server/failure.js +74 -0
- package/dist/server/failure.js.map +1 -0
- package/dist/server/index.d.ts +6 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +97 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/models.d.ts +19 -0
- package/dist/server/models.d.ts.map +1 -0
- package/dist/server/models.js +208 -0
- package/dist/server/models.js.map +1 -0
- package/dist/server/parse.d.ts +36 -0
- package/dist/server/parse.d.ts.map +1 -0
- package/dist/server/parse.js +378 -0
- package/dist/server/parse.js.map +1 -0
- package/dist/server/parse.test.d.ts +2 -0
- package/dist/server/parse.test.d.ts.map +1 -0
- package/dist/server/parse.test.js +29 -0
- package/dist/server/parse.test.js.map +1 -0
- package/dist/server/profile.d.ts +7 -0
- package/dist/server/profile.d.ts.map +1 -0
- package/dist/server/profile.js +40 -0
- package/dist/server/profile.js.map +1 -0
- package/dist/server/progress.d.ts +16 -0
- package/dist/server/progress.d.ts.map +1 -0
- package/dist/server/progress.js +226 -0
- package/dist/server/progress.js.map +1 -0
- package/dist/server/quota.d.ts +4 -0
- package/dist/server/quota.d.ts.map +1 -0
- package/dist/server/quota.js +129 -0
- package/dist/server/quota.js.map +1 -0
- package/dist/server/redaction-repair.d.ts +5 -0
- package/dist/server/redaction-repair.d.ts.map +1 -0
- package/dist/server/redaction-repair.js +122 -0
- package/dist/server/redaction-repair.js.map +1 -0
- package/dist/server/settings-overlay.d.ts +19 -0
- package/dist/server/settings-overlay.d.ts.map +1 -0
- package/dist/server/settings-overlay.js +60 -0
- package/dist/server/settings-overlay.js.map +1 -0
- package/dist/server/skills.d.ts +5 -0
- package/dist/server/skills.d.ts.map +1 -0
- package/dist/server/skills.js +110 -0
- package/dist/server/skills.js.map +1 -0
- package/dist/server/test.d.ts +3 -0
- package/dist/server/test.d.ts.map +1 -0
- package/dist/server/test.js +435 -0
- package/dist/server/test.js.map +1 -0
- package/dist/ui/index.d.ts +2 -0
- package/dist/ui/index.d.ts.map +1 -0
- package/dist/ui/index.js +2 -0
- package/dist/ui/index.js.map +1 -0
- package/dist/ui/parse-stdout.d.ts +4 -0
- package/dist/ui/parse-stdout.d.ts.map +1 -0
- package/dist/ui/parse-stdout.js +480 -0
- package/dist/ui/parse-stdout.js.map +1 -0
- package/dist/ui/parse-stdout.test.d.ts +2 -0
- package/dist/ui/parse-stdout.test.d.ts.map +1 -0
- package/dist/ui/parse-stdout.test.js +69 -0
- package/dist/ui/parse-stdout.test.js.map +1 -0
- package/package.json +58 -0
|
@@ -0,0 +1,812 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { inferOpenAiCompatibleBiller, } from "@tickernelz/paperclip-pro-adapter-utils";
|
|
5
|
+
import { adapterExecutionTargetIsRemote, adapterExecutionTargetSessionIdentity, adapterExecutionTargetSessionMatches, adapterExecutionTargetUsesManagedHome, adapterExecutionTargetUsesPaperclipBridge, describeAdapterExecutionTarget, ensureAdapterExecutionTargetCommandResolvable, ensureAdapterExecutionTargetDirectory, ensureAdapterExecutionTargetRuntimeCommandInstalled, overrideAdapterExecutionTargetRemoteCwd, prepareAdapterExecutionTargetRuntime, readAdapterExecutionTarget, resolveAdapterExecutionTargetCommandForLogs, resolveAdapterExecutionTargetTimeoutSec, runAdapterExecutionTargetProcess, startAdapterExecutionTargetPaperclipBridge, } from "@tickernelz/paperclip-pro-adapter-utils/execution-target";
|
|
6
|
+
import { DEFAULT_PAPERCLIP_AGENT_PROMPT_TEMPLATE, asBoolean, asNumber, asString, buildInvocationEnvForLogs, buildPaperclipEnv, ensureAbsoluteDirectory, ensurePathInEnv, joinPromptSections, parseObject, readPaperclipIssueWorkModeFromContext, refreshPaperclipWorkspaceEnvForExecution, renderPaperclipWakePrompt, renderTemplate, sanitizeInheritedPaperclipEnv, stringifyPaperclipWakePayload, } from "@tickernelz/paperclip-pro-adapter-utils/server-utils";
|
|
7
|
+
import { OMP_INSTALL_COMMAND } from "../metadata.js";
|
|
8
|
+
import { prepareOmpRuntimeConfig, resolveOmpCommand, } from "./config.js";
|
|
9
|
+
import { createOmpOutputAccumulator, isOmpUnknownSessionError, parseOmpJsonLine, } from "./parse.js";
|
|
10
|
+
import { classifyOmpFailure } from "./failure.js";
|
|
11
|
+
import { createOmpProgressReporter } from "./progress.js";
|
|
12
|
+
import { ensureOmpSkills } from "./skills.js";
|
|
13
|
+
import { writeOmpSettingsOverlay } from "./settings-overlay.js";
|
|
14
|
+
const CAPABILITY_MANIFEST = {
|
|
15
|
+
bindings: [
|
|
16
|
+
"omp-jsonl",
|
|
17
|
+
"session-dir-resume",
|
|
18
|
+
"paperclip-workspace-env",
|
|
19
|
+
"paperclip-skills",
|
|
20
|
+
"local-execution",
|
|
21
|
+
"ssh-execution",
|
|
22
|
+
"sandbox-execution",
|
|
23
|
+
],
|
|
24
|
+
limits: [
|
|
25
|
+
"one-process-per-heartbeat",
|
|
26
|
+
"no-live-steering",
|
|
27
|
+
"no-interactive-dialogs",
|
|
28
|
+
"no-rpc-transport",
|
|
29
|
+
"schema-driven-ui-only",
|
|
30
|
+
"no-provider-quota-hook",
|
|
31
|
+
"no-remote-session-resume",
|
|
32
|
+
],
|
|
33
|
+
};
|
|
34
|
+
function stringList(value, commaSeparated = false) {
|
|
35
|
+
const values = Array.isArray(value)
|
|
36
|
+
? value
|
|
37
|
+
: typeof value === "string"
|
|
38
|
+
? value.split(commaSeparated ? /[\r\n,]+/ : /\r?\n/)
|
|
39
|
+
: [];
|
|
40
|
+
return values
|
|
41
|
+
.filter((entry) => typeof entry === "string")
|
|
42
|
+
.map((entry) => entry.trim())
|
|
43
|
+
.filter(Boolean);
|
|
44
|
+
}
|
|
45
|
+
const REMOTE_PATH_FIELDS = ["configFiles", "extensions", "hooks", "pluginDirs"];
|
|
46
|
+
export function rewriteRemoteConfigPaths(config, localCwd, remoteCwd) {
|
|
47
|
+
const rewritten = { ...config };
|
|
48
|
+
for (const field of REMOTE_PATH_FIELDS) {
|
|
49
|
+
const values = stringList(config[field]);
|
|
50
|
+
if (values.length === 0)
|
|
51
|
+
continue;
|
|
52
|
+
rewritten[field] = values.map((value) => {
|
|
53
|
+
const expanded = value === "~"
|
|
54
|
+
? os.homedir()
|
|
55
|
+
: value.startsWith("~/") || value.startsWith("~\\")
|
|
56
|
+
? path.resolve(os.homedir(), value.slice(2))
|
|
57
|
+
: path.resolve(localCwd, value);
|
|
58
|
+
const relative = path.relative(localCwd, expanded);
|
|
59
|
+
if (relative.startsWith(`..${path.sep}`) || relative === ".." || path.isAbsolute(relative)) {
|
|
60
|
+
throw new Error(`Remote OMP ${field} path must be inside the synchronized workspace: ${value}`);
|
|
61
|
+
}
|
|
62
|
+
return relative && relative !== "."
|
|
63
|
+
? path.posix.join(remoteCwd, ...relative.split(path.sep))
|
|
64
|
+
: remoteCwd;
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
return rewritten;
|
|
68
|
+
}
|
|
69
|
+
function firstNonEmptyLine(text) {
|
|
70
|
+
return text.split(/\r?\n/).map((line) => line.trim()).find(Boolean) ?? "";
|
|
71
|
+
}
|
|
72
|
+
function safePathSegment(value) {
|
|
73
|
+
return encodeURIComponent(value).replaceAll(".", "%2E");
|
|
74
|
+
}
|
|
75
|
+
function executionCwdsMatch(saved, current, remote) {
|
|
76
|
+
return remote
|
|
77
|
+
? path.posix.normalize(saved) === path.posix.normalize(current)
|
|
78
|
+
: path.resolve(saved) === path.resolve(current);
|
|
79
|
+
}
|
|
80
|
+
function stringsOnly(env) {
|
|
81
|
+
return Object.fromEntries(Object.entries(env).filter((entry) => typeof entry[1] === "string"));
|
|
82
|
+
}
|
|
83
|
+
function configuredModelProvider(model) {
|
|
84
|
+
if (!model || model.startsWith("@"))
|
|
85
|
+
return null;
|
|
86
|
+
const separator = model.indexOf("/");
|
|
87
|
+
return separator > 0 ? model.slice(0, separator) : null;
|
|
88
|
+
}
|
|
89
|
+
function resolveBiller(env, provider) {
|
|
90
|
+
return provider ?? inferOpenAiCompatibleBiller(env, null) ?? "unknown";
|
|
91
|
+
}
|
|
92
|
+
function addWakeEnvironment(env, runId, context) {
|
|
93
|
+
env.PAPERCLIP_RUN_ID = runId;
|
|
94
|
+
const taskId = asString(context.taskId, "").trim() || asString(context.issueId, "").trim();
|
|
95
|
+
const wakeReason = asString(context.wakeReason, "").trim();
|
|
96
|
+
const wakeCommentId = asString(context.wakeCommentId, "").trim() || asString(context.commentId, "").trim();
|
|
97
|
+
const approvalId = asString(context.approvalId, "").trim();
|
|
98
|
+
const approvalStatus = asString(context.approvalStatus, "").trim();
|
|
99
|
+
const linkedIssueIds = Array.isArray(context.issueIds)
|
|
100
|
+
? context.issueIds.filter((value) => typeof value === "string" && value.trim().length > 0)
|
|
101
|
+
: [];
|
|
102
|
+
const wakePayload = stringifyPaperclipWakePayload(context.paperclipWake);
|
|
103
|
+
const issueWorkMode = readPaperclipIssueWorkModeFromContext(context);
|
|
104
|
+
if (taskId)
|
|
105
|
+
env.PAPERCLIP_TASK_ID = taskId;
|
|
106
|
+
if (wakeReason)
|
|
107
|
+
env.PAPERCLIP_WAKE_REASON = wakeReason;
|
|
108
|
+
if (wakeCommentId)
|
|
109
|
+
env.PAPERCLIP_WAKE_COMMENT_ID = wakeCommentId;
|
|
110
|
+
if (approvalId)
|
|
111
|
+
env.PAPERCLIP_APPROVAL_ID = approvalId;
|
|
112
|
+
if (approvalStatus)
|
|
113
|
+
env.PAPERCLIP_APPROVAL_STATUS = approvalStatus;
|
|
114
|
+
if (linkedIssueIds.length > 0)
|
|
115
|
+
env.PAPERCLIP_LINKED_ISSUE_IDS = linkedIssueIds.join(",");
|
|
116
|
+
if (wakePayload)
|
|
117
|
+
env.PAPERCLIP_WAKE_PAYLOAD_JSON = wakePayload;
|
|
118
|
+
if (issueWorkMode)
|
|
119
|
+
env.PAPERCLIP_ISSUE_WORK_MODE = issueWorkMode;
|
|
120
|
+
}
|
|
121
|
+
function buildOmpArgs(input) {
|
|
122
|
+
const { config } = input;
|
|
123
|
+
const args = ["--mode", "json", "-p"];
|
|
124
|
+
const baseSystemPrompt = asString(config.systemPrompt, "").trim();
|
|
125
|
+
if (baseSystemPrompt)
|
|
126
|
+
args.push("--system-prompt", baseSystemPrompt);
|
|
127
|
+
args.push("--append-system-prompt", input.systemPrompt);
|
|
128
|
+
const model = asString(config.model, "").trim();
|
|
129
|
+
const thinking = (asString(config.thinking, "") || asString(config.thinkingEffort, "") || asString(config.effort, "")).trim();
|
|
130
|
+
const profile = input.effectiveProfile ?? "";
|
|
131
|
+
const smol = asString(config.smolModel, "").trim();
|
|
132
|
+
const slow = asString(config.slowModel, "").trim();
|
|
133
|
+
const plan = asString(config.planModel, "").trim();
|
|
134
|
+
const modelCycle = stringList(config.modelCycle, true);
|
|
135
|
+
const tools = stringList(config.tools, true);
|
|
136
|
+
const skills = stringList(config.skills, true);
|
|
137
|
+
const approvalMode = asString(config.approvalMode, "").trim();
|
|
138
|
+
const noSession = asBoolean(config.noSession, false);
|
|
139
|
+
if (model)
|
|
140
|
+
args.push("--model", model);
|
|
141
|
+
args.push(asBoolean(config.printThoughts, true) ? "--print-thoughts" : "--hide-thinking");
|
|
142
|
+
if (thinking)
|
|
143
|
+
args.push("--thinking", thinking);
|
|
144
|
+
if (profile && !input.omitProfile)
|
|
145
|
+
args.push("--profile", profile);
|
|
146
|
+
if (smol)
|
|
147
|
+
args.push("--smol", smol);
|
|
148
|
+
if (slow)
|
|
149
|
+
args.push("--slow", slow);
|
|
150
|
+
if (plan)
|
|
151
|
+
args.push("--plan", plan);
|
|
152
|
+
if (modelCycle.length > 0)
|
|
153
|
+
args.push("--models", modelCycle.join(","));
|
|
154
|
+
if (asBoolean(config.noTools, false))
|
|
155
|
+
args.push("--no-tools");
|
|
156
|
+
if (tools.length > 0)
|
|
157
|
+
args.push("--tools", tools.join(","));
|
|
158
|
+
if (skills.length > 0)
|
|
159
|
+
args.push("--skills", skills.join(","));
|
|
160
|
+
args.push("--approval-mode", ["always-ask", "write", "yolo"].includes(approvalMode) ? approvalMode : "yolo");
|
|
161
|
+
if (asBoolean(config.advisor, false))
|
|
162
|
+
args.push("--advisor");
|
|
163
|
+
const noPrewalk = asBoolean(config.noPrewalk, false);
|
|
164
|
+
const prewalkInto = asString(config.prewalkInto, "").trim();
|
|
165
|
+
if (noPrewalk) {
|
|
166
|
+
args.push("--no-prewalk");
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
if (asBoolean(config.prewalk, false))
|
|
170
|
+
args.push("--prewalk");
|
|
171
|
+
if (prewalkInto)
|
|
172
|
+
args.push("--prewalk-into", prewalkInto);
|
|
173
|
+
}
|
|
174
|
+
if (asBoolean(config.allowHome, false))
|
|
175
|
+
args.push("--allow-home");
|
|
176
|
+
const planYolo = asBoolean(config.planYolo, false);
|
|
177
|
+
const planYoloInto = asString(config.planYoloInto, "").trim();
|
|
178
|
+
if (planYolo) {
|
|
179
|
+
args.push("--plan-yolo");
|
|
180
|
+
if (planYoloInto)
|
|
181
|
+
args.push("--plan-yolo-into", planYoloInto);
|
|
182
|
+
}
|
|
183
|
+
const maxTime = asString(config.maxTime, "").trim() || (asNumber(config.maxTime, 0) > 0 ? String(asNumber(config.maxTime, 0)) : "");
|
|
184
|
+
if (maxTime)
|
|
185
|
+
args.push("--max-time", maxTime);
|
|
186
|
+
if (input.settingsOverlayPath)
|
|
187
|
+
args.push("--config", input.settingsOverlayPath);
|
|
188
|
+
for (const configFile of stringList(config.configFiles))
|
|
189
|
+
args.push("--config", configFile);
|
|
190
|
+
for (const extension of stringList(config.extensions))
|
|
191
|
+
args.push("--extension", extension);
|
|
192
|
+
for (const pluginDir of stringList(config.pluginDirs))
|
|
193
|
+
args.push("--plugin-dir", pluginDir);
|
|
194
|
+
for (const hook of stringList(config.hooks))
|
|
195
|
+
args.push("--hook", hook);
|
|
196
|
+
if (asBoolean(config.noExtensions, false))
|
|
197
|
+
args.push("--no-extensions");
|
|
198
|
+
if (asBoolean(config.noSkills, false))
|
|
199
|
+
args.push("--no-skills");
|
|
200
|
+
if (asBoolean(config.noRules, false))
|
|
201
|
+
args.push("--no-rules");
|
|
202
|
+
if (asBoolean(config.noLsp, false))
|
|
203
|
+
args.push("--no-lsp");
|
|
204
|
+
if (asBoolean(config.noPty, false))
|
|
205
|
+
args.push("--no-pty");
|
|
206
|
+
if (asBoolean(config.noTitle, true))
|
|
207
|
+
args.push("--no-title");
|
|
208
|
+
if (noSession) {
|
|
209
|
+
args.push("--no-session");
|
|
210
|
+
}
|
|
211
|
+
else {
|
|
212
|
+
args.push("--session-dir", input.sessionDir);
|
|
213
|
+
if (input.resumeSessionId)
|
|
214
|
+
args.push("--resume", input.resumeSessionId);
|
|
215
|
+
}
|
|
216
|
+
const primaryCwd = input.cwd ? path.resolve(input.cwd) : "";
|
|
217
|
+
const addedDirs = new Set();
|
|
218
|
+
for (const dir of stringList(config.addDirs)) {
|
|
219
|
+
const resolved = path.resolve(dir);
|
|
220
|
+
if (resolved && resolved !== primaryCwd && !addedDirs.has(resolved)) {
|
|
221
|
+
addedDirs.add(resolved);
|
|
222
|
+
args.push("--add-dir=" + resolved);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
if (input.context && Array.isArray(input.context.paperclipWorkspaces)) {
|
|
226
|
+
for (const ws of input.context.paperclipWorkspaces) {
|
|
227
|
+
if (ws && typeof ws === "object" && !Array.isArray(ws)) {
|
|
228
|
+
const wsCwd = asString(ws.cwd, "").trim();
|
|
229
|
+
if (wsCwd) {
|
|
230
|
+
const resolved = path.resolve(wsCwd);
|
|
231
|
+
if (resolved && resolved !== primaryCwd && !addedDirs.has(resolved)) {
|
|
232
|
+
addedDirs.add(resolved);
|
|
233
|
+
args.push("--add-dir=" + resolved);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
args.push(...stringList(config.extraArgs));
|
|
240
|
+
args.push(input.userPrompt);
|
|
241
|
+
return args;
|
|
242
|
+
}
|
|
243
|
+
async function buildPrompts(input) {
|
|
244
|
+
const templateData = {
|
|
245
|
+
agentId: input.agent.id,
|
|
246
|
+
companyId: input.agent.companyId,
|
|
247
|
+
runId: input.runId,
|
|
248
|
+
company: { id: input.agent.companyId },
|
|
249
|
+
agent: input.agent,
|
|
250
|
+
run: { id: input.runId, source: "on_demand" },
|
|
251
|
+
context: input.context,
|
|
252
|
+
};
|
|
253
|
+
const notes = [];
|
|
254
|
+
const instructionsPath = asString(input.config.instructionsFilePath, "").trim();
|
|
255
|
+
let instructions = "";
|
|
256
|
+
if (instructionsPath) {
|
|
257
|
+
const resolvedPath = path.resolve(input.cwd, instructionsPath);
|
|
258
|
+
try {
|
|
259
|
+
const contents = await fs.readFile(resolvedPath, "utf8");
|
|
260
|
+
instructions = joinPromptSections([
|
|
261
|
+
contents,
|
|
262
|
+
`The above agent instructions were loaded from ${resolvedPath}. Resolve relative file references from ${path.dirname(resolvedPath)}/.`,
|
|
263
|
+
]);
|
|
264
|
+
notes.push(`Loaded agent instructions from ${resolvedPath}`);
|
|
265
|
+
}
|
|
266
|
+
catch (error) {
|
|
267
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
268
|
+
await input.onLog("stderr", `[paperclip] Warning: could not read agent instructions file "${resolvedPath}": ${reason}\n`);
|
|
269
|
+
notes.push(`Configured instructionsFilePath ${resolvedPath}, but it could not be read.`);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
const paperclipContract = renderTemplate(DEFAULT_PAPERCLIP_AGENT_PROMPT_TEMPLATE, templateData);
|
|
273
|
+
const systemPrompt = joinPromptSections([instructions, paperclipContract]);
|
|
274
|
+
const promptTemplate = asString(input.config.promptTemplate, DEFAULT_PAPERCLIP_AGENT_PROMPT_TEMPLATE);
|
|
275
|
+
const bootstrapTemplate = asString(input.config.bootstrapPromptTemplate, "");
|
|
276
|
+
const bootstrapPrompt = !input.resumedSession && bootstrapTemplate.trim()
|
|
277
|
+
? renderTemplate(bootstrapTemplate, templateData).trim()
|
|
278
|
+
: "";
|
|
279
|
+
const wakePrompt = renderPaperclipWakePrompt(input.context.paperclipWake, {
|
|
280
|
+
resumedSession: input.resumedSession,
|
|
281
|
+
});
|
|
282
|
+
const wakePayload = parseObject(input.context.paperclipWake);
|
|
283
|
+
const recoveryWake = Object.keys(parseObject(wakePayload.recovery)).length > 0 ||
|
|
284
|
+
asString(wakePayload.reason, "").trim() === "source_scoped_recovery_action";
|
|
285
|
+
const heartbeatPrompt = ((input.resumedSession && wakePrompt.length > 0) || recoveryWake)
|
|
286
|
+
? ""
|
|
287
|
+
: renderTemplate(promptTemplate, templateData).trim();
|
|
288
|
+
const sessionHandoff = asString(input.context.paperclipSessionHandoffMarkdown, "").trim();
|
|
289
|
+
const userPrompt = joinPromptSections([
|
|
290
|
+
bootstrapPrompt,
|
|
291
|
+
wakePrompt,
|
|
292
|
+
sessionHandoff,
|
|
293
|
+
heartbeatPrompt,
|
|
294
|
+
]);
|
|
295
|
+
return {
|
|
296
|
+
systemPrompt,
|
|
297
|
+
userPrompt,
|
|
298
|
+
promptMetrics: {
|
|
299
|
+
systemPromptChars: systemPrompt.length,
|
|
300
|
+
instructionsChars: instructions.length,
|
|
301
|
+
promptChars: userPrompt.length,
|
|
302
|
+
bootstrapPromptChars: bootstrapPrompt.length,
|
|
303
|
+
wakePromptChars: wakePrompt.length,
|
|
304
|
+
sessionHandoffChars: sessionHandoff.length,
|
|
305
|
+
heartbeatPromptChars: heartbeatPrompt.length,
|
|
306
|
+
},
|
|
307
|
+
notes,
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
export function applyOmpRuntimeToggleEnv(env, config) {
|
|
311
|
+
if (asBoolean(config.noPty, false))
|
|
312
|
+
env.PI_NO_PTY = "1";
|
|
313
|
+
else
|
|
314
|
+
delete env.PI_NO_PTY;
|
|
315
|
+
if (asBoolean(config.noTitle, true))
|
|
316
|
+
env.PI_NO_TITLE = "1";
|
|
317
|
+
else
|
|
318
|
+
delete env.PI_NO_TITLE;
|
|
319
|
+
}
|
|
320
|
+
export function applyRuntimeToolAccess(env, tools, mcp) {
|
|
321
|
+
const sections = [];
|
|
322
|
+
if (tools) {
|
|
323
|
+
env.PAPERCLIP_RUNTIME_TOOLS_TOKEN = tools.bearerToken;
|
|
324
|
+
env.PAPERCLIP_RUNTIME_TOOLS_MCP_ENDPOINT = tools.mcpEndpoint;
|
|
325
|
+
env.PAPERCLIP_RUNTIME_TOOLS_EXPIRES_AT = tools.expiresAt;
|
|
326
|
+
env.PAPERCLIP_CONNECTIONS_SEARCH_URL = tools.rest.connectionsSearch;
|
|
327
|
+
env.PAPERCLIP_CONNECTION_REQUEST_URL = tools.rest.connectionRequest;
|
|
328
|
+
sections.push(tools.guidance);
|
|
329
|
+
sections.push([
|
|
330
|
+
"Paperclip runtime tools are delivered through the environment of this run.",
|
|
331
|
+
`Bearer token: $PAPERCLIP_RUNTIME_TOOLS_TOKEN (valid until ${tools.expiresAt}).`,
|
|
332
|
+
`Search connections: POST $PAPERCLIP_CONNECTIONS_SEARCH_URL`,
|
|
333
|
+
`Request a connection: POST $PAPERCLIP_CONNECTION_REQUEST_URL`,
|
|
334
|
+
`MCP endpoint: $PAPERCLIP_RUNTIME_TOOLS_MCP_ENDPOINT`,
|
|
335
|
+
"Send the bearer token as the Authorization header. Never print the token.",
|
|
336
|
+
].join("\n"));
|
|
337
|
+
}
|
|
338
|
+
const servers = mcp?.getServers() ?? [];
|
|
339
|
+
if (servers.length > 0) {
|
|
340
|
+
env.PAPERCLIP_RUNTIME_MCP_SERVERS = JSON.stringify(servers.map((server) => ({ name: server.name, url: server.url, connectionId: server.connectionId })));
|
|
341
|
+
env.PAPERCLIP_RUNTIME_MCP_TOKENS = JSON.stringify(Object.fromEntries(servers.map((server) => [server.name, server.token])));
|
|
342
|
+
sections.push([
|
|
343
|
+
`Paperclip provisioned ${servers.length} MCP server(s) for this run: ${servers.map((s) => s.name).join(", ")}.`,
|
|
344
|
+
"Endpoints are in $PAPERCLIP_RUNTIME_MCP_SERVERS and their bearer tokens in $PAPERCLIP_RUNTIME_MCP_TOKENS, keyed by server name.",
|
|
345
|
+
"OMP loads MCP servers from its agent directory, so reach these over HTTP for this run instead of expecting them in the tool list.",
|
|
346
|
+
].join("\n"));
|
|
347
|
+
}
|
|
348
|
+
return sections.join("\n\n");
|
|
349
|
+
}
|
|
350
|
+
export function applyPreparedOmpAgentEnvironment(env, prepared, agentDir = prepared.agentDir) {
|
|
351
|
+
if (agentDir)
|
|
352
|
+
env.PI_CODING_AGENT_DIR = agentDir;
|
|
353
|
+
if (!prepared.materialized && !(prepared.profileSpecified && prepared.profile === null))
|
|
354
|
+
return;
|
|
355
|
+
env.OMP_PROFILE = "";
|
|
356
|
+
env.PI_PROFILE = "";
|
|
357
|
+
}
|
|
358
|
+
export async function execute(ctx) {
|
|
359
|
+
const { runId, agent, runtime, config, context, onLog, onMeta, onSpawn, authToken } = ctx;
|
|
360
|
+
const executionTarget = readAdapterExecutionTarget({
|
|
361
|
+
executionTarget: ctx.executionTarget,
|
|
362
|
+
legacyRemoteExecution: ctx.executionTransport?.remoteExecution,
|
|
363
|
+
});
|
|
364
|
+
const remote = adapterExecutionTargetIsRemote(executionTarget);
|
|
365
|
+
const workspace = parseObject(context.paperclipWorkspace);
|
|
366
|
+
const workspaceCwd = asString(workspace.cwd, "").trim();
|
|
367
|
+
const workspaceSource = asString(workspace.source, "");
|
|
368
|
+
const configuredCwd = asString(config.cwd, "").trim();
|
|
369
|
+
const useConfiguredInsteadOfAgentHome = workspaceSource === "agent_home" && configuredCwd.length > 0;
|
|
370
|
+
const effectiveWorkspaceCwd = useConfiguredInsteadOfAgentHome ? "" : workspaceCwd;
|
|
371
|
+
const cwd = effectiveWorkspaceCwd || configuredCwd || process.cwd();
|
|
372
|
+
await ensureAbsoluteDirectory(cwd, { createIfMissing: true });
|
|
373
|
+
const preparedConfig = await prepareOmpRuntimeConfig(config, { forceMaterialized: remote });
|
|
374
|
+
let restoreWorkspace = null;
|
|
375
|
+
let paperclipBridge = null;
|
|
376
|
+
let settingsOverlay = null;
|
|
377
|
+
try {
|
|
378
|
+
await ensureOmpSkills(config, preparedConfig.agentDir ?? undefined);
|
|
379
|
+
const configuredEnv = parseObject(config.env);
|
|
380
|
+
const env = buildPaperclipEnv(agent);
|
|
381
|
+
addWakeEnvironment(env, runId, context);
|
|
382
|
+
const workspaceHints = Array.isArray(context.paperclipWorkspaces)
|
|
383
|
+
? context.paperclipWorkspaces.filter((value) => value !== null && typeof value === "object" && !Array.isArray(value))
|
|
384
|
+
: [];
|
|
385
|
+
let effectiveExecutionCwd = executionTarget?.kind === "remote" ? executionTarget.remoteCwd : cwd;
|
|
386
|
+
refreshPaperclipWorkspaceEnvForExecution({
|
|
387
|
+
env,
|
|
388
|
+
envConfig: {},
|
|
389
|
+
workspaceCwd: effectiveWorkspaceCwd,
|
|
390
|
+
workspaceSource,
|
|
391
|
+
workspaceStrategy: asString(workspace.strategy, ""),
|
|
392
|
+
workspaceId: asString(workspace.workspaceId, ""),
|
|
393
|
+
workspaceRepoUrl: asString(workspace.repoUrl, ""),
|
|
394
|
+
workspaceRepoRef: asString(workspace.repoRef, ""),
|
|
395
|
+
workspaceBranch: asString(workspace.branch, ""),
|
|
396
|
+
workspaceWorktreePath: asString(workspace.worktreePath, ""),
|
|
397
|
+
workspaceHints,
|
|
398
|
+
agentHome: asString(workspace.agentHome, ""),
|
|
399
|
+
executionTargetIsRemote: remote,
|
|
400
|
+
executionCwd: effectiveExecutionCwd,
|
|
401
|
+
});
|
|
402
|
+
for (const [key, value] of Object.entries(configuredEnv)) {
|
|
403
|
+
if (typeof value !== "string")
|
|
404
|
+
continue;
|
|
405
|
+
if (key.startsWith("PAPERCLIP_") && Object.hasOwn(env, key))
|
|
406
|
+
continue;
|
|
407
|
+
env[key] = value;
|
|
408
|
+
}
|
|
409
|
+
if (!env.PAPERCLIP_API_KEY && authToken)
|
|
410
|
+
env.PAPERCLIP_API_KEY = authToken;
|
|
411
|
+
const runtimeToolGuidance = applyRuntimeToolAccess(env, ctx.runtimeTools, ctx.runtimeMcp);
|
|
412
|
+
applyPreparedOmpAgentEnvironment(env, preparedConfig);
|
|
413
|
+
const inheritedLocalEnv = stringsOnly(sanitizeInheritedPaperclipEnv(process.env));
|
|
414
|
+
applyPreparedOmpAgentEnvironment(inheritedLocalEnv, preparedConfig, null);
|
|
415
|
+
if (preparedConfig.materialized || (preparedConfig.profileSpecified && preparedConfig.profile === null)) {
|
|
416
|
+
inheritedLocalEnv.OMP_PROFILE = "";
|
|
417
|
+
inheritedLocalEnv.PI_PROFILE = "";
|
|
418
|
+
}
|
|
419
|
+
applyOmpRuntimeToggleEnv(env, config);
|
|
420
|
+
applyOmpRuntimeToggleEnv(inheritedLocalEnv, config);
|
|
421
|
+
const localRuntimeEnv = stringsOnly(ensurePathInEnv({ ...inheritedLocalEnv, ...env }));
|
|
422
|
+
const targetEnv = remote ? stringsOnly(ensurePathInEnv(env)) : localRuntimeEnv;
|
|
423
|
+
const command = resolveOmpCommand(config);
|
|
424
|
+
const timeoutSec = resolveAdapterExecutionTargetTimeoutSec(executionTarget, asNumber(config.timeoutSec, 43200));
|
|
425
|
+
const graceSec = asNumber(config.graceSec, 20);
|
|
426
|
+
await ensureAdapterExecutionTargetRuntimeCommandInstalled({
|
|
427
|
+
runId,
|
|
428
|
+
target: executionTarget,
|
|
429
|
+
installCommand: ctx.runtimeCommandSpec?.installCommand,
|
|
430
|
+
detectCommand: ctx.runtimeCommandSpec?.detectCommand ?? command,
|
|
431
|
+
cwd,
|
|
432
|
+
env: targetEnv,
|
|
433
|
+
timeoutSec,
|
|
434
|
+
graceSec,
|
|
435
|
+
onLog,
|
|
436
|
+
});
|
|
437
|
+
await ensureAdapterExecutionTargetCommandResolvable(command, executionTarget, cwd, targetEnv, {
|
|
438
|
+
installCommand: OMP_INSTALL_COMMAND,
|
|
439
|
+
timeoutSec,
|
|
440
|
+
});
|
|
441
|
+
let runtimeRootDir = null;
|
|
442
|
+
let executionConfig = config;
|
|
443
|
+
if (remote) {
|
|
444
|
+
await onLog("stdout", `[paperclip] Syncing workspace and OMP runtime assets to ${describeAdapterExecutionTarget(executionTarget)}.\n`);
|
|
445
|
+
const preparedTarget = await prepareAdapterExecutionTargetRuntime({
|
|
446
|
+
runId,
|
|
447
|
+
target: executionTarget,
|
|
448
|
+
adapterKey: "omp",
|
|
449
|
+
workspaceLocalDir: cwd,
|
|
450
|
+
timeoutSec,
|
|
451
|
+
installCommand: ctx.runtimeCommandSpec?.installCommand ?? OMP_INSTALL_COMMAND,
|
|
452
|
+
detectCommand: ctx.runtimeCommandSpec?.detectCommand ?? command,
|
|
453
|
+
onProgress: (line) => onLog("stdout", line),
|
|
454
|
+
onRuntimeProgress: ctx.onRuntimeProgress,
|
|
455
|
+
assets: preparedConfig.agentDir
|
|
456
|
+
? [{ key: "agentDir", localDir: preparedConfig.agentDir, followSymlinks: true }]
|
|
457
|
+
: [],
|
|
458
|
+
});
|
|
459
|
+
restoreWorkspace = () => preparedTarget.restoreWorkspace((line) => onLog("stdout", line));
|
|
460
|
+
effectiveExecutionCwd = preparedTarget.workspaceRemoteDir ?? effectiveExecutionCwd;
|
|
461
|
+
executionConfig = { ...rewriteRemoteConfigPaths(config, cwd, effectiveExecutionCwd), noSession: true };
|
|
462
|
+
runtimeRootDir = preparedTarget.runtimeRootDir;
|
|
463
|
+
applyPreparedOmpAgentEnvironment(env, preparedConfig, preparedTarget.assetDirs.agentDir ?? null);
|
|
464
|
+
if (adapterExecutionTargetUsesManagedHome(executionTarget) && runtimeRootDir)
|
|
465
|
+
env.HOME = runtimeRootDir;
|
|
466
|
+
refreshPaperclipWorkspaceEnvForExecution({
|
|
467
|
+
env,
|
|
468
|
+
envConfig: {},
|
|
469
|
+
workspaceCwd: effectiveWorkspaceCwd,
|
|
470
|
+
workspaceSource,
|
|
471
|
+
workspaceStrategy: asString(workspace.strategy, ""),
|
|
472
|
+
workspaceId: asString(workspace.workspaceId, ""),
|
|
473
|
+
workspaceRepoUrl: asString(workspace.repoUrl, ""),
|
|
474
|
+
workspaceRepoRef: asString(workspace.repoRef, ""),
|
|
475
|
+
workspaceBranch: asString(workspace.branch, ""),
|
|
476
|
+
workspaceWorktreePath: asString(workspace.worktreePath, ""),
|
|
477
|
+
workspaceHints,
|
|
478
|
+
agentHome: asString(workspace.agentHome, ""),
|
|
479
|
+
executionTargetIsRemote: true,
|
|
480
|
+
executionCwd: effectiveExecutionCwd,
|
|
481
|
+
});
|
|
482
|
+
applyPreparedOmpAgentEnvironment(env, preparedConfig, preparedTarget.assetDirs.agentDir ?? null);
|
|
483
|
+
}
|
|
484
|
+
const runtimeTarget = overrideAdapterExecutionTargetRemoteCwd(executionTarget, effectiveExecutionCwd);
|
|
485
|
+
if (remote && adapterExecutionTargetUsesPaperclipBridge(runtimeTarget)) {
|
|
486
|
+
paperclipBridge = await startAdapterExecutionTargetPaperclipBridge({
|
|
487
|
+
runId,
|
|
488
|
+
target: runtimeTarget,
|
|
489
|
+
runtimeRootDir,
|
|
490
|
+
adapterKey: "omp",
|
|
491
|
+
timeoutSec,
|
|
492
|
+
hostApiToken: env.PAPERCLIP_API_KEY,
|
|
493
|
+
hostApiUrl: env.PAPERCLIP_API_URL,
|
|
494
|
+
onLog,
|
|
495
|
+
});
|
|
496
|
+
if (paperclipBridge)
|
|
497
|
+
Object.assign(env, paperclipBridge.env);
|
|
498
|
+
}
|
|
499
|
+
const omitProfile = preparedConfig.materialized || (preparedConfig.profileSpecified && preparedConfig.profile === null);
|
|
500
|
+
const invocationEnv = remote ? stringsOnly(ensurePathInEnv(env)) : localRuntimeEnv;
|
|
501
|
+
if (remote)
|
|
502
|
+
applyPreparedOmpAgentEnvironment(invocationEnv, preparedConfig, env.PI_CODING_AGENT_DIR ?? null);
|
|
503
|
+
const resolvedCommand = await resolveAdapterExecutionTargetCommandForLogs(command, runtimeTarget, cwd, invocationEnv);
|
|
504
|
+
const loggedEnv = buildInvocationEnvForLogs(env, {
|
|
505
|
+
runtimeEnv: invocationEnv,
|
|
506
|
+
includeRuntimeKeys: ["HOME"],
|
|
507
|
+
resolvedCommand,
|
|
508
|
+
});
|
|
509
|
+
const runtimeParams = parseObject(runtime.sessionParams);
|
|
510
|
+
const savedSessionId = asString(runtimeParams.sessionId, runtime.sessionId ?? "").trim();
|
|
511
|
+
const savedCwd = asString(runtimeParams.cwd, "").trim();
|
|
512
|
+
const savedSessionDir = asString(runtimeParams.sessionDir, "").trim();
|
|
513
|
+
const savedRemoteExecution = parseObject(runtimeParams.remoteExecution);
|
|
514
|
+
const ephemeralSession = asBoolean(executionConfig.noSession, false);
|
|
515
|
+
const canResume = Boolean(!ephemeralSession &&
|
|
516
|
+
savedSessionId &&
|
|
517
|
+
(!savedCwd || executionCwdsMatch(savedCwd, effectiveExecutionCwd, remote)) &&
|
|
518
|
+
adapterExecutionTargetSessionMatches(savedRemoteExecution, runtimeTarget));
|
|
519
|
+
if (savedSessionId && !canResume) {
|
|
520
|
+
await onLog("stdout", `[paperclip] OMP session "${savedSessionId}" does not match the current workspace or execution target; starting fresh.\n`);
|
|
521
|
+
}
|
|
522
|
+
const configuredSessionDir = asString(executionConfig.sessionDir, "").trim();
|
|
523
|
+
const defaultSessionDir = remote
|
|
524
|
+
? path.posix.join(runtimeRootDir ?? path.posix.join(effectiveExecutionCwd, ".paperclip-runtime", "omp"), "sessions", safePathSegment(agent.id))
|
|
525
|
+
: path.join(os.homedir(), ".omp", "paperclip", "sessions", safePathSegment(agent.id));
|
|
526
|
+
const sessionDir = canResume && savedSessionDir
|
|
527
|
+
? savedSessionDir
|
|
528
|
+
: configuredSessionDir || defaultSessionDir;
|
|
529
|
+
if (!ephemeralSession) {
|
|
530
|
+
await ensureAdapterExecutionTargetDirectory(runId, runtimeTarget, sessionDir, {
|
|
531
|
+
cwd,
|
|
532
|
+
env: invocationEnv,
|
|
533
|
+
timeoutSec: Math.min(timeoutSec || 15, 15),
|
|
534
|
+
graceSec: Math.min(graceSec, 5),
|
|
535
|
+
onLog,
|
|
536
|
+
createIfMissing: true,
|
|
537
|
+
});
|
|
538
|
+
}
|
|
539
|
+
settingsOverlay = await writeOmpSettingsOverlay({
|
|
540
|
+
runId,
|
|
541
|
+
target: runtimeTarget,
|
|
542
|
+
remote,
|
|
543
|
+
config: executionConfig,
|
|
544
|
+
remoteRootDir: runtimeRootDir,
|
|
545
|
+
cwd: remote ? effectiveExecutionCwd : cwd,
|
|
546
|
+
env: invocationEnv,
|
|
547
|
+
timeoutSec,
|
|
548
|
+
graceSec,
|
|
549
|
+
});
|
|
550
|
+
const prompts = await buildPrompts({
|
|
551
|
+
config: executionConfig,
|
|
552
|
+
context,
|
|
553
|
+
agent,
|
|
554
|
+
runId,
|
|
555
|
+
resumedSession: canResume,
|
|
556
|
+
cwd,
|
|
557
|
+
onLog,
|
|
558
|
+
});
|
|
559
|
+
if (runtimeToolGuidance) {
|
|
560
|
+
prompts.systemPrompt = joinPromptSections([prompts.systemPrompt, runtimeToolGuidance]);
|
|
561
|
+
prompts.promptMetrics.systemPromptChars = prompts.systemPrompt.length;
|
|
562
|
+
}
|
|
563
|
+
const commandNotes = [
|
|
564
|
+
...preparedConfig.notes,
|
|
565
|
+
...prompts.notes,
|
|
566
|
+
canResume ? `Resuming OMP session ${savedSessionId}` : `Using fresh OMP session directory ${sessionDir}`,
|
|
567
|
+
...(remote ? ["Remote execution uses an ephemeral OMP session; remote runtime directories are per run."] : []),
|
|
568
|
+
];
|
|
569
|
+
const runAttempt = async (resumeSessionId) => {
|
|
570
|
+
const args = buildOmpArgs({
|
|
571
|
+
config: executionConfig,
|
|
572
|
+
context,
|
|
573
|
+
cwd,
|
|
574
|
+
systemPrompt: prompts.systemPrompt,
|
|
575
|
+
userPrompt: prompts.userPrompt,
|
|
576
|
+
sessionDir,
|
|
577
|
+
resumeSessionId,
|
|
578
|
+
omitProfile,
|
|
579
|
+
effectiveProfile: preparedConfig.profile,
|
|
580
|
+
settingsOverlayPath: settingsOverlay?.path ?? null,
|
|
581
|
+
});
|
|
582
|
+
if (onMeta) {
|
|
583
|
+
await onMeta({
|
|
584
|
+
adapterType: "omp_local",
|
|
585
|
+
command: resolvedCommand,
|
|
586
|
+
cwd: effectiveExecutionCwd,
|
|
587
|
+
commandArgs: args.map((value, index) => index === args.length - 1 ? `<prompt ${prompts.userPrompt.length} chars>` : value),
|
|
588
|
+
commandNotes,
|
|
589
|
+
env: loggedEnv,
|
|
590
|
+
prompt: prompts.userPrompt,
|
|
591
|
+
promptMetrics: prompts.promptMetrics,
|
|
592
|
+
context,
|
|
593
|
+
});
|
|
594
|
+
}
|
|
595
|
+
let stdoutBuffer = "";
|
|
596
|
+
let reporterFailed = false;
|
|
597
|
+
let logQueue = Promise.resolve();
|
|
598
|
+
const queueLog = (stream, chunk) => {
|
|
599
|
+
logQueue = logQueue.then(() => onLog(stream, chunk)).catch(() => { });
|
|
600
|
+
return logQueue;
|
|
601
|
+
};
|
|
602
|
+
const reporter = createOmpProgressReporter(ctx.onRuntimeProgress, ctx.onEvent);
|
|
603
|
+
const accumulator = createOmpOutputAccumulator();
|
|
604
|
+
const ingestLine = async (line) => {
|
|
605
|
+
const trimmed = line.trim();
|
|
606
|
+
if (!trimmed)
|
|
607
|
+
return;
|
|
608
|
+
const event = parseOmpJsonLine(trimmed);
|
|
609
|
+
accumulator.push(trimmed, event);
|
|
610
|
+
try {
|
|
611
|
+
await reporter.ingest(trimmed, event);
|
|
612
|
+
}
|
|
613
|
+
catch {
|
|
614
|
+
reporterFailed = true;
|
|
615
|
+
}
|
|
616
|
+
};
|
|
617
|
+
const bufferedOnLog = async (stream, chunk) => {
|
|
618
|
+
if (stream === "stderr") {
|
|
619
|
+
await queueLog(stream, chunk);
|
|
620
|
+
return;
|
|
621
|
+
}
|
|
622
|
+
stdoutBuffer += chunk;
|
|
623
|
+
let newline = stdoutBuffer.indexOf("\n");
|
|
624
|
+
while (newline >= 0) {
|
|
625
|
+
const completeLine = stdoutBuffer.slice(0, newline + 1);
|
|
626
|
+
stdoutBuffer = stdoutBuffer.slice(newline + 1);
|
|
627
|
+
await queueLog("stdout", completeLine);
|
|
628
|
+
await ingestLine(completeLine);
|
|
629
|
+
newline = stdoutBuffer.indexOf("\n");
|
|
630
|
+
}
|
|
631
|
+
};
|
|
632
|
+
let spawnedPgid = null;
|
|
633
|
+
let spawnedPid = null;
|
|
634
|
+
let abortHandler = null;
|
|
635
|
+
const handleSpawn = async (meta) => {
|
|
636
|
+
spawnedPid = meta.pid;
|
|
637
|
+
spawnedPgid = meta.processGroupId;
|
|
638
|
+
if (onSpawn)
|
|
639
|
+
await onSpawn(meta);
|
|
640
|
+
if (ctx.signal?.aborted) {
|
|
641
|
+
triggerProcessAbort();
|
|
642
|
+
}
|
|
643
|
+
};
|
|
644
|
+
const triggerProcessAbort = () => {
|
|
645
|
+
try {
|
|
646
|
+
if (spawnedPgid && spawnedPgid > 0) {
|
|
647
|
+
process.kill(-spawnedPgid, "SIGINT");
|
|
648
|
+
}
|
|
649
|
+
else if (spawnedPid && spawnedPid > 0) {
|
|
650
|
+
process.kill(spawnedPid, "SIGINT");
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
catch {
|
|
654
|
+
}
|
|
655
|
+
};
|
|
656
|
+
if (ctx.signal) {
|
|
657
|
+
abortHandler = () => triggerProcessAbort();
|
|
658
|
+
if (ctx.signal.aborted) {
|
|
659
|
+
triggerProcessAbort();
|
|
660
|
+
}
|
|
661
|
+
else {
|
|
662
|
+
ctx.signal.addEventListener("abort", abortHandler, { once: true });
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
await ctx.onCancellationReady?.();
|
|
666
|
+
let proc;
|
|
667
|
+
try {
|
|
668
|
+
proc = await runAdapterExecutionTargetProcess(runId, runtimeTarget, command, args, {
|
|
669
|
+
cwd,
|
|
670
|
+
env: invocationEnv,
|
|
671
|
+
timeoutSec,
|
|
672
|
+
graceSec,
|
|
673
|
+
onSpawn: handleSpawn,
|
|
674
|
+
onRuntimeProgress: ctx.onRuntimeProgress,
|
|
675
|
+
onLog: bufferedOnLog,
|
|
676
|
+
runLogTail: paperclipBridge?.runLogTail,
|
|
677
|
+
});
|
|
678
|
+
if (stdoutBuffer) {
|
|
679
|
+
await queueLog("stdout", stdoutBuffer);
|
|
680
|
+
await ingestLine(stdoutBuffer);
|
|
681
|
+
}
|
|
682
|
+
await logQueue;
|
|
683
|
+
}
|
|
684
|
+
finally {
|
|
685
|
+
if (ctx.signal && abortHandler) {
|
|
686
|
+
ctx.signal.removeEventListener("abort", abortHandler);
|
|
687
|
+
}
|
|
688
|
+
try {
|
|
689
|
+
await reporter.flush();
|
|
690
|
+
}
|
|
691
|
+
catch {
|
|
692
|
+
reporterFailed = true;
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
return {
|
|
696
|
+
proc,
|
|
697
|
+
parsed: accumulator.result(),
|
|
698
|
+
pendingToolCount: reporter.pendingToolCount(),
|
|
699
|
+
sawProviderWork: reporter.sawProviderWork(),
|
|
700
|
+
reporterFailed,
|
|
701
|
+
};
|
|
702
|
+
};
|
|
703
|
+
const toResult = (attempt, retriedFresh, priorAttempt) => {
|
|
704
|
+
const parsedError = attempt.parsed.errors.find((error) => error.trim()) ?? "";
|
|
705
|
+
const stderrLine = firstNonEmptyLine(attempt.proc.stderr);
|
|
706
|
+
const processExitCode = attempt.proc.exitCode ?? (attempt.proc.signal ? 1 : 0);
|
|
707
|
+
const effectiveExitCode = processExitCode === 0 && parsedError ? 1 : processExitCode;
|
|
708
|
+
const resolvedSessionId = ephemeralSession
|
|
709
|
+
? null
|
|
710
|
+
: attempt.parsed.sessionId ?? (!retriedFresh ? savedSessionId || null : null);
|
|
711
|
+
const remoteExecution = remote ? adapterExecutionTargetSessionIdentity(runtimeTarget) : null;
|
|
712
|
+
const sessionParams = resolvedSessionId
|
|
713
|
+
? {
|
|
714
|
+
sessionId: resolvedSessionId,
|
|
715
|
+
cwd: effectiveExecutionCwd,
|
|
716
|
+
sessionDir,
|
|
717
|
+
...(remoteExecution ? { remoteExecution } : {}),
|
|
718
|
+
}
|
|
719
|
+
: null;
|
|
720
|
+
const provider = attempt.parsed.provider ?? configuredModelProvider(asString(executionConfig.model, "").trim());
|
|
721
|
+
const model = attempt.parsed.model ?? (asString(executionConfig.model, "").trim() || null);
|
|
722
|
+
const fallbackError = parsedError || stderrLine || `OMP exited with code ${effectiveExitCode}.`;
|
|
723
|
+
const failed = effectiveExitCode !== 0 || attempt.proc.timedOut;
|
|
724
|
+
const classification = failed
|
|
725
|
+
? classifyOmpFailure({
|
|
726
|
+
parsedError,
|
|
727
|
+
stderr: attempt.proc.stderr,
|
|
728
|
+
timedOut: attempt.proc.timedOut,
|
|
729
|
+
exitCode: effectiveExitCode,
|
|
730
|
+
signal: attempt.proc.signal,
|
|
731
|
+
})
|
|
732
|
+
: { errorCode: null, errorFamily: null, retryNotBefore: null };
|
|
733
|
+
const cancelled = ctx.signal?.aborted === true && (failed || attempt.proc.signal !== null);
|
|
734
|
+
const executionRecovery = cancelled && resolvedSessionId && attempt.pendingToolCount === 0
|
|
735
|
+
? { kind: "interrupted", providerStopped: true, sessionPreserved: true, actionOutcomes: "settled" }
|
|
736
|
+
: !attempt.sawProviderWork && failed
|
|
737
|
+
? { kind: "bootstrap", providerWorkStarted: false }
|
|
738
|
+
: null;
|
|
739
|
+
const result = {
|
|
740
|
+
exitCode: effectiveExitCode,
|
|
741
|
+
signal: attempt.proc.signal,
|
|
742
|
+
timedOut: attempt.proc.timedOut,
|
|
743
|
+
errorMessage: attempt.proc.timedOut
|
|
744
|
+
? `Timed out after ${timeoutSec}s`
|
|
745
|
+
: failed
|
|
746
|
+
? fallbackError
|
|
747
|
+
: null,
|
|
748
|
+
...(classification.errorCode ? { errorCode: classification.errorCode } : {}),
|
|
749
|
+
...(classification.errorFamily ? { errorFamily: classification.errorFamily } : {}),
|
|
750
|
+
...(classification.retryNotBefore ? { retryNotBefore: classification.retryNotBefore } : {}),
|
|
751
|
+
...(executionRecovery ? { executionRecovery } : {}),
|
|
752
|
+
usage: attempt.parsed.usage,
|
|
753
|
+
usageBasis: "per_run",
|
|
754
|
+
sessionId: resolvedSessionId,
|
|
755
|
+
sessionParams,
|
|
756
|
+
sessionDisplayId: resolvedSessionId,
|
|
757
|
+
provider,
|
|
758
|
+
biller: resolveBiller(invocationEnv, provider),
|
|
759
|
+
model,
|
|
760
|
+
billingType: "unknown",
|
|
761
|
+
costUsd: attempt.parsed.costUsd,
|
|
762
|
+
...(attempt.parsed.costUsd > 0 ? { cacheAdjustedCostUsd: attempt.parsed.costUsd } : {}),
|
|
763
|
+
summary: attempt.parsed.finalMessage ?? attempt.parsed.messages.at(-1) ?? null,
|
|
764
|
+
clearSession: retriedFresh && !resolvedSessionId,
|
|
765
|
+
resultJson: {
|
|
766
|
+
...(executionRecovery?.kind === "interrupted"
|
|
767
|
+
? { executionCancellation: { state: "acknowledged" } }
|
|
768
|
+
: {}),
|
|
769
|
+
stdout: attempt.proc.stdout,
|
|
770
|
+
stderr: attempt.proc.stderr,
|
|
771
|
+
errors: attempt.parsed.errors,
|
|
772
|
+
toolCalls: attempt.parsed.toolCalls,
|
|
773
|
+
unknownLines: attempt.parsed.unknownLines,
|
|
774
|
+
...(attempt.reporterFailed ? { progressReporterFailed: true } : {}),
|
|
775
|
+
capabilityManifest: CAPABILITY_MANIFEST,
|
|
776
|
+
...(priorAttempt
|
|
777
|
+
? {
|
|
778
|
+
staleSessionAttempt: {
|
|
779
|
+
stdout: priorAttempt.proc.stdout,
|
|
780
|
+
stderr: priorAttempt.proc.stderr,
|
|
781
|
+
},
|
|
782
|
+
}
|
|
783
|
+
: {}),
|
|
784
|
+
},
|
|
785
|
+
};
|
|
786
|
+
return result;
|
|
787
|
+
};
|
|
788
|
+
const initial = await runAttempt(canResume ? savedSessionId : null);
|
|
789
|
+
const initialFailed = !initial.proc.timedOut && ((initial.proc.exitCode ?? 0) !== 0 || initial.parsed.errors.length > 0);
|
|
790
|
+
if (canResume &&
|
|
791
|
+
initialFailed &&
|
|
792
|
+
isOmpUnknownSessionError(initial.proc.stdout, initial.proc.stderr)) {
|
|
793
|
+
await onLog("stdout", `[paperclip] OMP session "${savedSessionId}" is unavailable; retrying once with a fresh session.\n`);
|
|
794
|
+
const retry = await runAttempt(null);
|
|
795
|
+
return toResult(retry, true, initial);
|
|
796
|
+
}
|
|
797
|
+
return toResult(initial, false);
|
|
798
|
+
}
|
|
799
|
+
finally {
|
|
800
|
+
try {
|
|
801
|
+
await Promise.all([
|
|
802
|
+
paperclipBridge?.stop(),
|
|
803
|
+
restoreWorkspace?.(),
|
|
804
|
+
]);
|
|
805
|
+
}
|
|
806
|
+
finally {
|
|
807
|
+
await settingsOverlay?.cleanup().catch(() => { });
|
|
808
|
+
await preparedConfig.cleanup();
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
//# sourceMappingURL=execute.js.map
|