@danypops/papyrus 0.2.0 → 0.2.1

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/README.md CHANGED
@@ -129,7 +129,7 @@ Proof types are `file`, `symbol`, `code`, `test`, `command`, `artifact`, and `ur
129
129
 
130
130
  Papyrus also injects an Alef-style reconciliation block on every agent turn while work remains: `Current`, `Desired`, `Verify`, and `Next`. The agent is explicitly instructed to ask **“Did we accomplish this task?”** and run gates before marking it done. The injection disappears when every task is complete.
131
131
 
132
- In TUI and RPC modes, the extension checks bounded active Tasks at Pi’s public `agent_settled` lifecycle boundary. If active work remains and no continuation is already pending, it queues one hidden next turn so the agent continues instead of handing off merely because a low-level run ended. Driving is single-flight and pauses after 20 automatic turns or 6 unchanged task snapshots. Use `/task-drive on`, `/task-drive off`, or `/task-drive status`; human input and task progress reset the bounded counters.
132
+ In TUI and RPC modes, the extension checks bounded active Tasks at Pi’s public `agent_settled` lifecycle boundary. If active work remains and no continuation is already pending, it queues one hidden next turn so the agent continues instead of handing off merely because a low-level run ended. Active Tasks are the trigger; no manual command is required. Driving is single-flight and pauses after 20 automatic turns or 6 unchanged task snapshots; human input and task progress reset the bounded counters automatically.
133
133
 
134
134
  ## Why
135
135
 
@@ -4,20 +4,19 @@ export interface ActiveTaskMarker {
4
4
  updated_at: string;
5
5
  }
6
6
 
7
- export interface TaskDriverOptions {
7
+ export interface ActiveTaskContinuationOptions {
8
8
  maxTurns: number;
9
9
  maxUnchangedTurns: number;
10
10
  }
11
11
 
12
- export interface TaskDriverState {
13
- enabled: boolean;
12
+ export interface ActiveTaskContinuationState {
14
13
  queued: boolean;
15
14
  consecutiveTurns: number;
16
15
  unchangedTurns: number;
17
16
  pausedReason?: string;
18
17
  }
19
18
 
20
- export interface TaskDriverDecision {
19
+ export interface ActiveTaskContinuationDecision {
21
20
  action: "continue" | "wait" | "pause";
22
21
  reason: string;
23
22
  prompt?: string;
@@ -37,29 +36,27 @@ function continuationPrompt(tasks: ActiveTaskMarker[]): string {
37
36
  const names = tasks.slice(0, DISPLAYED_TASK_LIMIT).map((task) => `- ${task.id}: ${task.title.slice(0, TITLE_LIMIT)}`);
38
37
  return [
39
38
  "Continue active Papyrus work now; do not hand off merely because the previous Pi run settled.",
40
- "Reconcile the active Tasks, choose the next concrete action, use tools, run gates before completion, and continue until done, blocked, or the bounded task driver pauses.",
39
+ "Reconcile the active Tasks, choose the next concrete action, use tools, run gates before completion, and continue until done, blocked, or the bounded continuation pauses.",
41
40
  "Active tasks:",
42
41
  ...names,
43
42
  ].join("\n");
44
43
  }
45
44
 
46
- export class TaskDriver {
47
- private enabled = true;
45
+ export class ActiveTaskContinuation {
48
46
  private queued = false;
49
47
  private consecutiveTurns = 0;
50
48
  private unchangedTurns = 0;
51
49
  private lastFingerprint: string | undefined;
52
50
  private pausedReason: string | undefined;
53
51
 
54
- constructor(private readonly options: TaskDriverOptions) {
52
+ constructor(private readonly options: ActiveTaskContinuationOptions) {
55
53
  if (!Number.isInteger(options.maxTurns) || options.maxTurns < 1) throw new Error("maxTurns must be a positive integer");
56
54
  if (!Number.isInteger(options.maxUnchangedTurns) || options.maxUnchangedTurns < 1) {
57
55
  throw new Error("maxUnchangedTurns must be a positive integer");
58
56
  }
59
57
  }
60
58
 
61
- evaluate(tasks: ActiveTaskMarker[], context: { idle: boolean; pendingMessages: boolean }): TaskDriverDecision {
62
- if (!this.enabled) return { action: "wait", reason: "disabled" };
59
+ evaluate(tasks: ActiveTaskMarker[], context: { idle: boolean; pendingMessages: boolean }): ActiveTaskContinuationDecision {
63
60
  if (!context.idle) return { action: "wait", reason: "Pi is not settled" };
64
61
  if (context.pendingMessages) return { action: "wait", reason: "Pi already has pending messages" };
65
62
  if (this.queued) return { action: "wait", reason: "continuation already queued" };
@@ -101,15 +98,8 @@ export class TaskDriver {
101
98
  this.resetProgress();
102
99
  }
103
100
 
104
- setEnabled(enabled: boolean): void {
105
- this.enabled = enabled;
106
- if (enabled) this.resetProgress();
107
- else this.queued = false;
108
- }
109
-
110
- status(): TaskDriverState {
101
+ status(): ActiveTaskContinuationState {
111
102
  return {
112
- enabled: this.enabled,
113
103
  queued: this.queued,
114
104
  consecutiveTurns: this.consecutiveTurns,
115
105
  unchangedTurns: this.unchangedTurns,
@@ -117,7 +107,7 @@ export class TaskDriver {
117
107
  };
118
108
  }
119
109
 
120
- private pause(reason: string): TaskDriverDecision {
110
+ private pause(reason: string): ActiveTaskContinuationDecision {
121
111
  this.pausedReason = reason;
122
112
  return { action: "pause", reason };
123
113
  }
@@ -21,7 +21,7 @@ import { formatMetadata } from "./artifact-format.ts";
21
21
  import { callService } from "./service-client.ts";
22
22
  import { registerDomainTools } from "./domain-tools.ts";
23
23
  import type { TaskGraph } from "../../src/task-service.ts";
24
- import { TaskDriver, type ActiveTaskMarker } from "./task-driver.ts";
24
+ import { ActiveTaskContinuation, type ActiveTaskMarker } from "./active-task-continuation.ts";
25
25
  import { buildTaskWidgetProjection } from "./task-widget.ts";
26
26
 
27
27
  function text(t: string, details: Record<string, unknown> = {}) {
@@ -137,7 +137,7 @@ class TaskOverlay {
137
137
 
138
138
  export default async function (pi: ExtensionAPI) {
139
139
  registerDomainTools(pi);
140
- const taskDriver = new TaskDriver({
140
+ const taskContinuation = new ActiveTaskContinuation({
141
141
  maxTurns: TASK_DRIVER_MAX_TURNS,
142
142
  maxUnchangedTurns: TASK_DRIVER_MAX_UNCHANGED_TURNS,
143
143
  });
@@ -149,7 +149,7 @@ export default async function (pi: ExtensionAPI) {
149
149
  status: "active",
150
150
  limit: TASK_DRIVER_ACTIVE_LIMIT,
151
151
  });
152
- const decision = taskDriver.evaluate(active, {
152
+ const decision = taskContinuation.evaluate(active, {
153
153
  idle: ctx.isIdle(),
154
154
  pendingMessages: ctx.hasPendingMessages(),
155
155
  });
@@ -160,40 +160,13 @@ export default async function (pi: ExtensionAPI) {
160
160
  display: false,
161
161
  }, { triggerTurn: true, deliverAs: "nextTurn" });
162
162
  } else if (decision.action === "pause" && ctx.hasUI) {
163
- ctx.ui.notify(`Papyrus task driving paused: ${decision.reason}. Use /task-drive on to resume.`, "warning");
163
+ ctx.ui.notify(`Papyrus task driving paused: ${decision.reason}. Human input or task progress resumes it automatically.`, "warning");
164
164
  }
165
165
  } catch {
166
166
  // The daemon may be unavailable during startup, reload, or shutdown.
167
167
  }
168
168
  };
169
169
 
170
- pi.registerCommand("task-drive", {
171
- description: "Control bounded automatic continuation while active Papyrus Tasks remain",
172
- handler: async (args, ctx) => {
173
- const action = args.trim().toLowerCase() || "status";
174
- if (action === "on") {
175
- taskDriver.setEnabled(true);
176
- ctx.ui.notify("Papyrus task driving enabled", "info");
177
- await driveActiveTasks(ctx);
178
- return;
179
- }
180
- if (action === "off") {
181
- taskDriver.setEnabled(false);
182
- ctx.ui.notify("Papyrus task driving disabled", "info");
183
- return;
184
- }
185
- if (action !== "status") {
186
- ctx.ui.notify("Usage: /task-drive <on|off|status>", "warning");
187
- return;
188
- }
189
- const status = taskDriver.status();
190
- ctx.ui.notify(
191
- `Papyrus task driving: ${status.enabled ? "on" : "off"} · ${status.consecutiveTurns}/${TASK_DRIVER_MAX_TURNS} automatic turns${status.pausedReason ? ` · paused: ${status.pausedReason}` : ""}`,
192
- "info",
193
- );
194
- },
195
- });
196
-
197
170
  // ── Low-level graph-store tools ────────────────────────────────────
198
171
 
199
172
  pi.registerTool({
@@ -393,9 +366,9 @@ export default async function (pi: ExtensionAPI) {
393
366
  // retry, compaction retry, and queued follow-up processing have finished.
394
367
 
395
368
  pi.on("input", (event) => {
396
- if (event.source !== "extension") taskDriver.onHumanInput();
369
+ if (event.source !== "extension") taskContinuation.onHumanInput();
397
370
  });
398
- pi.on("agent_start", () => { taskDriver.onAgentStart(); });
371
+ pi.on("agent_start", () => { taskContinuation.onAgentStart(); });
399
372
  pi.on("agent_settled", async (_event, ctx) => { await driveActiveTasks(ctx); });
400
373
 
401
374
  // ── "Are we there yet?" — inject active tasks into every turn ──────
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danypops/papyrus",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "Daemon-backed graph artifacts, evidence-bearing tasks, rules, skills, and native TUI workflows for Pi",
5
5
  "type": "module",
6
6
  "keywords": ["pi-package"],