@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.
Files changed (94) hide show
  1. package/LICENSE +22 -0
  2. package/dist/cli/format-event.d.ts +2 -0
  3. package/dist/cli/format-event.d.ts.map +1 -0
  4. package/dist/cli/format-event.js +56 -0
  5. package/dist/cli/format-event.js.map +1 -0
  6. package/dist/cli/index.d.ts +2 -0
  7. package/dist/cli/index.d.ts.map +1 -0
  8. package/dist/cli/index.js +2 -0
  9. package/dist/cli/index.js.map +1 -0
  10. package/dist/index.d.ts +2 -0
  11. package/dist/index.d.ts.map +1 -0
  12. package/dist/index.js +2 -0
  13. package/dist/index.js.map +1 -0
  14. package/dist/metadata.d.ts +9 -0
  15. package/dist/metadata.d.ts.map +1 -0
  16. package/dist/metadata.js +44 -0
  17. package/dist/metadata.js.map +1 -0
  18. package/dist/server/config.d.ts +22 -0
  19. package/dist/server/config.d.ts.map +1 -0
  20. package/dist/server/config.js +661 -0
  21. package/dist/server/config.js.map +1 -0
  22. package/dist/server/execute.cwd.test.d.ts +2 -0
  23. package/dist/server/execute.cwd.test.d.ts.map +1 -0
  24. package/dist/server/execute.cwd.test.js +88 -0
  25. package/dist/server/execute.cwd.test.js.map +1 -0
  26. package/dist/server/execute.d.ts +8 -0
  27. package/dist/server/execute.d.ts.map +1 -0
  28. package/dist/server/execute.js +812 -0
  29. package/dist/server/execute.js.map +1 -0
  30. package/dist/server/execute.toggles.test.d.ts +2 -0
  31. package/dist/server/execute.toggles.test.d.ts.map +1 -0
  32. package/dist/server/execute.toggles.test.js +147 -0
  33. package/dist/server/execute.toggles.test.js.map +1 -0
  34. package/dist/server/failure.d.ts +17 -0
  35. package/dist/server/failure.d.ts.map +1 -0
  36. package/dist/server/failure.js +74 -0
  37. package/dist/server/failure.js.map +1 -0
  38. package/dist/server/index.d.ts +6 -0
  39. package/dist/server/index.d.ts.map +1 -0
  40. package/dist/server/index.js +97 -0
  41. package/dist/server/index.js.map +1 -0
  42. package/dist/server/models.d.ts +19 -0
  43. package/dist/server/models.d.ts.map +1 -0
  44. package/dist/server/models.js +208 -0
  45. package/dist/server/models.js.map +1 -0
  46. package/dist/server/parse.d.ts +36 -0
  47. package/dist/server/parse.d.ts.map +1 -0
  48. package/dist/server/parse.js +378 -0
  49. package/dist/server/parse.js.map +1 -0
  50. package/dist/server/parse.test.d.ts +2 -0
  51. package/dist/server/parse.test.d.ts.map +1 -0
  52. package/dist/server/parse.test.js +29 -0
  53. package/dist/server/parse.test.js.map +1 -0
  54. package/dist/server/profile.d.ts +7 -0
  55. package/dist/server/profile.d.ts.map +1 -0
  56. package/dist/server/profile.js +40 -0
  57. package/dist/server/profile.js.map +1 -0
  58. package/dist/server/progress.d.ts +16 -0
  59. package/dist/server/progress.d.ts.map +1 -0
  60. package/dist/server/progress.js +226 -0
  61. package/dist/server/progress.js.map +1 -0
  62. package/dist/server/quota.d.ts +4 -0
  63. package/dist/server/quota.d.ts.map +1 -0
  64. package/dist/server/quota.js +129 -0
  65. package/dist/server/quota.js.map +1 -0
  66. package/dist/server/redaction-repair.d.ts +5 -0
  67. package/dist/server/redaction-repair.d.ts.map +1 -0
  68. package/dist/server/redaction-repair.js +122 -0
  69. package/dist/server/redaction-repair.js.map +1 -0
  70. package/dist/server/settings-overlay.d.ts +19 -0
  71. package/dist/server/settings-overlay.d.ts.map +1 -0
  72. package/dist/server/settings-overlay.js +60 -0
  73. package/dist/server/settings-overlay.js.map +1 -0
  74. package/dist/server/skills.d.ts +5 -0
  75. package/dist/server/skills.d.ts.map +1 -0
  76. package/dist/server/skills.js +110 -0
  77. package/dist/server/skills.js.map +1 -0
  78. package/dist/server/test.d.ts +3 -0
  79. package/dist/server/test.d.ts.map +1 -0
  80. package/dist/server/test.js +435 -0
  81. package/dist/server/test.js.map +1 -0
  82. package/dist/ui/index.d.ts +2 -0
  83. package/dist/ui/index.d.ts.map +1 -0
  84. package/dist/ui/index.js +2 -0
  85. package/dist/ui/index.js.map +1 -0
  86. package/dist/ui/parse-stdout.d.ts +4 -0
  87. package/dist/ui/parse-stdout.d.ts.map +1 -0
  88. package/dist/ui/parse-stdout.js +480 -0
  89. package/dist/ui/parse-stdout.js.map +1 -0
  90. package/dist/ui/parse-stdout.test.d.ts +2 -0
  91. package/dist/ui/parse-stdout.test.d.ts.map +1 -0
  92. package/dist/ui/parse-stdout.test.js +69 -0
  93. package/dist/ui/parse-stdout.test.js.map +1 -0
  94. 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