@crewspaceai/adapter-pi-local 2026.412.0-canary.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 (58) hide show
  1. package/LICENSE +21 -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 +99 -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 +8 -0
  11. package/dist/index.d.ts.map +1 -0
  12. package/dist/index.js +39 -0
  13. package/dist/index.js.map +1 -0
  14. package/dist/server/execute.d.ts +3 -0
  15. package/dist/server/execute.d.ts.map +1 -0
  16. package/dist/server/execute.js +407 -0
  17. package/dist/server/execute.js.map +1 -0
  18. package/dist/server/index.d.ts +8 -0
  19. package/dist/server/index.d.ts.map +1 -0
  20. package/dist/server/index.js +51 -0
  21. package/dist/server/index.js.map +1 -0
  22. package/dist/server/models.d.ts +20 -0
  23. package/dist/server/models.d.ts.map +1 -0
  24. package/dist/server/models.js +163 -0
  25. package/dist/server/models.js.map +1 -0
  26. package/dist/server/models.test.d.ts +2 -0
  27. package/dist/server/models.test.d.ts.map +1 -0
  28. package/dist/server/models.test.js +22 -0
  29. package/dist/server/models.test.js.map +1 -0
  30. package/dist/server/parse.d.ts +23 -0
  31. package/dist/server/parse.d.ts.map +1 -0
  32. package/dist/server/parse.js +180 -0
  33. package/dist/server/parse.js.map +1 -0
  34. package/dist/server/parse.test.d.ts +2 -0
  35. package/dist/server/parse.test.d.ts.map +1 -0
  36. package/dist/server/parse.test.js +206 -0
  37. package/dist/server/parse.test.js.map +1 -0
  38. package/dist/server/skills.d.ts +8 -0
  39. package/dist/server/skills.d.ts.map +1 -0
  40. package/dist/server/skills.js +69 -0
  41. package/dist/server/skills.js.map +1 -0
  42. package/dist/server/test.d.ts +3 -0
  43. package/dist/server/test.d.ts.map +1 -0
  44. package/dist/server/test.js +270 -0
  45. package/dist/server/test.js.map +1 -0
  46. package/dist/ui/build-config.d.ts +3 -0
  47. package/dist/ui/build-config.d.ts.map +1 -0
  48. package/dist/ui/build-config.js +82 -0
  49. package/dist/ui/build-config.js.map +1 -0
  50. package/dist/ui/index.d.ts +3 -0
  51. package/dist/ui/index.d.ts.map +1 -0
  52. package/dist/ui/index.js +3 -0
  53. package/dist/ui/index.js.map +1 -0
  54. package/dist/ui/parse-stdout.d.ts +4 -0
  55. package/dist/ui/parse-stdout.d.ts.map +1 -0
  56. package/dist/ui/parse-stdout.js +271 -0
  57. package/dist/ui/parse-stdout.js.map +1 -0
  58. package/package.json +54 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 CrewSpace AI
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,2 @@
1
+ export declare function printPiStreamEvent(raw: string, _debug: boolean): void;
2
+ //# sourceMappingURL=format-event.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"format-event.d.ts","sourceRoot":"","sources":["../../src/cli/format-event.ts"],"names":[],"mappings":"AA4BA,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,GAAG,IAAI,CA8ErE"}
@@ -0,0 +1,99 @@
1
+ import pc from "picocolors";
2
+ function safeJsonParse(text) {
3
+ try {
4
+ return JSON.parse(text);
5
+ }
6
+ catch {
7
+ return null;
8
+ }
9
+ }
10
+ function asRecord(value) {
11
+ if (typeof value !== "object" || value === null || Array.isArray(value))
12
+ return null;
13
+ return value;
14
+ }
15
+ function asString(value, fallback = "") {
16
+ return typeof value === "string" ? value : fallback;
17
+ }
18
+ function extractTextContent(content) {
19
+ if (typeof content === "string")
20
+ return content;
21
+ if (!Array.isArray(content))
22
+ return "";
23
+ return content
24
+ .filter((c) => c.type === "text" && c.text)
25
+ .map((c) => c.text)
26
+ .join("");
27
+ }
28
+ export function printPiStreamEvent(raw, _debug) {
29
+ const line = raw.trim();
30
+ if (!line)
31
+ return;
32
+ const parsed = asRecord(safeJsonParse(line));
33
+ if (!parsed) {
34
+ console.log(line);
35
+ return;
36
+ }
37
+ const type = asString(parsed.type);
38
+ if (type === "agent_start") {
39
+ console.log(pc.blue("Pi agent started"));
40
+ return;
41
+ }
42
+ if (type === "agent_end") {
43
+ console.log(pc.blue("Pi agent finished"));
44
+ return;
45
+ }
46
+ if (type === "turn_start") {
47
+ console.log(pc.blue("Turn started"));
48
+ return;
49
+ }
50
+ if (type === "turn_end") {
51
+ const message = asRecord(parsed.message);
52
+ if (message) {
53
+ const content = message.content;
54
+ const text = extractTextContent(content);
55
+ if (text) {
56
+ console.log(pc.green(`assistant: ${text}`));
57
+ }
58
+ }
59
+ return;
60
+ }
61
+ if (type === "message_update") {
62
+ const assistantEvent = asRecord(parsed.assistantMessageEvent);
63
+ if (assistantEvent) {
64
+ const msgType = asString(assistantEvent.type);
65
+ if (msgType === "text_delta") {
66
+ const delta = asString(assistantEvent.delta);
67
+ if (delta) {
68
+ console.log(pc.green(delta));
69
+ }
70
+ }
71
+ }
72
+ return;
73
+ }
74
+ if (type === "tool_execution_start") {
75
+ const toolName = asString(parsed.toolName);
76
+ const args = parsed.args;
77
+ console.log(pc.yellow(`tool_start: ${toolName}`));
78
+ if (args !== undefined) {
79
+ try {
80
+ console.log(pc.gray(JSON.stringify(args, null, 2)));
81
+ }
82
+ catch {
83
+ console.log(pc.gray(String(args)));
84
+ }
85
+ }
86
+ return;
87
+ }
88
+ if (type === "tool_execution_end") {
89
+ const result = parsed.result;
90
+ const isError = parsed.isError === true;
91
+ const output = typeof result === "string" ? result : JSON.stringify(result);
92
+ if (output) {
93
+ console.log((isError ? pc.red : pc.gray)(output));
94
+ }
95
+ return;
96
+ }
97
+ console.log(line);
98
+ }
99
+ //# sourceMappingURL=format-event.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"format-event.js","sourceRoot":"","sources":["../../src/cli/format-event.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export { printPiStreamEvent } from "./format-event.js";
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC"}
@@ -0,0 +1,2 @@
1
+ export { printPiStreamEvent } from "./format-event.js";
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC"}
@@ -0,0 +1,8 @@
1
+ export declare const type = "pi_local";
2
+ export declare const label = "Pi (local)";
3
+ export declare const models: Array<{
4
+ id: string;
5
+ label: string;
6
+ }>;
7
+ export declare const agentConfigurationDoc = "# pi_local agent configuration\n\nAdapter: pi_local\n\nUse when:\n- You want CrewSpace to run Pi (the AI coding agent) locally as the agent runtime\n- You want provider/model routing in Pi format (--provider <name> --model <id>)\n- You want Pi session resume across heartbeats via --session\n- You need Pi's tool set (read, bash, edit, write, grep, find, ls)\n\nDon't use when:\n- You need webhook-style external invocation (use openclaw_gateway or http)\n- You only need one-shot shell commands (use process)\n- Pi CLI is not installed on the machine\n\nCore fields:\n- cwd (string, optional): default absolute working directory fallback for the agent process (created if missing when possible)\n- instructionsFilePath (string, optional): absolute path to a markdown instructions file appended to system prompt via --append-system-prompt\n- promptTemplate (string, optional): user prompt template passed via -p flag\n- model (string, required): Pi model id in provider/model format (for example xai/grok-4)\n- thinking (string, optional): thinking level (off, minimal, low, medium, high, xhigh)\n- command (string, optional): defaults to \"pi\"\n- env (object, optional): KEY=VALUE environment variables\n\nOperational fields:\n- timeoutSec (number, optional): run timeout in seconds\n- graceSec (number, optional): SIGTERM grace period in seconds\n\nNotes:\n- Pi supports multiple providers and models. Use `pi --list-models` to list available options.\n- CrewSpace requires an explicit `model` value for `pi_local` agents.\n- Sessions are stored in ~/.pi/crewspaces/ and resumed with --session.\n- All tools (read, bash, edit, write, grep, find, ls) are enabled by default.\n- Agent instructions are appended to Pi's system prompt via --append-system-prompt, while the user task is sent via -p.\n";
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,IAAI,aAAa,CAAC;AAC/B,eAAO,MAAM,KAAK,eAAe,CAAC;AAElC,eAAO,MAAM,MAAM,EAAE,KAAK,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAM,CAAC;AAE/D,eAAO,MAAM,qBAAqB,4wDAkCjC,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,39 @@
1
+ export const type = "pi_local";
2
+ export const label = "Pi (local)";
3
+ export const models = [];
4
+ export const agentConfigurationDoc = `# pi_local agent configuration
5
+
6
+ Adapter: pi_local
7
+
8
+ Use when:
9
+ - You want CrewSpace to run Pi (the AI coding agent) locally as the agent runtime
10
+ - You want provider/model routing in Pi format (--provider <name> --model <id>)
11
+ - You want Pi session resume across heartbeats via --session
12
+ - You need Pi's tool set (read, bash, edit, write, grep, find, ls)
13
+
14
+ Don't use when:
15
+ - You need webhook-style external invocation (use openclaw_gateway or http)
16
+ - You only need one-shot shell commands (use process)
17
+ - Pi CLI is not installed on the machine
18
+
19
+ Core fields:
20
+ - cwd (string, optional): default absolute working directory fallback for the agent process (created if missing when possible)
21
+ - instructionsFilePath (string, optional): absolute path to a markdown instructions file appended to system prompt via --append-system-prompt
22
+ - promptTemplate (string, optional): user prompt template passed via -p flag
23
+ - model (string, required): Pi model id in provider/model format (for example xai/grok-4)
24
+ - thinking (string, optional): thinking level (off, minimal, low, medium, high, xhigh)
25
+ - command (string, optional): defaults to "pi"
26
+ - env (object, optional): KEY=VALUE environment variables
27
+
28
+ Operational fields:
29
+ - timeoutSec (number, optional): run timeout in seconds
30
+ - graceSec (number, optional): SIGTERM grace period in seconds
31
+
32
+ Notes:
33
+ - Pi supports multiple providers and models. Use \`pi --list-models\` to list available options.
34
+ - CrewSpace requires an explicit \`model\` value for \`pi_local\` agents.
35
+ - Sessions are stored in ~/.pi/crewspaces/ and resumed with --session.
36
+ - All tools (read, bash, edit, write, grep, find, ls) are enabled by default.
37
+ - Agent instructions are appended to Pi's system prompt via --append-system-prompt, while the user task is sent via -p.
38
+ `;
39
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,IAAI,GAAG,UAAU,CAAC;AAC/B,MAAM,CAAC,MAAM,KAAK,GAAG,YAAY,CAAC;AAElC,MAAM,CAAC,MAAM,MAAM,GAAyC,EAAE,CAAC;AAE/D,MAAM,CAAC,MAAM,qBAAqB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAkCpC,CAAC"}
@@ -0,0 +1,3 @@
1
+ import { type AdapterExecutionContext, type AdapterExecutionResult } from "@crewspaceai/adapter-utils";
2
+ export declare function execute(ctx: AdapterExecutionContext): Promise<AdapterExecutionResult>;
3
+ //# sourceMappingURL=execute.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"execute.d.ts","sourceRoot":"","sources":["../../src/server/execute.ts"],"names":[],"mappings":"AAIA,OAAO,EAA+B,KAAK,uBAAuB,EAAE,KAAK,sBAAsB,EAAE,MAAM,4BAA4B,CAAC;AAyGpI,wBAAsB,OAAO,CAAC,GAAG,EAAE,uBAAuB,GAAG,OAAO,CAAC,sBAAsB,CAAC,CAwY3F"}
@@ -0,0 +1,407 @@
1
+ import fs from "node:fs/promises";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import { inferOpenAiCompatibleBiller } from "@crewspaceai/adapter-utils";
6
+ import { asString, asNumber, asStringArray, parseObject, buildCrewSpaceEnv, joinPromptSections, buildInvocationEnvForLogs, ensureAbsoluteDirectory, ensureCommandResolvable, ensureCrewSpaceSkillSymlink, ensurePathInEnv, readCrewSpaceRuntimeSkillEntries, resolveCommandForLogs, resolveCrewSpaceDesiredSkillNames, removeMaintainerOnlySkillSymlinks, renderTemplate, runChildProcess, sanitizeCwd, } from "@crewspaceai/adapter-utils/server-utils";
7
+ import { isPiUnknownSessionError, parsePiJsonl } from "./parse.js";
8
+ import { ensurePiModelConfiguredAndAvailable } from "./models.js";
9
+ const __moduleDir = path.dirname(fileURLToPath(import.meta.url));
10
+ const CREWSPACE_SESSIONS_DIR = path.join(os.homedir(), ".pi", "crewspaces");
11
+ const PI_AGENT_SKILLS_DIR = path.join(os.homedir(), ".pi", "agent", "skills");
12
+ function firstNonEmptyLine(text) {
13
+ return (text
14
+ .split(/\r?\n/)
15
+ .map((line) => line.trim())
16
+ .find(Boolean) ?? "");
17
+ }
18
+ function parseModelProvider(model) {
19
+ if (!model)
20
+ return null;
21
+ const trimmed = model.trim();
22
+ if (!trimmed.includes("/"))
23
+ return null;
24
+ return trimmed.slice(0, trimmed.indexOf("/")).trim() || null;
25
+ }
26
+ function parseModelId(model) {
27
+ if (!model)
28
+ return null;
29
+ const trimmed = model.trim();
30
+ if (!trimmed.includes("/"))
31
+ return trimmed || null;
32
+ return trimmed.slice(trimmed.indexOf("/") + 1).trim() || null;
33
+ }
34
+ async function ensurePiSkillsInjected(onLog, skillsEntries, desiredSkillNames) {
35
+ const desiredSet = new Set(desiredSkillNames ?? skillsEntries.map((entry) => entry.key));
36
+ const selectedEntries = skillsEntries.filter((entry) => desiredSet.has(entry.key));
37
+ if (selectedEntries.length === 0)
38
+ return;
39
+ await fs.mkdir(PI_AGENT_SKILLS_DIR, { recursive: true });
40
+ const removedSkills = await removeMaintainerOnlySkillSymlinks(PI_AGENT_SKILLS_DIR, selectedEntries.map((entry) => entry.runtimeName));
41
+ for (const skillName of removedSkills) {
42
+ await onLog("stderr", `[crewspace] Removed maintainer-only Pi skill "${skillName}" from ${PI_AGENT_SKILLS_DIR}\n`);
43
+ }
44
+ for (const entry of selectedEntries) {
45
+ const target = path.join(PI_AGENT_SKILLS_DIR, entry.runtimeName);
46
+ try {
47
+ const result = await ensureCrewSpaceSkillSymlink(entry.source, target);
48
+ if (result === "skipped")
49
+ continue;
50
+ await onLog("stderr", `[crewspace] ${result === "repaired" ? "Repaired" : "Injected"} Pi skill "${entry.runtimeName}" into ${PI_AGENT_SKILLS_DIR}\n`);
51
+ }
52
+ catch (err) {
53
+ await onLog("stderr", `[crewspace] Failed to inject Pi skill "${entry.runtimeName}" into ${PI_AGENT_SKILLS_DIR}: ${err instanceof Error ? err.message : String(err)}\n`);
54
+ }
55
+ }
56
+ }
57
+ function resolvePiBiller(env, provider) {
58
+ return inferOpenAiCompatibleBiller(env, null) ?? provider ?? "unknown";
59
+ }
60
+ async function ensureSessionsDir() {
61
+ await fs.mkdir(CREWSPACE_SESSIONS_DIR, { recursive: true });
62
+ return CREWSPACE_SESSIONS_DIR;
63
+ }
64
+ function buildSessionPath(agentId, timestamp) {
65
+ const safeTimestamp = timestamp.replace(/[:.]/g, "-");
66
+ return path.join(CREWSPACE_SESSIONS_DIR, `${safeTimestamp}-${agentId}.jsonl`);
67
+ }
68
+ export async function execute(ctx) {
69
+ const { runId, agent, runtime, config, context, onLog, onMeta, onSpawn, authToken } = ctx;
70
+ const promptTemplate = asString(config.promptTemplate, "You are agent {{agent.id}} ({{agent.name}}). Continue your CrewSpace work.");
71
+ const command = asString(config.command, "pi");
72
+ const model = asString(config.model, "").trim();
73
+ const thinking = asString(config.thinking, "").trim();
74
+ // Parse model into provider and model id
75
+ const provider = parseModelProvider(model);
76
+ const modelId = parseModelId(model);
77
+ const workspaceContext = parseObject(context.crewspaceWorkspace);
78
+ const workspaceCwd = asString(workspaceContext.cwd, "");
79
+ const workspaceSource = asString(workspaceContext.source, "");
80
+ const workspaceId = asString(workspaceContext.workspaceId, "");
81
+ const workspaceRepoUrl = asString(workspaceContext.repoUrl, "");
82
+ const workspaceRepoRef = asString(workspaceContext.repoRef, "");
83
+ const agentHome = asString(workspaceContext.agentHome, "");
84
+ const workspaceHints = Array.isArray(context.crewspaceWorkspaces)
85
+ ? context.crewspaceWorkspaces.filter((value) => typeof value === "object" && value !== null)
86
+ : [];
87
+ const configuredCwd = asString(config.cwd, "");
88
+ const useConfiguredInsteadOfAgentHome = workspaceSource === "agent_home" && configuredCwd.length > 0;
89
+ const effectiveWorkspaceCwd = useConfiguredInsteadOfAgentHome ? "" : workspaceCwd;
90
+ const cwd = sanitizeCwd(effectiveWorkspaceCwd || configuredCwd || process.cwd());
91
+ await ensureAbsoluteDirectory(cwd, { createIfMissing: true });
92
+ // Ensure sessions directory exists
93
+ await ensureSessionsDir();
94
+ // Inject skills
95
+ const piSkillEntries = await readCrewSpaceRuntimeSkillEntries(config, __moduleDir);
96
+ const desiredPiSkillNames = resolveCrewSpaceDesiredSkillNames(config, piSkillEntries);
97
+ await ensurePiSkillsInjected(onLog, piSkillEntries, desiredPiSkillNames);
98
+ // Build environment
99
+ const envConfig = parseObject(config.env);
100
+ const hasExplicitApiKey = typeof envConfig.CREWSPACE_API_KEY === "string" && envConfig.CREWSPACE_API_KEY.trim().length > 0;
101
+ const env = { ...buildCrewSpaceEnv(agent) };
102
+ env.CREWSPACE_RUN_ID = runId;
103
+ const wakeTaskId = (typeof context.taskId === "string" && context.taskId.trim().length > 0 && context.taskId.trim()) ||
104
+ (typeof context.issueId === "string" && context.issueId.trim().length > 0 && context.issueId.trim()) ||
105
+ null;
106
+ const wakeReason = typeof context.wakeReason === "string" && context.wakeReason.trim().length > 0
107
+ ? context.wakeReason.trim()
108
+ : null;
109
+ const wakeCommentId = (typeof context.wakeCommentId === "string" && context.wakeCommentId.trim().length > 0 && context.wakeCommentId.trim()) ||
110
+ (typeof context.commentId === "string" && context.commentId.trim().length > 0 && context.commentId.trim()) ||
111
+ null;
112
+ const approvalId = typeof context.approvalId === "string" && context.approvalId.trim().length > 0
113
+ ? context.approvalId.trim()
114
+ : null;
115
+ const approvalStatus = typeof context.approvalStatus === "string" && context.approvalStatus.trim().length > 0
116
+ ? context.approvalStatus.trim()
117
+ : null;
118
+ const linkedIssueIds = Array.isArray(context.issueIds)
119
+ ? context.issueIds.filter((value) => typeof value === "string" && value.trim().length > 0)
120
+ : [];
121
+ if (wakeTaskId)
122
+ env.CREWSPACE_TASK_ID = wakeTaskId;
123
+ if (wakeReason)
124
+ env.CREWSPACE_WAKE_REASON = wakeReason;
125
+ if (wakeCommentId)
126
+ env.CREWSPACE_WAKE_COMMENT_ID = wakeCommentId;
127
+ if (approvalId)
128
+ env.CREWSPACE_APPROVAL_ID = approvalId;
129
+ if (approvalStatus)
130
+ env.CREWSPACE_APPROVAL_STATUS = approvalStatus;
131
+ if (linkedIssueIds.length > 0)
132
+ env.CREWSPACE_LINKED_ISSUE_IDS = linkedIssueIds.join(",");
133
+ if (workspaceCwd)
134
+ env.CREWSPACE_WORKSPACE_CWD = workspaceCwd;
135
+ if (workspaceSource)
136
+ env.CREWSPACE_WORKSPACE_SOURCE = workspaceSource;
137
+ if (workspaceId)
138
+ env.CREWSPACE_WORKSPACE_ID = workspaceId;
139
+ if (workspaceRepoUrl)
140
+ env.CREWSPACE_WORKSPACE_REPO_URL = workspaceRepoUrl;
141
+ if (workspaceRepoRef)
142
+ env.CREWSPACE_WORKSPACE_REPO_REF = workspaceRepoRef;
143
+ if (agentHome)
144
+ env.AGENT_HOME = agentHome;
145
+ if (workspaceHints.length > 0)
146
+ env.CREWSPACE_WORKSPACES_JSON = JSON.stringify(workspaceHints);
147
+ for (const [key, value] of Object.entries(envConfig)) {
148
+ if (typeof value === "string")
149
+ env[key] = value;
150
+ }
151
+ if (!hasExplicitApiKey && authToken) {
152
+ env.CREWSPACE_API_KEY = authToken;
153
+ }
154
+ const runtimeEnv = Object.fromEntries(Object.entries(ensurePathInEnv({ ...process.env, ...env })).filter((entry) => typeof entry[1] === "string"));
155
+ await ensureCommandResolvable(command, cwd, runtimeEnv);
156
+ const resolvedCommand = await resolveCommandForLogs(command, cwd, runtimeEnv);
157
+ const loggedEnv = buildInvocationEnvForLogs(env, {
158
+ runtimeEnv,
159
+ includeRuntimeKeys: ["HOME"],
160
+ resolvedCommand,
161
+ });
162
+ // Validate model is available before execution
163
+ await ensurePiModelConfiguredAndAvailable({
164
+ model,
165
+ command,
166
+ cwd,
167
+ env: runtimeEnv,
168
+ });
169
+ const timeoutSec = asNumber(config.timeoutSec, 0);
170
+ const graceSec = asNumber(config.graceSec, 20);
171
+ const extraArgs = (() => {
172
+ const fromExtraArgs = asStringArray(config.extraArgs);
173
+ if (fromExtraArgs.length > 0)
174
+ return fromExtraArgs;
175
+ return asStringArray(config.args);
176
+ })();
177
+ // Handle session
178
+ const runtimeSessionParams = parseObject(runtime.sessionParams);
179
+ const runtimeSessionId = asString(runtimeSessionParams.sessionId, runtime.sessionId ?? "");
180
+ const runtimeSessionCwd = asString(runtimeSessionParams.cwd, "");
181
+ const canResumeSession = runtimeSessionId.length > 0 &&
182
+ (runtimeSessionCwd.length === 0 || path.resolve(runtimeSessionCwd) === path.resolve(cwd));
183
+ const sessionPath = canResumeSession ? runtimeSessionId : buildSessionPath(agent.id, new Date().toISOString());
184
+ if (runtimeSessionId && !canResumeSession) {
185
+ await onLog("stdout", `[crewspace] Pi session "${runtimeSessionId}" was saved for cwd "${runtimeSessionCwd}" and will not be resumed in "${cwd}".\n`);
186
+ }
187
+ // Ensure session file exists (Pi requires this on first run)
188
+ if (!canResumeSession) {
189
+ try {
190
+ await fs.writeFile(sessionPath, "", { flag: "wx" });
191
+ }
192
+ catch (err) {
193
+ // File may already exist, that's ok
194
+ if (err.code !== "EEXIST") {
195
+ throw err;
196
+ }
197
+ }
198
+ }
199
+ // Handle instructions file and build system prompt extension
200
+ const instructionsFilePath = asString(config.instructionsFilePath, "").trim();
201
+ const resolvedInstructionsFilePath = instructionsFilePath
202
+ ? path.resolve(cwd, instructionsFilePath)
203
+ : "";
204
+ const instructionsFileDir = instructionsFilePath ? `${path.dirname(instructionsFilePath)}/` : "";
205
+ let systemPromptExtension = "";
206
+ let instructionsReadFailed = false;
207
+ if (resolvedInstructionsFilePath) {
208
+ try {
209
+ const instructionsContents = await fs.readFile(resolvedInstructionsFilePath, "utf8");
210
+ systemPromptExtension =
211
+ `${instructionsContents}\n\n` +
212
+ `The above agent instructions were loaded from ${resolvedInstructionsFilePath}. ` +
213
+ `Resolve any relative file references from ${instructionsFileDir}.\n\n` +
214
+ `You are agent {{agent.id}} ({{agent.name}}). Continue your CrewSpace work.`;
215
+ }
216
+ catch (err) {
217
+ instructionsReadFailed = true;
218
+ const reason = err instanceof Error ? err.message : String(err);
219
+ await onLog("stdout", `[crewspace] Warning: could not read agent instructions file "${resolvedInstructionsFilePath}": ${reason}\n`);
220
+ // Fall back to base prompt template
221
+ systemPromptExtension = promptTemplate;
222
+ }
223
+ }
224
+ else {
225
+ systemPromptExtension = promptTemplate;
226
+ }
227
+ const bootstrapPromptTemplate = asString(config.bootstrapPromptTemplate, "");
228
+ const templateData = {
229
+ agentId: agent.id,
230
+ companyId: agent.companyId,
231
+ runId,
232
+ company: { id: agent.companyId },
233
+ agent,
234
+ run: { id: runId, source: "on_demand" },
235
+ context,
236
+ };
237
+ const renderedSystemPromptExtension = renderTemplate(systemPromptExtension, templateData);
238
+ const renderedHeartbeatPrompt = renderTemplate(promptTemplate, templateData);
239
+ const renderedBootstrapPrompt = !canResumeSession && bootstrapPromptTemplate.trim().length > 0
240
+ ? renderTemplate(bootstrapPromptTemplate, templateData).trim()
241
+ : "";
242
+ const sessionHandoffNote = asString(context.crewspaceSessionHandoffMarkdown, "").trim();
243
+ const userPrompt = joinPromptSections([
244
+ renderedBootstrapPrompt,
245
+ sessionHandoffNote,
246
+ renderedHeartbeatPrompt,
247
+ ]);
248
+ const promptMetrics = {
249
+ systemPromptChars: renderedSystemPromptExtension.length,
250
+ promptChars: userPrompt.length,
251
+ bootstrapPromptChars: renderedBootstrapPrompt.length,
252
+ sessionHandoffChars: sessionHandoffNote.length,
253
+ heartbeatPromptChars: renderedHeartbeatPrompt.length,
254
+ };
255
+ const commandNotes = (() => {
256
+ if (!resolvedInstructionsFilePath)
257
+ return [];
258
+ if (instructionsReadFailed) {
259
+ return [
260
+ `Configured instructionsFilePath ${resolvedInstructionsFilePath}, but file could not be read; continuing without injected instructions.`,
261
+ ];
262
+ }
263
+ return [
264
+ `Loaded agent instructions from ${resolvedInstructionsFilePath}`,
265
+ `Appended instructions + path directive to system prompt (relative references from ${instructionsFileDir}).`,
266
+ ];
267
+ })();
268
+ const buildArgs = (sessionFile) => {
269
+ const args = [];
270
+ // Use JSON mode for structured output with print mode (non-interactive)
271
+ args.push("--mode", "json");
272
+ args.push("-p"); // Non-interactive mode: process prompt and exit
273
+ // Use --append-system-prompt to extend Pi's default system prompt
274
+ args.push("--append-system-prompt", renderedSystemPromptExtension);
275
+ if (provider)
276
+ args.push("--provider", provider);
277
+ if (modelId)
278
+ args.push("--model", modelId);
279
+ if (thinking)
280
+ args.push("--thinking", thinking);
281
+ args.push("--tools", "read,bash,edit,write,grep,find,ls");
282
+ args.push("--session", sessionFile);
283
+ // Add CrewSpace skills directory so Pi can load the crewspace skill
284
+ args.push("--skill", PI_AGENT_SKILLS_DIR);
285
+ if (extraArgs.length > 0)
286
+ args.push(...extraArgs);
287
+ // Add the user prompt as the last argument
288
+ args.push(userPrompt);
289
+ return args;
290
+ };
291
+ const runAttempt = async (sessionFile) => {
292
+ const args = buildArgs(sessionFile);
293
+ if (onMeta) {
294
+ await onMeta({
295
+ adapterType: "pi_local",
296
+ command: resolvedCommand,
297
+ cwd,
298
+ commandNotes,
299
+ commandArgs: args,
300
+ env: loggedEnv,
301
+ prompt: userPrompt,
302
+ promptMetrics,
303
+ context,
304
+ });
305
+ }
306
+ // Buffer stdout by lines to handle partial JSON chunks
307
+ let stdoutBuffer = "";
308
+ const bufferedOnLog = async (stream, chunk) => {
309
+ if (stream === "stderr") {
310
+ // Pass stderr through immediately (not JSONL)
311
+ await onLog(stream, chunk);
312
+ return;
313
+ }
314
+ // Buffer stdout and emit only complete lines
315
+ stdoutBuffer += chunk;
316
+ const lines = stdoutBuffer.split("\n");
317
+ // Keep the last (potentially incomplete) line in the buffer
318
+ stdoutBuffer = lines.pop() || "";
319
+ // Emit complete lines
320
+ for (const line of lines) {
321
+ if (line) {
322
+ await onLog(stream, line + "\n");
323
+ }
324
+ }
325
+ };
326
+ const proc = await runChildProcess(runId, command, args, {
327
+ cwd,
328
+ env: runtimeEnv,
329
+ timeoutSec,
330
+ graceSec,
331
+ onSpawn,
332
+ onLog: bufferedOnLog,
333
+ });
334
+ // Flush any remaining buffer content
335
+ if (stdoutBuffer) {
336
+ await onLog("stdout", stdoutBuffer);
337
+ }
338
+ return {
339
+ proc,
340
+ rawStderr: proc.stderr,
341
+ parsed: parsePiJsonl(proc.stdout),
342
+ };
343
+ };
344
+ const toResult = (attempt, clearSessionOnMissingSession = false) => {
345
+ if (attempt.proc.timedOut) {
346
+ return {
347
+ exitCode: attempt.proc.exitCode,
348
+ signal: attempt.proc.signal,
349
+ timedOut: true,
350
+ errorMessage: `Timed out after ${timeoutSec}s`,
351
+ clearSession: clearSessionOnMissingSession,
352
+ };
353
+ }
354
+ const resolvedSessionId = clearSessionOnMissingSession ? null : sessionPath;
355
+ const resolvedSessionParams = resolvedSessionId
356
+ ? { sessionId: resolvedSessionId, cwd }
357
+ : null;
358
+ const stderrLine = firstNonEmptyLine(attempt.proc.stderr);
359
+ const rawExitCode = attempt.proc.exitCode;
360
+ const fallbackErrorMessage = stderrLine || `Pi exited with code ${rawExitCode ?? -1}`;
361
+ return {
362
+ exitCode: rawExitCode,
363
+ signal: attempt.proc.signal,
364
+ timedOut: false,
365
+ errorMessage: (rawExitCode ?? 0) === 0 ? null : fallbackErrorMessage,
366
+ usage: {
367
+ inputTokens: attempt.parsed.usage.inputTokens,
368
+ outputTokens: attempt.parsed.usage.outputTokens,
369
+ cachedInputTokens: attempt.parsed.usage.cachedInputTokens,
370
+ },
371
+ sessionId: resolvedSessionId,
372
+ sessionParams: resolvedSessionParams,
373
+ sessionDisplayId: resolvedSessionId,
374
+ provider: provider,
375
+ biller: resolvePiBiller(runtimeEnv, provider),
376
+ model: model,
377
+ billingType: "unknown",
378
+ costUsd: attempt.parsed.usage.costUsd,
379
+ resultJson: {
380
+ stdout: attempt.proc.stdout,
381
+ stderr: attempt.proc.stderr,
382
+ },
383
+ summary: attempt.parsed.finalMessage ?? attempt.parsed.messages.join("\n\n").trim(),
384
+ clearSession: Boolean(clearSessionOnMissingSession),
385
+ };
386
+ };
387
+ const initial = await runAttempt(sessionPath);
388
+ const initialFailed = !initial.proc.timedOut && ((initial.proc.exitCode ?? 0) !== 0 || initial.parsed.errors.length > 0);
389
+ if (canResumeSession &&
390
+ initialFailed &&
391
+ isPiUnknownSessionError(initial.proc.stdout, initial.rawStderr)) {
392
+ await onLog("stdout", `[crewspace] Pi session "${runtimeSessionId}" is unavailable; retrying with a fresh session.\n`);
393
+ const newSessionPath = buildSessionPath(agent.id, new Date().toISOString());
394
+ try {
395
+ await fs.writeFile(newSessionPath, "", { flag: "wx" });
396
+ }
397
+ catch (err) {
398
+ if (err.code !== "EEXIST") {
399
+ throw err;
400
+ }
401
+ }
402
+ const retry = await runAttempt(newSessionPath);
403
+ return toResult(retry, true);
404
+ }
405
+ return toResult(initial);
406
+ }
407
+ //# sourceMappingURL=execute.js.map