pi-better-harness 0.28.1 → 0.28.2
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/node_modules/pi-better-goal/README.md +1 -1
- package/node_modules/pi-better-goal/package.json +1 -1
- package/node_modules/pi-better-goal/src/goal-state.ts +1 -1
- package/node_modules/pi-better-goal/src/index.ts +26 -12
- package/node_modules/pi-better-goal/src/types.ts +2 -1
- package/package.json +2 -2
|
@@ -44,7 +44,7 @@ metadata:
|
|
|
44
44
|
workflow-role: coordinator
|
|
45
45
|
```
|
|
46
46
|
|
|
47
|
-
Invoke the skill with Pi's `/skill:name` command, or supervise it with `/goal /skill:name task`. The goal extension resolves the command against Pi's registry, persists its source, and expands the skill on kickoff and continuation (including after session resume). A bound command that disappears or changes source pauses the goal. `/goal /template task` also re-expands prompt templates on continuation; `/goal /extension-command task` dispatches the extension command once, then continues with ordinary goal supervision to avoid repeating side effects. Plain-language goals work as before. This command binding requires Pi 0.84.4 or later. Legacy slash-shaped goals without a binding pause on resume rather than running without the skill. `/workflow` shows a coordinator skill owner
|
|
47
|
+
Invoke the skill with Pi's `/skill:name` command, or supervise it with `/goal /skill:name task`. The goal extension resolves the command against Pi's registry, persists its source, and expands the skill on kickoff and continuation (including after session resume). A bound command that disappears or changes source pauses the goal. `/goal /template task` also re-expands prompt templates on continuation; `/goal /extension-command task` dispatches the extension command once, then continues with ordinary goal supervision to avoid repeating side effects. Plain-language goals work as before. This command binding requires Pi 0.84.4 or later. Legacy slash-shaped goals without a binding pause on resume rather than running without the skill. `/workflow` shows a coordinator skill owner. When the workflow is done, complete the goal (`update_goal`), which also releases ownership. When it hands back unfinished work, call `release_workflow`, or use `/workflow clear` to release ownership manually. `release_workflow` also pauses an active Goal bound to that coordinator as a handback, preserving its objective and incomplete status. Resuming that Goal (`/goal resume`, `alt+g`, or, with conversational resume on, the user's go-ahead such as "continue") rebinds the coordinator it was created with, provided the skill is still registered at its original source; automatic continuation and synthetic input never reacquire ownership. Unrelated ordinary Goals are unchanged.
|
|
48
48
|
|
|
49
49
|
A skill that is only an alias of a coordinator declares the target instead of a role:
|
|
50
50
|
|
|
@@ -66,7 +66,7 @@ function isGoalStatus(value: unknown): value is GoalStatus {
|
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
function isPauseReason(value: unknown): value is GoalPauseReason {
|
|
69
|
-
return value === "interrupt" || value === "permission-blocker";
|
|
69
|
+
return value === "interrupt" || value === "permission-blocker" || value === "handback";
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
function isGoalLike(value: unknown): value is GoalLike {
|
|
@@ -87,7 +87,8 @@ const GOAL_RESUME_RULE =
|
|
|
87
87
|
|
|
88
88
|
/** True when the agent may resume this goal with `goal_resume`. */
|
|
89
89
|
export function agentResumable(goal: GoalSnapshot | null, conversationalResume = true): boolean {
|
|
90
|
-
return conversationalResume && goal?.status === "paused" &&
|
|
90
|
+
return conversationalResume && goal?.status === "paused" &&
|
|
91
|
+
(goal.pauseReason === "interrupt" || goal.pauseReason === "handback");
|
|
91
92
|
}
|
|
92
93
|
|
|
93
94
|
/** Footer status for a paused goal, telling the user how to resume it. */
|
|
@@ -98,7 +99,9 @@ export function pausedGoalStatus(goal: GoalSnapshot, conversationalResume = true
|
|
|
98
99
|
|
|
99
100
|
function pausedGoalPrompt(goal: GoalSnapshot, conversationalResume: boolean): string {
|
|
100
101
|
return [
|
|
101
|
-
|
|
102
|
+
goal.pauseReason === "handback"
|
|
103
|
+
? `Pi Better Goal is paused because its workflow handed back unfinished work for the user's answer. Goal: ${goal.objective}`
|
|
104
|
+
: `Pi Better Goal is paused because the user pressed escape. Goal: ${goal.objective}`,
|
|
102
105
|
"Treat the user's messages as ordinary conversation: answer them, but do not continue the goal's work until it is resumed.",
|
|
103
106
|
conversationalResume ? GOAL_RESUME_RULE : "Only the user can resume this goal, with /goal resume or alt+g. A conversational go-ahead does not resume it.",
|
|
104
107
|
].join("\n");
|
|
@@ -456,15 +459,21 @@ export default function (pi: ExtensionAPI): void {
|
|
|
456
459
|
return `Cannot resume: /${goal.command.name} is no longer registered at its original source.`;
|
|
457
460
|
}
|
|
458
461
|
if (goal.command?.source === "skill") {
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
return `Reinvoke /${goal.command.name}
|
|
462
|
+
try {
|
|
463
|
+
goalWorkflowOwner(goal);
|
|
464
|
+
} catch (error) {
|
|
465
|
+
return `Cannot resume: ${error instanceof Error ? error.message : String(error)} Reinvoke /${goal.command.name}.`;
|
|
463
466
|
}
|
|
464
467
|
}
|
|
465
468
|
return null;
|
|
466
469
|
};
|
|
467
470
|
|
|
471
|
+
/** The workflow a skill-bound goal owns, re-resolved from its registered skill; null for other goals. */
|
|
472
|
+
const goalWorkflowOwner = (goal: GoalSnapshot): ReturnType<typeof currentWorkflowOwner> =>
|
|
473
|
+
goal.command?.source === "skill"
|
|
474
|
+
? workflowOwnerFromSkill(goal.command.name.slice("skill:".length), goal.command.path, registeredSkillPath)
|
|
475
|
+
: null;
|
|
476
|
+
|
|
468
477
|
/**
|
|
469
478
|
* The one resume path shared by `/goal resume`, the hotkey, and `goal_resume`:
|
|
470
479
|
* reactivate a paused goal or reopen an active no-progress hold.
|
|
@@ -498,6 +507,11 @@ export default function (pi: ExtensionAPI): void {
|
|
|
498
507
|
}
|
|
499
508
|
pi.appendEntry(EXTENSION_NAME, permissionRecord(current.goalId, "permission-release"));
|
|
500
509
|
}
|
|
510
|
+
// Resuming a skill-bound goal is explicit re-entry into its workflow: rebind the owner the
|
|
511
|
+
// goal was created with (released by a handback) before the continuation runs.
|
|
512
|
+
const owner = goalWorkflowOwner(current);
|
|
513
|
+
const activeOwner = getWorkflow(ctx);
|
|
514
|
+
if (owner && (activeOwner?.name !== owner.name || activeOwner.path !== owner.path)) recordWorkflow(owner);
|
|
501
515
|
setGoal(goal, ctx, source);
|
|
502
516
|
queueGoalContinuation(goal, ctx);
|
|
503
517
|
return { ok: true, goal };
|
|
@@ -1049,8 +1063,8 @@ export default function (pi: ExtensionAPI): void {
|
|
|
1049
1063
|
name: GOAL_RESUME_TOOL,
|
|
1050
1064
|
label: "Resume Goal",
|
|
1051
1065
|
description:
|
|
1052
|
-
"Resume the goal the user paused with escape, exactly like /goal resume. " + GOAL_RESUME_RULE,
|
|
1053
|
-
promptSnippet: "Resume the escape-paused goal, only on the user's clear go-ahead.",
|
|
1066
|
+
"Resume the goal the user paused with escape, or one its workflow handed back, exactly like /goal resume. " + GOAL_RESUME_RULE,
|
|
1067
|
+
promptSnippet: "Resume the escape-paused or handed-back goal, only on the user's clear go-ahead.",
|
|
1054
1068
|
parameters: Type.Object({
|
|
1055
1069
|
reason: Type.Optional(Type.String({ description: "Short quote or summary of the user's go-ahead." })),
|
|
1056
1070
|
}),
|
|
@@ -1115,7 +1129,7 @@ export default function (pi: ExtensionAPI): void {
|
|
|
1115
1129
|
pi.registerTool({
|
|
1116
1130
|
name: "release_workflow",
|
|
1117
1131
|
label: "Release Workflow",
|
|
1118
|
-
description: "Release the active skill-owned workflow
|
|
1132
|
+
description: "Release the active skill-owned workflow when it hands back unfinished work (a blocked unit awaiting the human). Its bound active Goal pauses; /goal resume rebinds the workflow. When the workflow is done, call update_goal complete instead, which also releases it.",
|
|
1119
1133
|
parameters: Type.Object({}),
|
|
1120
1134
|
async execute(_toolCallId, _params, _signal, _onUpdate, ctx) {
|
|
1121
1135
|
const owner = getWorkflow(ctx);
|
|
@@ -1125,13 +1139,13 @@ export default function (pi: ExtensionAPI): void {
|
|
|
1125
1139
|
if (goal?.status === "active" && goal.command?.source === "skill") {
|
|
1126
1140
|
const boundOwner = workflowOwnerFromSkill(goal.command.name.slice("skill:".length), goal.command.path, registeredSkillPath);
|
|
1127
1141
|
if (boundOwner?.name === owner.name && boundOwner.path === owner.path) {
|
|
1128
|
-
setGoal(goalWithStatus(goal, "paused"), ctx, "tool");
|
|
1142
|
+
setGoal(goalWithStatus(goal, "paused", undefined, "handback"), ctx, "tool");
|
|
1129
1143
|
resumeCommand = goal.command.name;
|
|
1130
1144
|
}
|
|
1131
1145
|
}
|
|
1132
1146
|
recordWorkflow(null);
|
|
1133
1147
|
return {
|
|
1134
|
-
content: [{ type: "text", text: `Released ${owner.name} workflow ownership.${resumeCommand ? ` Its Goal is paused, not complete
|
|
1148
|
+
content: [{ type: "text", text: `Released ${owner.name} workflow ownership.${resumeCommand ? ` Its Goal is paused, not complete; the user's go-ahead or /goal resume rebinds /${resumeCommand}.` : ""}` }],
|
|
1135
1149
|
details: { released: true, owner: owner.name, goalPaused: resumeCommand !== undefined },
|
|
1136
1150
|
};
|
|
1137
1151
|
},
|
|
@@ -1261,7 +1275,7 @@ export default function (pi: ExtensionAPI): void {
|
|
|
1261
1275
|
if (permissionPrompt && goal?.status === "paused") {
|
|
1262
1276
|
return { systemPrompt: `${event.systemPrompt}\n\n${permissionPrompt}` };
|
|
1263
1277
|
}
|
|
1264
|
-
const pausedInstruction = permissionPrompt || (goal?.status === "paused" && goal.pauseReason === "interrupt"
|
|
1278
|
+
const pausedInstruction = permissionPrompt || (goal?.status === "paused" && (goal.pauseReason === "interrupt" || goal.pauseReason === "handback")
|
|
1265
1279
|
? pausedGoalPrompt(goal, preferences.conversationalResume) : "");
|
|
1266
1280
|
if (!isPokeable(goal) && !owner) {
|
|
1267
1281
|
return pausedInstruction ? { systemPrompt: `${event.systemPrompt}\n\n${pausedInstruction}` } : undefined;
|
|
@@ -75,7 +75,8 @@ export type GoalStatus = "active" | "paused" | "budgetLimited" | "complete";
|
|
|
75
75
|
* requires those human controls, and releases just one same-scope retry while
|
|
76
76
|
* keeping its separate blocker/release history.
|
|
77
77
|
*/
|
|
78
|
-
|
|
78
|
+
/** Why a goal paused: escape, a permission hold, or a workflow handing back unfinished work for the user's answer. */
|
|
79
|
+
export type GoalPauseReason = "interrupt" | "permission-blocker" | "handback";
|
|
79
80
|
|
|
80
81
|
export interface GoalUsage {
|
|
81
82
|
tokensUsed: number;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-better-harness",
|
|
3
|
-
"version": "0.28.
|
|
3
|
+
"version": "0.28.2",
|
|
4
4
|
"description": "Pi extension bundle for a write sandbox, subagents, background tasks, SSH, goals, and structured plans.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
},
|
|
55
55
|
"dependencies": {
|
|
56
56
|
"pi-better-background-tasks": "0.8.0",
|
|
57
|
-
"pi-better-goal": "0.8.
|
|
57
|
+
"pi-better-goal": "0.8.4",
|
|
58
58
|
"pi-better-plan": "0.8.0",
|
|
59
59
|
"pi-better-sandbox": "0.12.0",
|
|
60
60
|
"pi-better-ssh": "0.1.2",
|