@danypops/pi-papyrus 0.57.10 → 0.58.0

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.
@@ -50,7 +50,7 @@ import {
50
50
  automaticPauseReason,
51
51
  shouldResumeFocusOnHumanInput,
52
52
  } from "./task/active-task-continuation.ts";
53
- import { emitTaskFocusEvent, setTaskFocusEventBus } from "./task/task-focus-events.ts";
53
+ import { emitTaskFocusEvent, extractDeclaredEffort, setTaskFocusEventBus } from "./task/task-focus-events.ts";
54
54
  import { TASK_STATUS_PRESENTATION, taskTreeConnector } from "./task/task-presentation.ts";
55
55
  import { buildTaskWidgetProjection, type TaskWidgetProjection } from "./task/task-widget.ts";
56
56
  import { measure as artifactCardMeasure } from "./tool-rendering/artifact-card.ts";
@@ -492,7 +492,7 @@ export default async function (pi: ExtensionAPI) {
492
492
  session_id: sessionId,
493
493
  ...sessionSecretField(sessionId),
494
494
  });
495
- emitTaskFocusEvent({ taskId: paused.artifact.id, sessionId, status: "paused" });
495
+ emitTaskFocusEvent({ taskId: paused.artifact.id, sessionId, status: "paused", effort: extractDeclaredEffort(paused.artifact.extra) });
496
496
  if (ctx.hasUI) ctx.ui.notify(`Papyrus task driving paused: ${decision.reason}. Human input resumes it automatically.`, "warning");
497
497
  }
498
498
  } catch {
@@ -896,7 +896,12 @@ export default async function (pi: ExtensionAPI) {
896
896
  session_id: sessionId,
897
897
  ...sessionSecretField(sessionId),
898
898
  });
899
- emitTaskFocusEvent({ taskId: focus.artifact.id, sessionId, status: "unpaused" });
899
+ emitTaskFocusEvent({
900
+ taskId: focus.artifact.id,
901
+ sessionId,
902
+ status: "unpaused",
903
+ effort: extractDeclaredEffort(focus.artifact.extra),
904
+ });
900
905
  }
901
906
  } catch {
902
907
  // The daemon may be unavailable during startup, reload, or shutdown.
@@ -3,12 +3,31 @@ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
3
3
 
4
4
  export type TaskFocusStatus = "focused" | "paused" | "unpaused" | "cleared";
5
5
 
6
+ /**
7
+ * A Task's own declared effort convention (`extra.effort`) -- Papyrus's core schema and
8
+ * validation know nothing about this value; it is genuine free-form `extra` JSON, the same
9
+ * mechanism `gates`/`checklist` already use. This module only reads it, opaquely, to relay it
10
+ * on the task-focus broadcast when present; Papyrus never assigns or requires it.
11
+ */
12
+ export const TASK_DECLARED_EFFORT_LEVELS = ["low", "medium", "high"] as const;
13
+ export type TaskDeclaredEffort = (typeof TASK_DECLARED_EFFORT_LEVELS)[number];
14
+
15
+ /** Returns the task's own declared `extra.effort` when it is one of the recognized values, or undefined otherwise -- an absent or malformed value is silently omitted, never guessed or defaulted. */
16
+ export function extractDeclaredEffort(extra: Record<string, unknown> | undefined): TaskDeclaredEffort | undefined {
17
+ const value = extra?.effort;
18
+ return typeof value === "string" && (TASK_DECLARED_EFFORT_LEVELS as readonly string[]).includes(value)
19
+ ? (value as TaskDeclaredEffort)
20
+ : undefined;
21
+ }
22
+
6
23
  export interface TaskFocusEvent {
7
24
  schema: typeof PAPYRUS_TASK_FOCUS_SCHEMA;
8
25
  taskId: string | null;
9
26
  sessionId?: string;
10
27
  status: TaskFocusStatus;
11
28
  observedAt: number;
29
+ /** The focused task's own declared `extra.effort`, when it has one -- omitted (never null) otherwise, matching this payload's existing minimalism. */
30
+ effort?: TaskDeclaredEffort;
12
31
  }
13
32
 
14
33
  export interface TaskFocusEventInput {
@@ -16,14 +35,16 @@ export interface TaskFocusEventInput {
16
35
  sessionId?: string;
17
36
  status: TaskFocusStatus;
18
37
  observedAt?: number;
38
+ effort?: TaskDeclaredEffort;
19
39
  }
20
40
 
21
41
  /**
22
42
  * Pure event builder, mirroring buildContextInjection's shape: no task title, body, or any other
23
- * artifact content -- only the id, session, lifecycle status, and timestamp, which are already
24
- * public metadata a caller with the id could look up directly. This is the payload emitted on
25
- * papyrus.task-focus.v1, the analogue of papyrus.context-injection.v1, so extensions such as a
26
- * token-cost router can correlate their own telemetry with the currently focused task without
43
+ * artifact content -- only the id, session, lifecycle status, timestamp, and (when the task
44
+ * declares one) its own bounded effort enum, which are already public metadata a caller with the
45
+ * id could look up directly. This is the payload emitted on papyrus.task-focus.v1, the analogue
46
+ * of papyrus.context-injection.v1, so extensions such as a token-cost router can correlate their
47
+ * own telemetry with the currently focused task -- and now its declared effort -- without
27
48
  * Papyrus depending on them.
28
49
  */
29
50
  export function buildTaskFocusEvent(input: TaskFocusEventInput): TaskFocusEvent {
@@ -35,6 +56,7 @@ export function buildTaskFocusEvent(input: TaskFocusEventInput): TaskFocusEvent
35
56
  status: input.status,
36
57
  observedAt: input.observedAt ?? Date.now(),
37
58
  ...(input.sessionId === undefined ? {} : { sessionId: input.sessionId }),
59
+ ...(input.effort === undefined ? {} : { effort: input.effort }),
38
60
  };
39
61
  }
40
62
 
@@ -25,7 +25,7 @@ import {
25
25
  import { callService } from "../service-client.ts";
26
26
  import { sessionSecretField } from "../session-identity.ts";
27
27
  import { showTaskDetails } from "./task-detail-view.ts";
28
- import { emitTaskFocusEvent } from "./task-focus-events.ts";
28
+ import { emitTaskFocusEvent, extractDeclaredEffort } from "./task-focus-events.ts";
29
29
  import { showTaskGraph } from "./task-graph.ts";
30
30
 
31
31
  export { taskDetailsText } from "./task-detail-format.ts";
@@ -313,7 +313,7 @@ export async function showTasks(ctx: ExtensionCommandContext): Promise<void> {
313
313
  session_id: sessionId,
314
314
  ...sessionSecretField(sessionId),
315
315
  });
316
- emitTaskFocusEvent({ taskId: focused.id, sessionId, status: "focused" });
316
+ emitTaskFocusEvent({ taskId: focused.id, sessionId, status: "focused", effort: extractDeclaredEffort(focused.extra) });
317
317
  ctx.ui.notify(`Active: ${action.row.title}`, "info");
318
318
  } catch (error) {
319
319
  ctx.ui.notify(`Focus failed: ${error instanceof Error ? error.message : error}`, "error");
@@ -336,7 +336,12 @@ export async function showTasks(ctx: ExtensionCommandContext): Promise<void> {
336
336
  session_id: sessionId,
337
337
  ...sessionSecretField(sessionId),
338
338
  });
339
- emitTaskFocusEvent({ taskId: result.artifact.id, sessionId, status: choice === "Pause focus" ? "paused" : "unpaused" });
339
+ emitTaskFocusEvent({
340
+ taskId: result.artifact.id,
341
+ sessionId,
342
+ status: choice === "Pause focus" ? "paused" : "unpaused",
343
+ effort: extractDeclaredEffort(result.artifact.extra),
344
+ });
340
345
  }
341
346
  ctx.ui.notify(choice === "Clear focus" ? "Task focus cleared" : choice, "info");
342
347
  } catch (error) {
@@ -35,7 +35,7 @@ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
35
35
  import { discussLiveFollowUp } from "../discuss/discuss-live-follow-up.ts";
36
36
  import { currentVehicleClientTarget } from "../service-client.ts";
37
37
  import { sessionSecretField } from "../session-identity.ts";
38
- import { emitTaskFocusEvent } from "../task/task-focus-events.ts";
38
+ import { emitTaskFocusEvent, extractDeclaredEffort } from "../task/task-focus-events.ts";
39
39
  import { recordRenderDiagnostic, shapeFingerprint } from "./render-diagnostics.ts";
40
40
  import { papyrusVehiclePresentations, papyrusVehicleRenderers } from "./vehicle-artifact-renderers.ts";
41
41
 
@@ -210,14 +210,18 @@ export function registerNotesVehicle(pi: ExtensionAPI): Promise<RegisteredPiVehi
210
210
  // timestamp against when registration actually completed.
211
211
  recordRenderDiagnostic({ event: "invoked", operation: descriptor.name, output: shapeFingerprint(output) });
212
212
  if (descriptor.name === "tasks.focus") {
213
- const artifact = output as { id: string } | undefined;
214
- if (artifact?.id) emitTaskFocusEvent({ taskId: artifact.id, status: "focused" });
213
+ const artifact = output as { id: string; extra?: Record<string, unknown> } | undefined;
214
+ if (artifact?.id) emitTaskFocusEvent({ taskId: artifact.id, status: "focused", effort: extractDeclaredEffort(artifact.extra) });
215
215
  return;
216
216
  }
217
217
  if (descriptor.name === "tasks.pause" || descriptor.name === "tasks.unpause") {
218
- const focus = output as { artifact: { id: string } } | undefined;
218
+ const focus = output as { artifact: { id: string; extra?: Record<string, unknown> } } | undefined;
219
219
  if (focus?.artifact?.id)
220
- emitTaskFocusEvent({ taskId: focus.artifact.id, status: descriptor.name === "tasks.pause" ? "paused" : "unpaused" });
220
+ emitTaskFocusEvent({
221
+ taskId: focus.artifact.id,
222
+ status: descriptor.name === "tasks.pause" ? "paused" : "unpaused",
223
+ effort: extractDeclaredEffort(focus.artifact.extra),
224
+ });
221
225
  return;
222
226
  }
223
227
  if (descriptor.name === "tasks.clear_focus") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danypops/pi-papyrus",
3
- "version": "0.57.10",
3
+ "version": "0.58.0",
4
4
  "description": "Pi host extension for Papyrus: native tools, TUI panels, and context injection over the daemon-backed graph store",
5
5
  "license": "MIT",
6
6
  "type": "module",