@astrosheep/pi-goal-next 0.1.0 → 0.1.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/docs/architecture.md +1 -1
- package/package.json +1 -1
- package/src/accounting.ts +1 -1
- package/src/continuation.ts +2 -0
- package/src/lifecycle.ts +8 -1
package/docs/architecture.md
CHANGED
|
@@ -61,7 +61,7 @@ User messages win: `hasPendingMessages()` check + CAS conflict as backstop.
|
|
|
61
61
|
1. `session_start`: any restored `active` goal → commit a system transition to `paused` (the journal does not contain a separate `loaded` entry). Never silently resume; the continuation generation is not explicitly invalidated by this hook.
|
|
62
62
|
2. `session_before_tree`: tell continuation to void generation; next event triggers rebuild from `getBranch()` via goal-commit.
|
|
63
63
|
3. `session_before_compact`: append `goal.summarize()` text to the compaction if the hook allows (verify at implementation; continuation messages are self-contained regardless).
|
|
64
|
-
4. `agent_settled`: commit the accounting verdict, then — if the current goal is `budget_limited` and this goal instance has not been steered yet — send `budgetLimitPrompt(goal)` with `triggerTurn: true`. Steering is sent once per goal instance (`steeredGoalId`/`steered` closure state, reset on a null snapshot or a new goal id). Then run `continuation.onSettled()
|
|
64
|
+
4. `agent_settled`: commit the accounting verdict, then — if the current goal is `budget_limited` and this goal instance has not been steered yet — send `budgetLimitPrompt(goal)` with `triggerTurn: true`. Steering is sent once per goal instance (`steeredGoalId`/`steered` closure state, reset on a null snapshot or a new goal id). Then run `continuation.onSettled()`, except that it skips continuation when the previous turn ended error/aborted (structured `stopReason`; such turns produced no completed work).
|
|
65
65
|
|
|
66
66
|
No other business. Retry needs no lifecycle handling — accounting dedupes by message id; retry messages have new ids and are honestly counted (they cost real tokens).
|
|
67
67
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@astrosheep/pi-goal-next",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Persistent autonomous goals for pi, with Codex-verbatim goal semantics: continuation prompts, self-audited completion, budgets, and CAS-journaled state.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
package/src/accounting.ts
CHANGED
|
@@ -7,7 +7,7 @@ export type Usage = {
|
|
|
7
7
|
cacheWrite?: number | null;
|
|
8
8
|
[key: string]: unknown;
|
|
9
9
|
};
|
|
10
|
-
export type Message = { entryId: string; role: "assistant" | "toolResult"; usage?: Usage | null; toolName?: string };
|
|
10
|
+
export type Message = { entryId: string; role: "assistant" | "toolResult"; usage?: Usage | null; toolName?: string; stopReason?: string };
|
|
11
11
|
export type Delta = { input: number; output: number; cacheRead: number; cacheWrite: number; unknownMessages: number; stale: boolean };
|
|
12
12
|
export type Verdict = { kind: "ok" } | { kind: "budget_limited"; reason: string };
|
|
13
13
|
|
package/src/continuation.ts
CHANGED
|
@@ -43,6 +43,8 @@ export function createContinuation(deps: ContinuationDeps): Continuation {
|
|
|
43
43
|
const seq = goal.continuationSeq + 1;
|
|
44
44
|
const result = await deps.commit({ type: "continuation_sent", generation: leaseGeneration }, revision);
|
|
45
45
|
if (result.kind !== "ok" || generation !== leaseGeneration) return;
|
|
46
|
+
const latest = deps.getSnapshot();
|
|
47
|
+
if (!latest || latest.goal.id !== goal.id || latest.goal.status !== "active") return; // user may have cleared/paused during the commit await
|
|
46
48
|
|
|
47
49
|
deps.send({
|
|
48
50
|
customType: "pi-goal-next/continuation",
|
package/src/lifecycle.ts
CHANGED
|
@@ -31,7 +31,9 @@ function messageFromEvent(event: any, ctx: any): Message | null {
|
|
|
31
31
|
const m = candidate?.message ?? candidate ?? message;
|
|
32
32
|
const entryId = m?.id ?? m?.entryId ?? message.id ?? message.entryId;
|
|
33
33
|
// Entry identity is heuristic-by-position because branch entries may omit message ids.
|
|
34
|
-
return typeof entryId === "string"
|
|
34
|
+
return typeof entryId === "string"
|
|
35
|
+
? { entryId, role: message.role, usage: m?.usage ?? message.usage, toolName: m?.toolName ?? message.toolName, stopReason: m?.stopReason ?? message.stopReason }
|
|
36
|
+
: null;
|
|
35
37
|
}
|
|
36
38
|
|
|
37
39
|
export type PiEvents = { on(name: string, handler: (event: any, ctx: any) => unknown): void };
|
|
@@ -49,6 +51,8 @@ export function registerLifecycle(pi: PiEvents, deps: LifecycleDeps): void {
|
|
|
49
51
|
// Budget-limit steering is sent once per goal instance; reset on null branch or a new goal id.
|
|
50
52
|
let steeredGoalId: string | null = null;
|
|
51
53
|
let steered = false;
|
|
54
|
+
// Stop reason of the most recent assistant turn; gates continuation on normal completion.
|
|
55
|
+
let lastAssistantStop: string | undefined;
|
|
52
56
|
|
|
53
57
|
pi.on("session_start", async (_event: SessionStartEvent, ctx: any) => {
|
|
54
58
|
await bootstrap();
|
|
@@ -80,6 +84,7 @@ export function registerLifecycle(pi: PiEvents, deps: LifecycleDeps): void {
|
|
|
80
84
|
await before(ctx);
|
|
81
85
|
const message = messageFromEvent(event, ctx);
|
|
82
86
|
if (!message) return;
|
|
87
|
+
if (message.role === "assistant") lastAssistantStop = message.stopReason;
|
|
83
88
|
const recorded = deps.accounting.recordMessage(message);
|
|
84
89
|
if (recorded && !recorded.duplicate) {
|
|
85
90
|
const delta = recorded.delta;
|
|
@@ -103,6 +108,8 @@ export function registerLifecycle(pi: PiEvents, deps: LifecycleDeps): void {
|
|
|
103
108
|
steered = true;
|
|
104
109
|
deps.send({ customType: "pi-goal-next/budget_limit", content: budgetLimitPrompt(goal), display: false, details: { goalId: goal.id } }, { triggerTurn: true });
|
|
105
110
|
}
|
|
111
|
+
// Error/aborted turns produced no completed work; only continue after a normally-finished turn (Codex parity).
|
|
112
|
+
if (lastAssistantStop === "error" || lastAssistantStop === "aborted") return;
|
|
106
113
|
await deps.continuation.onSettled();
|
|
107
114
|
});
|
|
108
115
|
pi.on("message_start", async (event: MessageStartEvent, ctx: any) => { await before(ctx); await deps.continuation.onMessageStart(event?.message ?? event); });
|