@mocito/pi-goal 0.1.10 → 0.1.11
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/CHANGELOG.md +8 -0
- package/package.json +4 -4
- package/src/commands.ts +4 -3
- package/src/metadata.ts +9 -1
- package/src/state.ts +44 -4
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.1.11 - 2026-07-17
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- Preserve accumulated wall-clock time when `/goal resume` is used on an already-active goal.
|
|
10
|
+
- Derive persisted `piGoalVersion` metadata from the package version so release versions cannot drift.
|
|
11
|
+
- Ignore malformed persisted mutations instead of allowing invalid status, budget, or accounting data to corrupt reconstructed goal state.
|
|
12
|
+
|
|
5
13
|
## 0.1.10 - 2026-07-01
|
|
6
14
|
|
|
7
15
|
### Changed
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mocito/pi-goal",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.11",
|
|
4
4
|
"description": "Persistent long-running goals for Pi.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -56,9 +56,9 @@
|
|
|
56
56
|
"@earendil-works/pi-ai": "^0.80.0",
|
|
57
57
|
"@earendil-works/pi-coding-agent": "^0.80.0",
|
|
58
58
|
"@earendil-works/pi-tui": "^0.80.0",
|
|
59
|
-
"@types/node": "^
|
|
60
|
-
"typebox": "^1.
|
|
61
|
-
"tsx": "^4.
|
|
59
|
+
"@types/node": "^26.1.0",
|
|
60
|
+
"typebox": "^1.3.4",
|
|
61
|
+
"tsx": "^4.23.0",
|
|
62
62
|
"typescript": "^6.0.3"
|
|
63
63
|
},
|
|
64
64
|
"publishConfig": {
|
package/src/commands.ts
CHANGED
|
@@ -94,9 +94,10 @@ async function editGoal(pi: ExtensionAPI, ctx: ExtensionCommandContext, runtime:
|
|
|
94
94
|
function setGoalStatus(pi: ExtensionAPI, ctx: ExtensionCommandContext, runtime: CommandRuntime, status: "paused" | "active"): void {
|
|
95
95
|
const current = accountCurrent(pi, ctx, runtime.getGoal());
|
|
96
96
|
if (!current) return ctx.ui.notify("No goal set.", "error");
|
|
97
|
-
const
|
|
98
|
-
const
|
|
99
|
-
const
|
|
97
|
+
const transitionedAt = nowIso();
|
|
98
|
+
const time = realizedTimeUsed(current, Date.parse(transitionedAt));
|
|
99
|
+
const activeStartedAt = status === "active" ? transitionedAt : undefined;
|
|
100
|
+
const mutation = statusMutation(current, status, time, activeStartedAt, transitionMeta(status === "active" ? "command:/goal resume" : "command:/goal pause", current, time, transitionedAt));
|
|
100
101
|
appendGoalMutation(pi, mutation);
|
|
101
102
|
runtime.setGoal(applyGoalMutation(current, mutation));
|
|
102
103
|
runtime.afterGoalChanged(ctx, status === "active" ? "Goal resumed." : "Goal paused.");
|
package/src/metadata.ts
CHANGED
|
@@ -1,7 +1,15 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
1
2
|
import type { BranchEntry, GoalMutationMeta, GoalState } from "./types.js";
|
|
2
3
|
import { secondsBetween } from "./utils.js";
|
|
3
4
|
|
|
4
|
-
|
|
5
|
+
const packageJson = createRequire(import.meta.url)("../package.json") as { version?: unknown };
|
|
6
|
+
|
|
7
|
+
if (typeof packageJson.version !== "string" || packageJson.version.length === 0) {
|
|
8
|
+
throw new Error("pi-goal package version is missing or invalid.");
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** Package version that produced the persisted metadata; schema versioning is separate. */
|
|
12
|
+
export const PI_GOAL_VERSION = packageJson.version;
|
|
5
13
|
|
|
6
14
|
export function withPiGoalVersion(meta: GoalMutationMeta = {}): GoalMutationMeta {
|
|
7
15
|
return { piGoalVersion: PI_GOAL_VERSION, ...meta };
|
package/src/state.ts
CHANGED
|
@@ -70,10 +70,50 @@ export function applyGoalMutation(current: GoalState | null, mutation: GoalMutat
|
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
function isKnownMutation(value: Partial<GoalMutation> | undefined): value is GoalMutation {
|
|
73
|
-
if (!value || value.schemaVersion !== GOAL_SCHEMA_VERSION || typeof value.kind !== "string" ||
|
|
74
|
-
|
|
75
|
-
if (
|
|
76
|
-
|
|
73
|
+
if (!value || value.schemaVersion !== GOAL_SCHEMA_VERSION || typeof value.kind !== "string" || !validTimestamp(value.at)) return false;
|
|
74
|
+
const mutation = value as Record<string, unknown>;
|
|
75
|
+
if (value.kind === "clear") return optionalNonNegativeInteger(mutation.timeUsedSeconds);
|
|
76
|
+
if (typeof mutation.goalId !== "string" || mutation.goalId.length === 0) return false;
|
|
77
|
+
switch (value.kind) {
|
|
78
|
+
case "create":
|
|
79
|
+
case "replace":
|
|
80
|
+
return typeof mutation.objective === "string" && optionalPositiveInteger(mutation.tokenBudget);
|
|
81
|
+
case "edit":
|
|
82
|
+
return typeof mutation.objective === "string" && (mutation.status === undefined || isGoalStatus(mutation.status));
|
|
83
|
+
case "status":
|
|
84
|
+
return isGoalStatus(mutation.status)
|
|
85
|
+
&& optionalNonNegativeInteger(mutation.timeUsedSeconds)
|
|
86
|
+
&& (mutation.activeStartedAt === undefined || validTimestamp(mutation.activeStartedAt));
|
|
87
|
+
case "budget":
|
|
88
|
+
return optionalPositiveInteger(mutation.tokenBudget);
|
|
89
|
+
case "account":
|
|
90
|
+
return nonNegativeInteger(mutation.tokens)
|
|
91
|
+
&& Array.isArray(mutation.entryIds)
|
|
92
|
+
&& mutation.entryIds.every((id) => typeof id === "string");
|
|
93
|
+
default:
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function isGoalStatus(value: unknown): value is GoalStatus {
|
|
99
|
+
return typeof value === "string" && ["active", "paused", "blocked", "usage_limited", "budget_limited", "complete"].includes(value);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function validTimestamp(value: unknown): value is string {
|
|
103
|
+
return typeof value === "string" && Number.isFinite(Date.parse(value));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function nonNegativeInteger(value: unknown): value is number {
|
|
107
|
+
return typeof value === "number" && Number.isFinite(value) && Number.isInteger(value) && value >= 0;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function optionalNonNegativeInteger(value: unknown): boolean {
|
|
111
|
+
return value === undefined || nonNegativeInteger(value);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function optionalPositiveInteger(value: unknown): boolean {
|
|
115
|
+
return value === undefined
|
|
116
|
+
|| (typeof value === "number" && Number.isFinite(value) && Number.isInteger(value) && value > 0);
|
|
77
117
|
}
|
|
78
118
|
|
|
79
119
|
export function statusMutation(goal: GoalState, status: GoalStatus, timeUsedSeconds: number, activeStartedAt?: string, meta?: GoalMutationMeta): GoalMutation {
|