Add new extensions and updates to the Dev sandbox
This commit is contained in:
parent
5f053ec81d
commit
19f3c6a050
5 changed files with 211 additions and 1 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -3,4 +3,4 @@ aws-config
|
|||
.pi/agent/auth.json
|
||||
.pi/agent/sessions/
|
||||
bin/pi
|
||||
|
||||
.claude/settings.local.json
|
||||
|
|
|
|||
|
|
@ -32,3 +32,10 @@ Additional notes:
|
|||
- If I send the outputs of failing commands, you should provide recommendations instead of addressing configuration/environment issues yourself.
|
||||
- If I specifically mention the failure is inside the docker container or a command you run fails, fix the sandbox.
|
||||
|
||||
## Scope
|
||||
|
||||
Limit your scope to the task at hand. Do touch unrelated code or attempt to fix existing issues.
|
||||
If you see something just raise it if it is confusing.
|
||||
Examples:
|
||||
|
||||
- prompt: Write a test for fun1() -> Only write the test for fun1(), don't fix any bugs/issues
|
||||
|
|
|
|||
40
.pi/agent/extensions/exit.ts
Normal file
40
.pi/agent/extensions/exit.ts
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
import type { ExtensionAPI, SessionEntry } from "@earendil-works/pi-coding-agent";
|
||||
|
||||
const QUICK_MARKER_TYPE = "quick-question-session";
|
||||
|
||||
type QuickSessionMarker = {
|
||||
returnSession: string;
|
||||
};
|
||||
|
||||
function getQuickSessionMarker(entries: SessionEntry[]): QuickSessionMarker | undefined {
|
||||
for (let i = entries.length - 1; i >= 0; i--) {
|
||||
const entry = entries[i];
|
||||
if (entry.type !== "custom" || entry.customType !== QUICK_MARKER_TYPE) continue;
|
||||
|
||||
const data = entry.data as Partial<QuickSessionMarker> | undefined;
|
||||
if (typeof data?.returnSession === "string") {
|
||||
return { returnSession: data.returnSession };
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export default function (pi: ExtensionAPI) {
|
||||
pi.on("input", async (event, ctx) => {
|
||||
if (event.source === "extension") return { action: "continue" };
|
||||
if (event.text.trim() !== "exit") return { action: "continue" };
|
||||
|
||||
const marker = getQuickSessionMarker(ctx.sessionManager.getBranch());
|
||||
if (marker) {
|
||||
(ctx.sessionManager as unknown as { setSessionFile(path: string): void }).setSessionFile(marker.returnSession);
|
||||
ctx.ui.setStatus("quick-question", undefined);
|
||||
ctx.ui.setWidget("quick-question", undefined);
|
||||
ctx.ui.notify("Returned from quick session.", "info");
|
||||
return { action: "handled" };
|
||||
}
|
||||
|
||||
ctx.shutdown();
|
||||
return { action: "handled" };
|
||||
});
|
||||
}
|
||||
143
.pi/agent/extensions/quick-question.ts
Normal file
143
.pi/agent/extensions/quick-question.ts
Normal file
|
|
@ -0,0 +1,143 @@
|
|||
import type { ExtensionAPI, ExtensionCommandContext, SessionEntry } from "@earendil-works/pi-coding-agent";
|
||||
|
||||
const MARKER_TYPE = "quick-question-session";
|
||||
|
||||
type QuickSessionMarker = {
|
||||
returnSession: string;
|
||||
createdAt: string;
|
||||
};
|
||||
|
||||
function getQuickSessionMarker(entries: SessionEntry[]): QuickSessionMarker | undefined {
|
||||
for (let i = entries.length - 1; i >= 0; i--) {
|
||||
const entry = entries[i];
|
||||
if (entry.type !== "custom" || entry.customType !== MARKER_TYPE) continue;
|
||||
|
||||
const data = entry.data as Partial<QuickSessionMarker> | undefined;
|
||||
if (typeof data?.returnSession === "string") {
|
||||
return {
|
||||
returnSession: data.returnSession,
|
||||
createdAt: typeof data.createdAt === "string" ? data.createdAt : "",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export default function (pi: ExtensionAPI) {
|
||||
pi.on("input", async (event, ctx) => {
|
||||
if (event.source === "extension") return { action: "continue" };
|
||||
|
||||
const aliases: Record<string, string> = {
|
||||
"/quick-close": "/quickclose",
|
||||
"/quick-done": "/quickdone",
|
||||
"/quick-back": "/quickback",
|
||||
};
|
||||
const replacement = aliases[event.text.trim()];
|
||||
if (!replacement) return { action: "continue" };
|
||||
|
||||
const marker = getQuickSessionMarker(ctx.sessionManager.getBranch());
|
||||
if (!marker) {
|
||||
ctx.ui.notify("This is not a quick question session.", "error");
|
||||
return { action: "handled" };
|
||||
}
|
||||
|
||||
(ctx.sessionManager as unknown as { setSessionFile(path: string): void }).setSessionFile(marker.returnSession);
|
||||
ctx.ui.setStatus("quick-question", undefined);
|
||||
ctx.ui.setWidget("quick-question", undefined);
|
||||
ctx.ui.notify("Returned from quick session.", "info");
|
||||
return { action: "handled" };
|
||||
});
|
||||
|
||||
pi.on("session_start", async (_event, ctx) => {
|
||||
const marker = getQuickSessionMarker(ctx.sessionManager.getBranch());
|
||||
|
||||
if (!marker) {
|
||||
ctx.ui.setStatus("quick-question", undefined);
|
||||
ctx.ui.setWidget("quick-question", undefined);
|
||||
return;
|
||||
}
|
||||
|
||||
ctx.ui.setStatus("quick-question", ctx.ui.theme.fg("accent", "quick"));
|
||||
ctx.ui.setWidget("quick-question", [
|
||||
ctx.ui.theme.fg(
|
||||
"dim",
|
||||
"Quick question session. Use /quickclose, /quickdone, or /quickback to return to the original session.",
|
||||
),
|
||||
]);
|
||||
});
|
||||
|
||||
pi.registerCommand("quick", {
|
||||
description: "Start a temporary one-off question session; pass a question to ask immediately",
|
||||
handler: async (args, ctx) => {
|
||||
if (!ctx.hasUI) {
|
||||
ctx.ui.notify("quick requires interactive mode", "error");
|
||||
return;
|
||||
}
|
||||
|
||||
await ctx.waitForIdle();
|
||||
|
||||
const returnSession = ctx.sessionManager.getSessionFile();
|
||||
if (!returnSession) {
|
||||
ctx.ui.notify("Current session is ephemeral; cannot return to it later.", "error");
|
||||
return;
|
||||
}
|
||||
|
||||
const question = args.trim();
|
||||
|
||||
const result = await ctx.newSession({
|
||||
parentSession: returnSession,
|
||||
setup: async (session) => {
|
||||
session.appendCustomEntry(MARKER_TYPE, {
|
||||
returnSession,
|
||||
createdAt: new Date().toISOString(),
|
||||
} satisfies QuickSessionMarker);
|
||||
session.appendSessionInfo("Quick question");
|
||||
},
|
||||
withSession: async (replacementCtx) => {
|
||||
replacementCtx.ui.notify(
|
||||
"Quick session started. Use /quickclose, /quickdone, or /quickback to return.",
|
||||
"info",
|
||||
);
|
||||
|
||||
if (question) {
|
||||
await replacementCtx.sendUserMessage(question);
|
||||
} else {
|
||||
replacementCtx.ui.setEditorText("");
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
if (result.cancelled) {
|
||||
ctx.ui.notify("Quick session cancelled", "info");
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
async function closeQuickSession(ctx: ExtensionCommandContext) {
|
||||
await ctx.waitForIdle();
|
||||
|
||||
const marker = getQuickSessionMarker(ctx.sessionManager.getBranch());
|
||||
if (!marker) {
|
||||
ctx.ui.notify("This is not a quick question session.", "error");
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await ctx.switchSession(marker.returnSession, {
|
||||
withSession: async (replacementCtx) => {
|
||||
replacementCtx.ui.notify("Returned from quick session.", "info");
|
||||
},
|
||||
});
|
||||
|
||||
if (result.cancelled) {
|
||||
ctx.ui.notify("Return to original session cancelled", "info");
|
||||
}
|
||||
}
|
||||
|
||||
for (const name of ["quickclose", "quickdone", "quickback", "quick-close", "quick-done", "quick-back"] as const) {
|
||||
pi.registerCommand(name, {
|
||||
description: "Close the quick question session and return to the original session",
|
||||
handler: async (_args, ctx) => closeQuickSession(ctx),
|
||||
});
|
||||
}
|
||||
}
|
||||
20
Dockerfile
20
Dockerfile
|
|
@ -6,6 +6,7 @@ ARG JAVA_VERSION=17
|
|||
ARG GO_VERSION=1.22.4
|
||||
ARG GRADLE_VERSION=8.7
|
||||
ARG TERRAFORM_VERSION=1.8.5
|
||||
ARG ZIG_VERSION=0.16.0
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
ENV LANG=C.UTF-8
|
||||
|
|
@ -38,6 +39,8 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
|||
ripgrep \
|
||||
neovim \
|
||||
less \
|
||||
hugo \
|
||||
xz-utils \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& ln -sf /usr/bin/fdfind /usr/local/bin/fd
|
||||
|
||||
|
|
@ -63,6 +66,10 @@ RUN curl -sSL https://install.python-poetry.org | python3 - && \
|
|||
# uv (fast Python package/project manager from Astral)
|
||||
RUN curl -LsSf https://astral.sh/uv/install.sh | env UV_INSTALL_DIR=/usr/local/bin sh
|
||||
|
||||
# Keep sandbox-created Python project virtualenvs separate from the host/user .venv.
|
||||
# This avoids invalid interpreter symlinks when the same workspace is shared with macOS.
|
||||
ENV UV_PROJECT_ENVIRONMENT=".venv_sandbox"
|
||||
|
||||
# Node.js 22
|
||||
RUN curl -fsSL https://deb.nodesource.com/setup_${NODE_MAJOR}.x | bash - && \
|
||||
apt-get install -y --no-install-recommends nodejs && \
|
||||
|
|
@ -112,6 +119,19 @@ RUN ARCH=$(dpkg --print-architecture) && \
|
|||
unzip /tmp/terraform.zip -d /usr/local/bin && \
|
||||
rm /tmp/terraform.zip
|
||||
|
||||
# Zig
|
||||
RUN ARCH=$(uname -m) && \
|
||||
case "$ARCH" in \
|
||||
x86_64) ZIG_ARCH=x86_64 ;; \
|
||||
aarch64) ZIG_ARCH=aarch64 ;; \
|
||||
*) echo "Unsupported architecture: $ARCH" >&2; exit 1 ;; \
|
||||
esac && \
|
||||
curl -fsSL "https://ziglang.org/download/${ZIG_VERSION}/zig-${ZIG_ARCH}-linux-${ZIG_VERSION}.tar.xz" -o /tmp/zig.tar.xz && \
|
||||
tar -C /opt -xf /tmp/zig.tar.xz && \
|
||||
mv "/opt/zig-${ZIG_ARCH}-linux-${ZIG_VERSION}" "/opt/zig-${ZIG_VERSION}" && \
|
||||
ln -s "/opt/zig-${ZIG_VERSION}/zig" /usr/local/bin/zig && \
|
||||
rm /tmp/zig.tar.xz
|
||||
|
||||
# Atlassian CLI (acli)
|
||||
RUN mkdir -p -m 755 /etc/apt/keyrings && \
|
||||
wget -nv -O- https://acli.atlassian.com/gpg/public-key.asc | gpg --dearmor -o /etc/apt/keyrings/acli-archive-keyring.gpg && \
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue