Compare commits

..

15 Commits

Author SHA1 Message Date
Devin Foley
035cb8aec2 Merge pull request #737 from alaa-alghazouli/fix/embedded-postgres-initdbflags-types
Some checks failed
Refresh Lockfile / refresh (push) Has been cancelled
Release / verify_canary (push) Has been cancelled
Release / publish_canary (push) Has been cancelled
Release / verify_stable (push) Has been cancelled
Release / preview_stable (push) Has been cancelled
Release / publish_stable (push) Has been cancelled
fix: add initdbFlags to embedded postgres constructor typings
2026-03-19 12:21:27 -07:00
Dotta
b1c4b2e420 Merge pull request #743 from Sigmabrogz/fix/openclaw-gateway-unhandled-rejection
fix(openclaw-gateway): handle challengePromise rejection to prevent crash
2026-03-19 09:12:20 -05:00
Dotta
1d1511e37c Merge pull request #1129 from AOrobator/ao/link-ticket-refs-skill
Clarify linked ticket references in Paperclip skill
2026-03-19 09:11:33 -05:00
Dotta
fff0600b1d Merge pull request #1250 from richardanaya/master
fix(pi-local): fix not using skills, fix poor parsing of pi output, fix pi-local not showing up in pi-config section
2026-03-19 07:46:02 -05:00
Richard Anaya
1cd61601f3 fix: handle direct array format for Pi tool results
Pi sometimes sends tool results as a direct array [{"type":"text","text":"..."}]
rather than wrapped in {"content": [...]}. Now handles both formats to
properly extract text content instead of showing raw JSON.
2026-03-18 21:11:03 -07:00
Richard Anaya
6eb9545a72 fix: extract text content from Pi's tool result content arrays
Pi returns tool results in format: {"content": [{"type": "text", "text": "..."}]}
Previously we were JSON.stringify-ing the whole object, showing as:
  {"content":[{"type":"text","text":"..."}]}
Now we extract the actual text content for cleaner display.
2026-03-18 21:02:53 -07:00
Richard Anaya
47a6d86174 fix: include toolName in tool_result entries from turn_end toolResults
The turn_end event includes toolResults array with toolName. Previously
the parsing only included toolCallId, now we also extract toolName so
the UI can display the correct tool name even when tool_result entries
arrive without a preceding tool_call.
2026-03-18 20:57:32 -07:00
Richard Anaya
aa854e7efe fix: include toolName in tool_result transcript entries for Pi adapter
When tool_result entries arrive without a matching tool_call, the transcript
was showing generic 'tool' as the name. Now pl-local parses toolName from
tool_execution_end events and passes it through, so the UI can display
the actual tool name (e.g., 'bash', 'Read', 'Ls') instead of 'tool'.
2026-03-18 20:51:59 -07:00
Richard Anaya
5536e6b91e fix(pi-local): ensure skills directory exists before passing --skill flag
- Hoist PI_AGENT_SKILLS_DIR to module-level constant to avoid duplicate path computation
- Always create ~/.pi/agent/skills directory before early return in ensurePiSkillsInjected, so the path is valid when --skill flag is passed
2026-03-18 20:40:55 -07:00
Richard Anaya
f37e0aa7b3 fix(pi-local): pass --skill flag for paperclip skills and enable pi_local in adapter dropdown
- Add --skill ~/.pi/agent/skills to pi CLI invocation so it loads the paperclip skill
- Enable pi_local in the AgentConfigForm adapter type dropdown (was showing as coming soon)
2026-03-18 20:34:08 -07:00
Andrew Orobator
c539fcde8b Fix stale Paperclip issue link example
Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-17 09:52:32 -04:00
Andrew Orobator
7a08fbd370 Reduce duplicate ticket-link guidance
Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-17 09:43:47 -04:00
Andrew Orobator
71e1bc260d Clarify linked ticket references in Paperclip skill
Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-17 09:35:57 -04:00
Sigmabrogz
3d2abbde72 fix(openclaw-gateway): catch challengePromise rejection to prevent unhandled rejection process crash
Resolves #727

Signed-off-by: Sigmabrogz <bnb1000bnb@gmail.com>
2026-03-13 00:42:28 +00:00
Alaa Alghazouli
ff02220890 fix: add initdbFlags to embedded postgres ctor types 2026-03-12 23:03:44 +01:00
7 changed files with 61 additions and 14 deletions

View File

@@ -246,7 +246,7 @@ export type TranscriptEntry =
| { kind: "thinking"; ts: string; text: string; delta?: boolean }
| { kind: "user"; ts: string; text: string }
| { kind: "tool_call"; ts: string; name: string; input: unknown; toolUseId?: string }
| { kind: "tool_result"; ts: string; toolUseId: string; content: string; isError: boolean }
| { kind: "tool_result"; ts: string; toolUseId: string; toolName?: string; content: string; isError: boolean }
| { kind: "init"; ts: string; model: string; sessionId: string }
| { kind: "result"; ts: string; text: string; inputTokens: number; outputTokens: number; cachedTokens: number; costUsd: number; subtype: string; isError: boolean; errors: string[] }
| { kind: "stderr"; ts: string; text: string }

View File

@@ -605,6 +605,7 @@ class GatewayWsClient {
this.resolveChallenge = resolve;
this.rejectChallenge = reject;
});
this.challengePromise.catch(() => {});
}
async connect(

View File

@@ -26,6 +26,7 @@ import { ensurePiModelConfiguredAndAvailable } from "./models.js";
const __moduleDir = path.dirname(fileURLToPath(import.meta.url));
const PAPERCLIP_SESSIONS_DIR = path.join(os.homedir(), ".pi", "paperclips");
const PI_AGENT_SKILLS_DIR = path.join(os.homedir(), ".pi", "agent", "skills");
function firstNonEmptyLine(text: string): string {
return (
@@ -56,35 +57,35 @@ function resolvePiBiller(env: Record<string, string>, provider: string | null):
async function ensurePiSkillsInjected(onLog: AdapterExecutionContext["onLog"]) {
const skillsEntries = await listPaperclipSkillEntries(__moduleDir);
await fs.mkdir(PI_AGENT_SKILLS_DIR, { recursive: true });
if (skillsEntries.length === 0) return;
const piSkillsHome = path.join(os.homedir(), ".pi", "agent", "skills");
await fs.mkdir(piSkillsHome, { recursive: true });
const removedSkills = await removeMaintainerOnlySkillSymlinks(
piSkillsHome,
PI_AGENT_SKILLS_DIR,
skillsEntries.map((entry) => entry.name),
);
for (const skillName of removedSkills) {
await onLog(
"stderr",
`[paperclip] Removed maintainer-only Pi skill "${skillName}" from ${piSkillsHome}\n`,
`[paperclip] Removed maintainer-only Pi skill "${skillName}" from ${PI_AGENT_SKILLS_DIR}\n`,
);
}
for (const entry of skillsEntries) {
const target = path.join(piSkillsHome, entry.name);
const target = path.join(PI_AGENT_SKILLS_DIR, entry.name);
try {
const result = await ensurePaperclipSkillSymlink(entry.source, target);
if (result === "skipped") continue;
await onLog(
"stderr",
`[paperclip] ${result === "repaired" ? "Repaired" : "Injected"} Pi skill "${entry.name}" into ${piSkillsHome}\n`,
`[paperclip] ${result === "repaired" ? "Repaired" : "Injected"} Pi skill "${entry.name}" into ${PI_AGENT_SKILLS_DIR}\n`,
);
} catch (err) {
await onLog(
"stderr",
`[paperclip] Failed to inject Pi skill "${entry.name}" into ${piSkillsHome}: ${err instanceof Error ? err.message : String(err)}\n`,
`[paperclip] Failed to inject Pi skill "${entry.name}" into ${PI_AGENT_SKILLS_DIR}: ${err instanceof Error ? err.message : String(err)}\n`,
);
}
}
@@ -336,6 +337,9 @@ export async function execute(ctx: AdapterExecutionContext): Promise<AdapterExec
args.push("--tools", "read,bash,edit,write,grep,find,ls");
args.push("--session", sessionFile);
// Add Paperclip skills directory so Pi can load the paperclip skill
args.push("--skill", PI_AGENT_SKILLS_DIR);
if (extraArgs.length > 0) args.push(...extraArgs);
return args;

View File

@@ -72,11 +72,22 @@ export function parsePiStdoutLine(line: string, ts: string): TranscriptEntry[] {
for (const tr of toolResults) {
const content = tr.content;
const isError = tr.isError === true;
const contentStr = typeof content === "string" ? content : JSON.stringify(content);
// Extract text from Pi's content array format
let contentStr: string;
if (typeof content === "string") {
contentStr = content;
} else if (Array.isArray(content)) {
contentStr = extractTextContent(content as Array<{ type: string; text?: string }>);
} else {
contentStr = JSON.stringify(content);
}
entries.push({
kind: "tool_result",
ts,
toolUseId: asString(tr.toolCallId, "unknown"),
toolName: asString(tr.toolName),
content: contentStr,
isError,
});
@@ -130,14 +141,35 @@ export function parsePiStdoutLine(line: string, ts: string): TranscriptEntry[] {
if (type === "tool_execution_end") {
const toolCallId = asString(parsed.toolCallId);
const toolName = asString(parsed.toolName);
const result = parsed.result;
const isError = parsed.isError === true;
const contentStr = typeof result === "string" ? result : JSON.stringify(result);
// Extract text from Pi's content array format
// Can be: {"content": [{"type": "text", "text": "..."}]} or [{"type": "text", "text": "..."}]
let contentStr: string;
if (typeof result === "string") {
contentStr = result;
} else if (Array.isArray(result)) {
// Direct array format: result is [{"type": "text", "text": "..."}]
contentStr = extractTextContent(result as Array<{ type: string; text?: string }>);
} else if (result && typeof result === "object") {
const resultObj = result as Record<string, unknown>;
if (Array.isArray(resultObj.content)) {
// Wrapped format: result is {"content": [{"type": "text", "text": "..."}]}
contentStr = extractTextContent(resultObj.content as Array<{ type: string; text?: string }>);
} else {
contentStr = JSON.stringify(result);
}
} else {
contentStr = JSON.stringify(result);
}
return [{
kind: "tool_result",
ts,
toolUseId: toolCallId || "unknown",
toolName,
content: contentStr,
isError,
}];

View File

@@ -71,6 +71,8 @@ Read enough ancestor/comment context to understand _why_ the task exists and wha
**Step 8 — Update status and communicate.** Always include the run ID header.
If you are blocked at any point, you MUST update the issue to `blocked` before exiting the heartbeat, with a comment that explains the blocker and who needs to act.
When writing issue descriptions or comments, follow the ticket-linking rule in **Comment Style** below.
```json
PATCH /api/issues/{issueId}
Headers: X-Paperclip-Run-Id: $PAPERCLIP_RUN_ID
@@ -144,12 +146,19 @@ Access control:
## Comment Style (Required)
When posting issue comments, use concise markdown with:
When posting issue comments or writing issue descriptions, use concise markdown with:
- a short status line
- bullets for what changed / what is blocked
- links to related entities when available
**Ticket references are links (required):** If you mention another issue identifier such as `PAP-224`, `ZED-24`, or any `{PREFIX}-{NUMBER}` ticket id inside a comment body or issue description, wrap it in a Markdown link:
- `[PAP-224](/PAP/issues/PAP-224)`
- `[ZED-24](/ZED/issues/ZED-24)`
Never leave bare ticket ids in issue descriptions or comments when a clickable internal link can be provided.
**Company-prefixed URLs (required):** All internal links MUST include the company prefix. Derive the prefix from any issue identifier you have (e.g., `PAP-315` → prefix is `PAP`). Use this prefix in all UI links:
- Issues: `/<prefix>/issues/<issue-identifier>` (e.g., `/PAP/issues/PAP-224`)
@@ -171,7 +180,8 @@ Submitted CTO hire request and linked it for board review.
- Approval: [ca6ba09d](/PAP/approvals/ca6ba09d-b558-4a53-a552-e7ef87e54a1b)
- Pending agent: [CTO draft](/PAP/agents/cto)
- Source issue: [PC-142](/PAP/issues/PC-142)
- Source issue: [PAP-142](/PAP/issues/PAP-142)
- Depends on: [PAP-224](/PAP/issues/PAP-224)
```
## Planning (Required when planning requested)

View File

@@ -939,7 +939,7 @@ function AdapterEnvironmentResult({ result }: { result: AdapterEnvironmentTestRe
/* ---- Internal sub-components ---- */
const ENABLED_ADAPTER_TYPES = new Set(["claude_local", "codex_local", "gemini_local", "opencode_local", "cursor"]);
const ENABLED_ADAPTER_TYPES = new Set(["claude_local", "codex_local", "gemini_local", "opencode_local", "pi_local", "cursor"]);
/** Display list includes all real adapter types plus UI-only coming-soon entries. */
const ADAPTER_DISPLAY_LIST: { value: string; label: string; comingSoon: boolean }[] = [

View File

@@ -400,7 +400,7 @@ export function normalizeTranscript(entries: TranscriptEntry[], streaming: boole
type: "tool",
ts: entry.ts,
endTs: entry.ts,
name: "tool",
name: entry.toolName ?? "tool",
toolUseId: entry.toolUseId,
input: null,
result: entry.content,