mirror of
https://github.com/wassname/pi-lgtm.git
synced 2026-09-13 13:01:22 +08:00
v0.3.3
This commit is contained in:
+78
-13
@@ -44,15 +44,25 @@ export default function (pi: ExtensionAPI) {
|
||||
// Initialize store and config
|
||||
const cfg = loadTasksConfig();
|
||||
const piTasks = process.env.PI_TASKS;
|
||||
const localTasksPath = join(process.cwd(), ".pi", "tasks", "tasks.json");
|
||||
const store =
|
||||
piTasks === "off" ? new TaskStore() :
|
||||
piTasks?.startsWith("/") ? new TaskStore(piTasks) :
|
||||
piTasks?.startsWith(".") ? new TaskStore(resolve(piTasks)) :
|
||||
piTasks ? new TaskStore(piTasks) :
|
||||
cfg.persistTasks === false ? new TaskStore() :
|
||||
new TaskStore(localTasksPath);
|
||||
const taskScope = cfg.taskScope ?? "session";
|
||||
|
||||
/** Resolve the task store path from env/config (without session ID). */
|
||||
function resolveStorePath(sessionId?: string): string | undefined {
|
||||
if (piTasks === "off") return undefined;
|
||||
if (piTasks?.startsWith("/")) return piTasks;
|
||||
if (piTasks?.startsWith(".")) return resolve(piTasks);
|
||||
if (piTasks) return piTasks;
|
||||
if (taskScope === "memory") return undefined;
|
||||
if (taskScope === "session" && sessionId) {
|
||||
return join(process.cwd(), ".pi", "tasks", `tasks-${sessionId}.json`);
|
||||
}
|
||||
if (taskScope === "session") return undefined; // no session ID yet, start in-memory
|
||||
return join(process.cwd(), ".pi", "tasks", "tasks.json");
|
||||
}
|
||||
|
||||
// For project scope (or env override), create store immediately.
|
||||
// For session scope, start with in-memory and upgrade once we have the session ID.
|
||||
let store = new TaskStore(resolveStorePath());
|
||||
const tracker = new ProcessTracker();
|
||||
const widget = new TaskWidget(store);
|
||||
|
||||
@@ -165,13 +175,51 @@ export default function (pi: ExtensionAPI) {
|
||||
widget.update();
|
||||
});
|
||||
|
||||
// ── Session-scoped store upgrade ──
|
||||
// For session scope, the store starts in-memory (no session ID at init time).
|
||||
// Upgrade to file-backed on first context arrival (turn_start, before_agent_start,
|
||||
// or tool_execution_start — whichever fires first).
|
||||
let storeUpgraded = false;
|
||||
let persistedTasksShown = false;
|
||||
function upgradeStoreIfNeeded(ctx: ExtensionContext) {
|
||||
if (storeUpgraded) return;
|
||||
if (taskScope === "session" && !piTasks) {
|
||||
const sessionId = ctx.sessionManager.getSessionId();
|
||||
const path = resolveStorePath(sessionId);
|
||||
store = new TaskStore(path);
|
||||
widget.setStore(store);
|
||||
}
|
||||
storeUpgraded = true;
|
||||
}
|
||||
|
||||
/** Restore widget on session start/resume if there's unfinished work.
|
||||
* On new sessions, auto-clear if all tasks are completed (clean slate).
|
||||
* On resume, always show tasks (user may want to review).
|
||||
* Only runs once — the first caller wins. */
|
||||
function showPersistedTasks(isResume = false) {
|
||||
if (persistedTasksShown) return;
|
||||
persistedTasksShown = true;
|
||||
const tasks = store.list();
|
||||
if (tasks.length > 0) {
|
||||
if (!isResume && tasks.every(t => t.status === "completed")) {
|
||||
store.clearCompleted();
|
||||
if (taskScope === "session") store.deleteFileIfEmpty();
|
||||
} else {
|
||||
widget.update();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Turn tracking for system-reminder injection ──
|
||||
let currentTurn = 0;
|
||||
let lastTaskToolUseTurn = 0;
|
||||
let reminderInjectedThisCycle = false;
|
||||
|
||||
pi.on("turn_start", async () => {
|
||||
pi.on("turn_start", async (_event, ctx) => {
|
||||
currentTurn++;
|
||||
latestCtx = ctx;
|
||||
widget.setUICtx(ctx.ui as UICtx);
|
||||
upgradeStoreIfNeeded(ctx);
|
||||
});
|
||||
|
||||
// ── Token usage tracking ──
|
||||
@@ -211,17 +259,27 @@ export default function (pi: ExtensionAPI) {
|
||||
});
|
||||
|
||||
// Grab UI context early — before_agent_start fires before any tool calls,
|
||||
// so persisted tasks show up immediately on session resume.
|
||||
// so persisted tasks show up immediately on session start.
|
||||
pi.on("before_agent_start", async (_event, ctx) => {
|
||||
latestCtx = ctx;
|
||||
widget.setUICtx(ctx.ui as UICtx);
|
||||
if (store.list().length > 0) widget.update();
|
||||
upgradeStoreIfNeeded(ctx);
|
||||
showPersistedTasks();
|
||||
});
|
||||
|
||||
// session_switch fires on resume (reason: "resume") — reload persisted tasks.
|
||||
pi.on("session_switch" as any, async (event: any, ctx: ExtensionContext) => {
|
||||
latestCtx = ctx;
|
||||
widget.setUICtx(ctx.ui as UICtx);
|
||||
upgradeStoreIfNeeded(ctx);
|
||||
showPersistedTasks(event?.reason === "resume");
|
||||
});
|
||||
|
||||
// Keep latestCtx fresh on every tool execution as well.
|
||||
pi.on("tool_execution_start", async (_event, ctx) => {
|
||||
latestCtx = ctx;
|
||||
widget.setUICtx(ctx.ui as UICtx);
|
||||
upgradeStoreIfNeeded(ctx);
|
||||
widget.update();
|
||||
});
|
||||
|
||||
@@ -741,9 +799,10 @@ Set up task dependencies:
|
||||
const choices: string[] = [
|
||||
`View all tasks (${taskCount})`,
|
||||
"Create task",
|
||||
"Settings",
|
||||
];
|
||||
if (completedCount > 0) choices.push(`Clear completed (${completedCount})`);
|
||||
if (taskCount > 0) choices.push(`Clear all (${taskCount})`);
|
||||
choices.push("Settings");
|
||||
|
||||
const choice = await ui.select("Tasks", choices);
|
||||
if (!choice) return;
|
||||
@@ -754,8 +813,14 @@ Set up task dependencies:
|
||||
await createTask();
|
||||
} else if (choice === "Settings") {
|
||||
await settingsMenu();
|
||||
} else if (choice.startsWith("Clear")) {
|
||||
} else if (choice.startsWith("Clear completed")) {
|
||||
store.clearCompleted();
|
||||
if (taskScope === "session") store.deleteFileIfEmpty();
|
||||
widget.update();
|
||||
await mainMenu();
|
||||
} else if (choice.startsWith("Clear all")) {
|
||||
store.clearAll();
|
||||
if (taskScope === "session") store.deleteFileIfEmpty();
|
||||
widget.update();
|
||||
await mainMenu();
|
||||
}
|
||||
|
||||
@@ -265,6 +265,22 @@ export class TaskStore {
|
||||
});
|
||||
}
|
||||
|
||||
/** Remove all tasks. */
|
||||
clearAll(): number {
|
||||
return this.withLock(() => {
|
||||
const count = this.tasks.size;
|
||||
this.tasks.clear();
|
||||
return count;
|
||||
});
|
||||
}
|
||||
|
||||
/** Delete the backing file (if file-backed and empty). */
|
||||
deleteFileIfEmpty(): boolean {
|
||||
if (!this.filePath || this.tasks.size > 0) return false;
|
||||
try { unlinkSync(this.filePath); } catch { /* ignore */ }
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Remove all completed tasks. */
|
||||
clearCompleted(): number {
|
||||
return this.withLock(() => {
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ import { readFileSync, writeFileSync, mkdirSync } from "node:fs";
|
||||
import { join, dirname } from "node:path";
|
||||
|
||||
export interface TasksConfig {
|
||||
persistTasks?: boolean; // default: true
|
||||
taskScope?: "memory" | "session" | "project"; // default: "session"
|
||||
autoCascade?: boolean; // default: false
|
||||
}
|
||||
|
||||
|
||||
+13
-12
@@ -28,6 +28,17 @@ export async function openSettingsMenu(
|
||||
): Promise<void> {
|
||||
await ui.custom((_tui, theme, _kb, done) => {
|
||||
const items: SettingItem[] = [
|
||||
{
|
||||
id: "taskScope",
|
||||
label: "Task storage",
|
||||
description:
|
||||
"memory: tasks live only in memory, lost when session ends. " +
|
||||
"session: persisted per session (tasks-<sessionId>.json), survives resume. " +
|
||||
"project: shared across all sessions (tasks.json). " +
|
||||
"Takes effect on next session start.",
|
||||
currentValue: cfg.taskScope ?? "session",
|
||||
values: ["memory", "session", "project"],
|
||||
},
|
||||
{
|
||||
id: "autoCascade",
|
||||
label: "Auto-execute with agents",
|
||||
@@ -37,16 +48,6 @@ export async function openSettingsMenu(
|
||||
currentValue: (cfg.autoCascade ?? false) ? "on" : "off",
|
||||
values: ["on", "off"],
|
||||
},
|
||||
{
|
||||
id: "persist",
|
||||
label: "Persist tasks across sessions",
|
||||
description:
|
||||
"When ON: pending and in-progress tasks are saved to .pi/tasks/tasks.json so they " +
|
||||
"survive a restart. Completed tasks are never written to disk. " +
|
||||
"Toggle takes effect on next session start.",
|
||||
currentValue: (cfg.persistTasks ?? true) ? "on" : "off",
|
||||
values: ["on", "off"],
|
||||
},
|
||||
];
|
||||
|
||||
const list = new SettingsList(
|
||||
@@ -58,8 +59,8 @@ export async function openSettingsMenu(
|
||||
cfg.autoCascade = newValue === "on";
|
||||
saveTasksConfig(cfg);
|
||||
}
|
||||
if (id === "persist") {
|
||||
cfg.persistTasks = newValue === "on";
|
||||
if (id === "taskScope") {
|
||||
cfg.taskScope = newValue as "memory" | "session" | "project";
|
||||
saveTasksConfig(cfg);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -75,6 +75,10 @@ export class TaskWidget {
|
||||
|
||||
constructor(private store: TaskStore) {}
|
||||
|
||||
setStore(store: TaskStore) {
|
||||
this.store = store;
|
||||
}
|
||||
|
||||
setUICtx(ctx: UICtx) {
|
||||
this.uiCtx = ctx;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user