@danypops/papyrus 0.29.4 → 0.29.6
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.
- package/extension/src/index.ts +15 -5
- package/extension/src/tasks.ts +4 -3
- package/package.json +1 -1
- package/src/cli.ts +13 -9
- package/src/daemon.ts +5 -1
- package/src/db.ts +16 -6
- package/src/task-service.ts +2 -1
package/extension/src/index.ts
CHANGED
|
@@ -95,7 +95,7 @@ export function renderTaskWidgetLines(theme: Theme, projection: TaskWidgetProjec
|
|
|
95
95
|
return lines;
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
-
class TaskOverlay {
|
|
98
|
+
export class TaskOverlay {
|
|
99
99
|
private uiCtx: ExtensionUIContext | undefined;
|
|
100
100
|
private registered = false;
|
|
101
101
|
private tui: any | undefined;
|
|
@@ -116,6 +116,12 @@ class TaskOverlay {
|
|
|
116
116
|
// concurrent agent's focused task never shows as active in this session's widget.
|
|
117
117
|
setSessionId(sessionId: string): void { this.sessionId = sessionId; }
|
|
118
118
|
|
|
119
|
+
/**
|
|
120
|
+
* Never throws: called from several pi.on(...) handlers, some of which (session_compact,
|
|
121
|
+
* session_tree, tool_execution_end) don't wrap it themselves -- Pi's event emitter does not
|
|
122
|
+
* guarantee catching a handler's rejection, so an unguarded throw here would become an
|
|
123
|
+
* unhandled rejection at the call site instead of a stability issue contained to this widget.
|
|
124
|
+
*/
|
|
119
125
|
async refresh(): Promise<void> {
|
|
120
126
|
if (!this.projectRoot) return;
|
|
121
127
|
try {
|
|
@@ -123,7 +129,11 @@ class TaskOverlay {
|
|
|
123
129
|
} catch {
|
|
124
130
|
this.snapshot = { nodes: [], rootIds: [] };
|
|
125
131
|
}
|
|
126
|
-
|
|
132
|
+
try {
|
|
133
|
+
this.render();
|
|
134
|
+
} catch {
|
|
135
|
+
// A rendering bug must not crash the extension host over a best-effort status widget.
|
|
136
|
+
}
|
|
127
137
|
}
|
|
128
138
|
|
|
129
139
|
private render(): void {
|
|
@@ -519,7 +529,7 @@ export default async function (pi: ExtensionAPI) {
|
|
|
519
529
|
const usage = ctx.getContextUsage?.();
|
|
520
530
|
// Real tree (not just the linear current-branch path): surfaces content sitting in an
|
|
521
531
|
// abandoned /tree branch, which cost real tokens to generate but isn't in context now.
|
|
522
|
-
const tree = ctx.sessionManager.getTree() as
|
|
532
|
+
const tree = ctx.sessionManager.getTree() as SessionTreeNodeLike[];
|
|
523
533
|
// buildContextEntries(), NOT getBranch(): getBranch() returns every raw entry on the
|
|
524
534
|
// current path including everything a real compaction has already summarized away.
|
|
525
535
|
// A session with 3 real compactions confirmed this made "active" message-history
|
|
@@ -528,8 +538,8 @@ export default async function (pi: ExtensionAPI) {
|
|
|
528
538
|
// LLM"); buildContextEntries() is the compaction-aware entry list matching what the
|
|
529
539
|
// LLM actually sees (the latest compaction entry itself, plus kept entries from its
|
|
530
540
|
// firstKeptEntryId onward, plus everything after -- older summarized entries omitted).
|
|
531
|
-
const activeEntryIds = new Set((ctx.sessionManager.buildContextEntries() as
|
|
532
|
-
const branchEntryIds = new Set((ctx.sessionManager.getBranch() as
|
|
541
|
+
const activeEntryIds = new Set((ctx.sessionManager.buildContextEntries() as SessionEntryLike[]).map((entry) => entry.id));
|
|
542
|
+
const branchEntryIds = new Set((ctx.sessionManager.getBranch() as SessionEntryLike[]).map((entry) => entry.id));
|
|
533
543
|
const messageHistory = buildMessageHistoryTree(tree, activeEntryIds, branchEntryIds);
|
|
534
544
|
const breakdown = buildContextBreakdown({
|
|
535
545
|
totalTokens: usage?.tokens ?? null,
|
package/extension/src/tasks.ts
CHANGED
|
@@ -120,7 +120,8 @@ export async function showTasks(ctx: ExtensionCommandContext): Promise<void> {
|
|
|
120
120
|
if (action.type === "graph") { await showTaskGraph(ctx, graph); continue; }
|
|
121
121
|
if (action.type !== "action" || !action.row) continue;
|
|
122
122
|
|
|
123
|
-
const
|
|
123
|
+
const rowId = action.row.id;
|
|
124
|
+
const node = graph.nodes.find((entry) => entry.task.id === rowId);
|
|
124
125
|
const active = node?.active === true;
|
|
125
126
|
const focusStatus = node?.focusStatus;
|
|
126
127
|
const choices = [
|
|
@@ -136,8 +137,8 @@ export async function showTasks(ctx: ExtensionCommandContext): Promise<void> {
|
|
|
136
137
|
const choice = await ctx.ui.select(action.row.title, choices);
|
|
137
138
|
if (!choice) continue;
|
|
138
139
|
|
|
139
|
-
if (choice === "Remove dependency" || choice === "Remove from parent") {
|
|
140
|
-
const relatedIds = choice === "Remove dependency" ? node
|
|
140
|
+
if ((choice === "Remove dependency" || choice === "Remove from parent") && node) {
|
|
141
|
+
const relatedIds = choice === "Remove dependency" ? node.dependencyIds : node.parentIds;
|
|
141
142
|
const relatedTasks = relatedIds.map((relatedId) => graph.nodes.find((entry) => entry.task.id === relatedId)?.task).filter((task): task is Artifact => task !== undefined);
|
|
142
143
|
const relatedTitles = taskChoiceLabels(relatedTasks);
|
|
143
144
|
const selected = await ctx.ui.select(choice === "Remove dependency" ? "Remove which dependency?" : "Remove from which parent?", relatedTitles);
|
package/package.json
CHANGED
package/src/cli.ts
CHANGED
|
@@ -112,7 +112,8 @@ const USAGE = `Usage:
|
|
|
112
112
|
papyrus skills instantiate <template-id> [--title <title>] [--body <body>] [--status <status>] [--labels-json <json>] [--extra-json <json>] [--json]
|
|
113
113
|
papyrus skills assign-project <id> [project-root] [--json]
|
|
114
114
|
papyrus skills update <id> [--title <title>] [--body <body>] [--labels-json <json>] [--json]
|
|
115
|
-
papyrus playbooks create --title <title> [--body <body>] [--trigger <text>] [--steps-json <json>] [--tools-json <json>] [--labels-json <json>] [--extra-json <json>] [--arguments-json <json>] [--project-root <path>] [--json]
|
|
115
|
+
papyrus playbooks create --title <title> [--body <body>] [--trigger <text>] [--steps-json <json>] [--tools-json <json>] [--labels-json <json>] [--extra-json <json>] [--arguments-json <json array>] [--project-root <path>] [--json]
|
|
116
|
+
papyrus playbooks invoke <id> [--arguments-json <json object>] [--json]
|
|
116
117
|
papyrus playbooks list [--status <status>] [--text <query>] [--limit <count>] [--project-root <path>] [--json]
|
|
117
118
|
papyrus playbooks show <id> [--json]
|
|
118
119
|
papyrus playbooks invoke <id> [--json]
|
|
@@ -201,12 +202,15 @@ function parseJsonStringArrayFlag(value: string | undefined, flag: string): stri
|
|
|
201
202
|
return parsed as string[];
|
|
202
203
|
}
|
|
203
204
|
|
|
204
|
-
/**
|
|
205
|
-
|
|
205
|
+
/**
|
|
206
|
+
* No shape assertion here -- unlike every other JSON flag, playbooks --arguments-json is genuinely
|
|
207
|
+
* polymorphic (an array on create, a {name: value} map on invoke), and the two actions share one
|
|
208
|
+
* flag-parsing pass in runPlaybooksCli. The service validates the shape for whichever operation
|
|
209
|
+
* actually receives it.
|
|
210
|
+
*/
|
|
211
|
+
function parseJsonAnyFlag(value: string | undefined, flag: string): unknown {
|
|
206
212
|
if (value === undefined) throw new Error(`${flag} requires a value`);
|
|
207
|
-
|
|
208
|
-
if (!Array.isArray(parsed)) throw new Error(`${flag} must be a JSON array`);
|
|
209
|
-
return parsed;
|
|
213
|
+
return JSON.parse(value) as unknown;
|
|
210
214
|
}
|
|
211
215
|
|
|
212
216
|
function artifactLabel(artifact: CliArtifact): string {
|
|
@@ -794,7 +798,7 @@ export async function runPlaybooksCli(args: string[], client: TaskCliClient): Pr
|
|
|
794
798
|
let tools: string[] | undefined;
|
|
795
799
|
let labels: string[] | undefined;
|
|
796
800
|
let extra: Record<string, unknown> | undefined;
|
|
797
|
-
let playbookArguments: unknown
|
|
801
|
+
let playbookArguments: unknown;
|
|
798
802
|
let status: string | undefined;
|
|
799
803
|
let text: string | undefined;
|
|
800
804
|
let limit: number | undefined;
|
|
@@ -809,7 +813,7 @@ export async function runPlaybooksCli(args: string[], client: TaskCliClient): Pr
|
|
|
809
813
|
if (argument === "--tools-json") { tools = parseJsonStringArrayFlag(args[++index], "--tools-json"); continue; }
|
|
810
814
|
if (argument === "--labels-json") { labels = parseJsonStringArrayFlag(args[++index], "--labels-json"); continue; }
|
|
811
815
|
if (argument === "--extra-json") { extra = parseJsonObjectFlag(args[++index], "--extra-json"); continue; }
|
|
812
|
-
if (argument === "--arguments-json") { playbookArguments =
|
|
816
|
+
if (argument === "--arguments-json") { playbookArguments = parseJsonAnyFlag(args[++index], "--arguments-json"); continue; }
|
|
813
817
|
if (argument === "--status") { status = args[++index]; if (!status) throw new Error("--status requires a value"); continue; }
|
|
814
818
|
if (argument === "--text") { text = args[++index]; if (text === undefined) throw new Error("--text requires a value"); continue; }
|
|
815
819
|
if (argument === "--project-root") { playbookProjectRoot = args[++index]; if (!playbookProjectRoot) throw new Error("--project-root requires a value"); continue; }
|
|
@@ -850,7 +854,7 @@ export async function runPlaybooksCli(args: string[], client: TaskCliClient): Pr
|
|
|
850
854
|
}
|
|
851
855
|
case "invoke": {
|
|
852
856
|
if (!id || second) throw new Error("playbooks invoke requires exactly one playbook id");
|
|
853
|
-
const invocation = await client.call<Record<string, unknown>, string>("playbooks.invoke", { id });
|
|
857
|
+
const invocation = await client.call<Record<string, unknown>, string>("playbooks.invoke", { id, arguments: playbookArguments });
|
|
854
858
|
result = invocation;
|
|
855
859
|
human = invocation;
|
|
856
860
|
break;
|
package/src/daemon.ts
CHANGED
|
@@ -52,7 +52,11 @@ export function serveMain(): void {
|
|
|
52
52
|
clearInterval(purgeTrashTimer);
|
|
53
53
|
clearDaemonPort(stateDir);
|
|
54
54
|
service.close();
|
|
55
|
-
|
|
55
|
+
// .finally() re-throws rather than handling a rejection -- catching it first turns a bare
|
|
56
|
+
// unhandled-rejection warning into a real, queryable shutdown-failure log line.
|
|
57
|
+
void server.stop(true)
|
|
58
|
+
.catch((error) => logEvent("error", "server_stop_failed", { message: error instanceof Error ? error.message : String(error) }))
|
|
59
|
+
.finally(() => process.exit(0));
|
|
56
60
|
};
|
|
57
61
|
process.on("SIGINT", shutdown);
|
|
58
62
|
process.on("SIGTERM", shutdown);
|
package/src/db.ts
CHANGED
|
@@ -12,13 +12,23 @@ import { SQLITE_BUSY_TIMEOUT_MS, SQLITE_SCHEMA_VERSION } from "./constants.ts";
|
|
|
12
12
|
|
|
13
13
|
const require_ = createRequire(import.meta.url);
|
|
14
14
|
const IS_BUN = typeof (globalThis as { Bun?: unknown }).Bun !== "undefined";
|
|
15
|
-
const backend = IS_BUN
|
|
16
|
-
? (require_("bun:sqlite") as typeof import("bun:sqlite"))
|
|
17
|
-
: (require_("node:sqlite") as unknown as typeof import("bun:sqlite"));
|
|
18
15
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
16
|
+
/** Bun's bun:sqlite exports Database; Node's node:sqlite exports DatabaseSync -- neither module is
|
|
17
|
+
* actually the other, but both satisfy this shape at the methods Papyrus calls through Db/DbStatement. */
|
|
18
|
+
interface SqliteBackendModule {
|
|
19
|
+
Database?: new (path: string, opts?: { create?: boolean }) => Db;
|
|
20
|
+
DatabaseSync?: new (path: string, opts?: { create?: boolean }) => Db;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const backend = require_(IS_BUN ? "bun:sqlite" : "node:sqlite") as SqliteBackendModule;
|
|
24
|
+
// IIFE + explicit return type, not a bare `const DatabaseCtor = backend.DatabaseSync ?? backend.Database`
|
|
25
|
+
// with a following throw-guard: that guard's narrowing wouldn't propagate into openDb() below, a
|
|
26
|
+
// separate function closing over this module-level binding.
|
|
27
|
+
const DatabaseCtor: new (path: string, opts?: { create?: boolean }) => Db = (() => {
|
|
28
|
+
const ctor = backend.DatabaseSync ?? backend.Database;
|
|
29
|
+
if (!ctor) throw new Error("no compatible sqlite backend found (expected bun:sqlite's Database or node:sqlite's DatabaseSync)");
|
|
30
|
+
return ctor;
|
|
31
|
+
})();
|
|
22
32
|
|
|
23
33
|
export interface DbStatement {
|
|
24
34
|
/** changes: number of rows the statement affected. Both bun:sqlite and node:sqlite's real run() return this at runtime; declared here so callers (e.g. reapStale) can rely on it without an unsafe cast. */
|
package/src/task-service.ts
CHANGED
|
@@ -286,10 +286,11 @@ export class Tasks {
|
|
|
286
286
|
const byId = new Map(tasks.map((task) => [task.id, task]));
|
|
287
287
|
const focus = this.focusStore.get(filter.sessionId);
|
|
288
288
|
const focusedId = focus?.taskId;
|
|
289
|
+
const focusStatus = focus?.status;
|
|
289
290
|
const nodes = new Map(tasks.map((task) => [task.id, {
|
|
290
291
|
task,
|
|
291
292
|
active: task.id === focusedId,
|
|
292
|
-
...(task.id === focusedId ? { focusStatus
|
|
293
|
+
...(task.id === focusedId ? { focusStatus } : {}),
|
|
293
294
|
parentIds: [] as string[],
|
|
294
295
|
childIds: [] as string[],
|
|
295
296
|
dependencyIds: [] as string[],
|