@astrosheep/pi-goal-next 0.1.3 → 0.1.4
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 +1 -1
- package/docs/architecture.md +1 -1
- package/package.json +1 -1
- package/src/commands.ts +7 -1
- package/src/goal-commit.ts +1 -1
- package/src/goal.ts +8 -5
package/README.md
CHANGED
|
@@ -61,7 +61,7 @@ The prompt text is copied byte-for-byte from Codex Goal (`continuation.md`, `bud
|
|
|
61
61
|
|
|
62
62
|
## Limits and accounting
|
|
63
63
|
|
|
64
|
-
- The default maximum is 25 continuation turns (`maxContinuations`). Change it with `/goal turns N`.
|
|
64
|
+
- The default maximum is 25 continuation turns (`maxContinuations`) per run. Change it with `/goal turns N`. `/goal resume` resets the run's continuation count and immediately schedules work when idle. It preserves the objective, journal history, cumulative usage, and token budget. If the token budget is exhausted or the continuation allowance is zero, resume reports the limit instead of claiming success.
|
|
65
65
|
- A token budget is unset by default. Set one at creation with `/goal --tokens N[k|M] ...` or later with `/goal budget N`.
|
|
66
66
|
- Usage is attributed by message entry id, separately for assistant and tool-result messages. Duplicate entry ids are ignored.
|
|
67
67
|
- Missing provider usage is recorded as unknown, never treated as confirmed zero.
|
package/docs/architecture.md
CHANGED
|
@@ -88,7 +88,7 @@ The blocked audit is prompt-level only: the runtime does not count blocking turn
|
|
|
88
88
|
|
|
89
89
|
`/goal` or `/goal status` · `/goal [--tokens N[k|M]] <objective>` (create; refuses while unfinished) · `/goal edit <objective>` · `/goal pause` · `/goal resume` · `/goal clear` · `/goal budget <tokens|none>` · `/goal turns <max-continuations>`.
|
|
90
90
|
|
|
91
|
-
A successful `create` or `resume` calls `continuation.onSettled()` so an idle session starts pursuing immediately (Codex starts the turn directly). A successful `edit` sends `objectiveUpdatedPrompt(goal)` with `triggerTurn: true`; `pause` and `clear` send nothing (the active-status check stops continuation).
|
|
91
|
+
A successful `resume` atomically journals `resetContinuations: true` on the user transition to active, resetting the run's continuation count while preserving the objective, usage, budget, and historical entries. It also accepts an already-active goal. Exhausted token budgets and zero continuation allowances are reported without resuming. A successful `create` or `resume` calls `continuation.onSettled()` so an idle session starts pursuing immediately (Codex starts the turn directly). A successful `edit` sends `objectiveUpdatedPrompt(goal)` with `triggerTurn: true`; `pause` and `clear` send nothing (the active-status check stops continuation).
|
|
92
92
|
|
|
93
93
|
## Defaults
|
|
94
94
|
|
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.4",
|
|
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/commands.ts
CHANGED
|
@@ -49,7 +49,13 @@ export function registerGoalCommands(piLike: CommandPiLike, deps: CommandDeps):
|
|
|
49
49
|
case "clear": return finish(await goalCommit.commit({ type: "clear" }, revision), "Goal cleared.");
|
|
50
50
|
case "pause": return finish(await goalCommit.commit({ type: "transition", to: "paused", by: "user", userRequest: '"/goal pause"' }, revision), "Goal paused.");
|
|
51
51
|
case "resume": {
|
|
52
|
-
|
|
52
|
+
if (!current) return "Cannot resume: no goal exists.";
|
|
53
|
+
const goal = current.goal;
|
|
54
|
+
if (goal.status === "complete") return "Cannot resume: goal is complete.";
|
|
55
|
+
const used = goal.usage.input + goal.usage.output + goal.usage.cacheRead + goal.usage.cacheWrite;
|
|
56
|
+
if (goal.tokenBudget !== null && used >= goal.tokenBudget) return "Cannot resume: token budget exhausted. Adjust /goal budget first; cumulative usage is preserved.";
|
|
57
|
+
if (goal.maxContinuations === 0) return "Cannot resume: continuation allowance is zero. Adjust /goal turns first.";
|
|
58
|
+
const r = await goalCommit.commit({ type: "transition", to: "active", by: "user", resetContinuations: true }, revision);
|
|
53
59
|
if (r.kind === "ok") await deps.kick();
|
|
54
60
|
return r.kind === "ok" ? "Goal resumed." : "Goal update failed.";
|
|
55
61
|
}
|
package/src/goal-commit.ts
CHANGED
|
@@ -39,7 +39,7 @@ export function createGoalCommit(store: GoalStore) {
|
|
|
39
39
|
switch (intent.type) {
|
|
40
40
|
case "create": return { type: "goal.created", version: 1, seq, goal: next! };
|
|
41
41
|
case "clear": return { type: "goal.cleared", version: 1, seq };
|
|
42
|
-
case "transition": return { type: "goal.transition", version: 1, seq, from: previous!.status, to: intent.to, by: intent.by, ...(intent.userRequest ? { userRequest: intent.userRequest } : {}) };
|
|
42
|
+
case "transition": return { type: "goal.transition", version: 1, seq, from: previous!.status, to: intent.to, by: intent.by, ...(intent.userRequest ? { userRequest: intent.userRequest } : {}), ...(intent.resetContinuations ? { resetContinuations: true } : {}) };
|
|
43
43
|
case "usage": return { type: "goal.usage", version: 1, seq, input: intent.input ?? null, output: intent.output ?? null, cacheRead: intent.cacheRead ?? null, cacheWrite: intent.cacheWrite ?? null, unknownMessages: intent.unknownMessages ?? 0 };
|
|
44
44
|
case "continuation_sent": return { type: "goal.continuation_sent", version: 1, seq, generation: intent.generation };
|
|
45
45
|
case "stale_turn": return { type: "goal.stale_turn", version: 1, seq, generation: intent.generation };
|
package/src/goal.ts
CHANGED
|
@@ -7,7 +7,7 @@ export type Goal = {
|
|
|
7
7
|
};
|
|
8
8
|
export type Entry =
|
|
9
9
|
| { type: "goal.created"; version: 1; seq: number; goal: Goal }
|
|
10
|
-
| { type: "goal.transition"; version: 1; seq: number; from: Status; to: Status; by: Actor; userRequest?: string }
|
|
10
|
+
| { type: "goal.transition"; version: 1; seq: number; from: Status; to: Status; by: Actor; userRequest?: string; resetContinuations?: boolean }
|
|
11
11
|
| { type: "goal.cleared"; version: 1; seq: number }
|
|
12
12
|
| { type: "goal.usage"; version: 1; seq: number; input: number | null; output: number | null; cacheRead: number | null; cacheWrite: number | null; unknownMessages: number }
|
|
13
13
|
| { type: "goal.continuation_sent"; version: 1; seq: number; generation: number }
|
|
@@ -15,7 +15,7 @@ export type Entry =
|
|
|
15
15
|
| { type: "goal.limit_config"; version: 1; seq: number; tokenBudget: number | null; maxContinuations: number };
|
|
16
16
|
export type Intent =
|
|
17
17
|
| { type: "create"; id: string; objective: string; tokenBudget?: number | null; maxContinuations?: number }
|
|
18
|
-
| { type: "transition"; to: Status; by: Actor; userRequest?: string }
|
|
18
|
+
| { type: "transition"; to: Status; by: Actor; userRequest?: string; resetContinuations?: boolean }
|
|
19
19
|
| { type: "clear" }
|
|
20
20
|
| { type: "usage"; input?: number | null; output?: number | null; cacheRead?: number | null; cacheWrite?: number | null; unknownMessages?: number }
|
|
21
21
|
| { type: "continuation_sent"; generation: number }
|
|
@@ -55,8 +55,11 @@ export function transition(state: Goal | null, intent: Intent): Goal | null {
|
|
|
55
55
|
if (intent.to === "paused" && !intent.userRequest?.trim()) throw new GoalError("forbidden", "paused requires user request evidence");
|
|
56
56
|
if (intent.by === "agent" && !["complete", "blocked"].includes(intent.to)) throw new GoalError("forbidden", "agent cannot set this status");
|
|
57
57
|
if (intent.by === "agent" && intent.to === "complete" && state.status !== "active") throw new GoalError("forbidden", "agent can complete only an active goal");
|
|
58
|
-
|
|
59
|
-
|
|
58
|
+
const reset = intent.resetContinuations === true;
|
|
59
|
+
if (reset && (intent.by !== "user" || intent.to !== "active")) throw new GoalError("forbidden", "only user resume can reset continuations");
|
|
60
|
+
if (reset && state.tokenBudget !== null && usageKeys.reduce((sum, key) => sum + state.usage[key], 0) >= state.tokenBudget) throw new GoalError("budget", "token budget exhausted; adjust /goal budget before resuming");
|
|
61
|
+
if (state.status === "complete" || (intent.to === state.status && !reset)) throw new GoalError("illegal", "illegal status transition");
|
|
62
|
+
return { ...state, status: intent.to, ...(reset ? { continuationSeq: 0 } : {}) };
|
|
60
63
|
}
|
|
61
64
|
if (intent.type === "limit_config") {
|
|
62
65
|
if (!Number.isInteger(intent.maxContinuations) || intent.maxContinuations < 0 || (intent.tokenBudget !== null && (!Number.isFinite(intent.tokenBudget) || intent.tokenBudget < 0))) throw new GoalError("invalid", "invalid limits");
|
|
@@ -95,7 +98,7 @@ export function fold(entries: readonly Entry[]): Goal | null {
|
|
|
95
98
|
if (!entry.type.startsWith("goal.")) continue;
|
|
96
99
|
if (entry.type === "goal.created") state = { ...entry.goal };
|
|
97
100
|
else if (entry.type === "goal.cleared") state = null;
|
|
98
|
-
else if (state && entry.type === "goal.transition") state = transition(state, { type: "transition", to: entry.to, by: entry.by, userRequest: entry.userRequest });
|
|
101
|
+
else if (state && entry.type === "goal.transition") state = transition(state, { type: "transition", to: entry.to, by: entry.by, userRequest: entry.userRequest, resetContinuations: entry.resetContinuations });
|
|
99
102
|
else if (state && entry.type === "goal.limit_config") state = transition(state, { type: "limit_config", tokenBudget: entry.tokenBudget, maxContinuations: entry.maxContinuations });
|
|
100
103
|
else if (state && entry.type === "goal.continuation_sent") state = transition(state, { type: "continuation_sent", generation: entry.generation });
|
|
101
104
|
else if (state && entry.type === "goal.usage") state = transition(state, { type: "usage", input: entry.input, output: entry.output, cacheRead: entry.cacheRead, cacheWrite: entry.cacheWrite, unknownMessages: entry.unknownMessages });
|