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/auth.json
|
||||||
.pi/agent/sessions/
|
.pi/agent/sessions/
|
||||||
bin/pi
|
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 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.
|
- 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 GO_VERSION=1.22.4
|
||||||
ARG GRADLE_VERSION=8.7
|
ARG GRADLE_VERSION=8.7
|
||||||
ARG TERRAFORM_VERSION=1.8.5
|
ARG TERRAFORM_VERSION=1.8.5
|
||||||
|
ARG ZIG_VERSION=0.16.0
|
||||||
|
|
||||||
ENV DEBIAN_FRONTEND=noninteractive
|
ENV DEBIAN_FRONTEND=noninteractive
|
||||||
ENV LANG=C.UTF-8
|
ENV LANG=C.UTF-8
|
||||||
|
|
@ -38,6 +39,8 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
ripgrep \
|
ripgrep \
|
||||||
neovim \
|
neovim \
|
||||||
less \
|
less \
|
||||||
|
hugo \
|
||||||
|
xz-utils \
|
||||||
&& rm -rf /var/lib/apt/lists/* \
|
&& rm -rf /var/lib/apt/lists/* \
|
||||||
&& ln -sf /usr/bin/fdfind /usr/local/bin/fd
|
&& 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)
|
# 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
|
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
|
# Node.js 22
|
||||||
RUN curl -fsSL https://deb.nodesource.com/setup_${NODE_MAJOR}.x | bash - && \
|
RUN curl -fsSL https://deb.nodesource.com/setup_${NODE_MAJOR}.x | bash - && \
|
||||||
apt-get install -y --no-install-recommends nodejs && \
|
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 && \
|
unzip /tmp/terraform.zip -d /usr/local/bin && \
|
||||||
rm /tmp/terraform.zip
|
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)
|
# Atlassian CLI (acli)
|
||||||
RUN mkdir -p -m 755 /etc/apt/keyrings && \
|
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 && \
|
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