@plannotator/pi-extension 0.28.37 → 0.28.39
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/generated/ai/providers/ask-ai-env.ts +217 -0
- package/generated/ai/providers/claude-agent-sdk.ts +19 -0
- package/generated/ai/providers/codex-app-server.ts +13 -5
- package/generated/ai/providers/opencode-sdk.ts +13 -5
- package/generated/ai/providers/pi-events.ts +1 -1
- package/generated/ai/providers/pi-sdk-node.ts +5 -0
- package/generated/ai/providers/pi-sdk.ts +6 -2
- package/generated/ai/providers/qwen-sdk.ts +5 -1
- package/generated/call-flow-languages.ts +4 -2
- package/generated/call-flow-runtime/package-lock.json +7 -7
- package/generated/call-flow-runtime/package.json +2 -2
- package/generated/call-flow.ts +149 -32
- package/generated/child-env.ts +12 -0
- package/generated/claude-review.ts +3 -0
- package/generated/codex-review.ts +3 -0
- package/generated/guide-review.ts +3 -0
- package/generated/guide-viewer-manifest.ts +4 -4
- package/generated/inbox/connection.ts +59 -16
- package/generated/project.ts +3 -2
- package/generated/question-block.ts +2 -2
- package/generated/tour-review.ts +3 -0
- package/generated/update-check.ts +113 -14
- package/package.json +1 -1
- package/plannotator.html +452 -451
- package/review-editor.html +450 -424
- package/server/agent-jobs-ask-ai-child.test.ts +149 -0
- package/server/agent-jobs.ts +5 -1
- package/server/serverReview.ts +4 -0
- package/skills/plannotator/SKILL.md +3 -3
|
@@ -84,6 +84,15 @@ const RELEASE_CHECK_TIMEOUT_MS = 8_000;
|
|
|
84
84
|
*/
|
|
85
85
|
export const RELEASE_ASSETS_FETCH_INTERVAL_MS = 24 * 60 * 60 * 1000;
|
|
86
86
|
export const RELEASE_ASSETS_FETCH_RETRY_MS = 6 * 60 * 60 * 1000;
|
|
87
|
+
/**
|
|
88
|
+
* A release's notes answered with no "What's New" part while the release is
|
|
89
|
+
* young (published under RELEASE_NOTES_YOUNG_MS ago): GitHub's generated
|
|
90
|
+
* body, read in the minutes before the written notes replaced it. Asked again
|
|
91
|
+
* after this instead of RELEASE_ASSETS_FETCH_INTERVAL_MS, so the "What's new"
|
|
92
|
+
* link shows within the hour, not a day later.
|
|
93
|
+
*/
|
|
94
|
+
export const RELEASE_NOTES_PENDING_RETRY_MS = 15 * 60 * 1000;
|
|
95
|
+
export const RELEASE_NOTES_YOUNG_MS = 2 * 24 * 60 * 60 * 1000;
|
|
87
96
|
/** "Remind me tomorrow" in the Inbox. */
|
|
88
97
|
export const UPDATE_SNOOZE_MS = 24 * 60 * 60 * 1000;
|
|
89
98
|
/** The terminal hint shows at most this often. */
|
|
@@ -145,6 +154,34 @@ export function releaseUrlFor(version: string): string {
|
|
|
145
154
|
// State (update-state.json, shared with auto-update)
|
|
146
155
|
// ---------------------------------------------------------------------------
|
|
147
156
|
|
|
157
|
+
/** One version's notes fetch (`notesFetch[<version>]`). */
|
|
158
|
+
export interface NotesFetchRecord {
|
|
159
|
+
attemptedAt: number;
|
|
160
|
+
failedAt?: number;
|
|
161
|
+
error?: string;
|
|
162
|
+
/**
|
|
163
|
+
* The release's `published_at` (epoch ms), kept when its answer carried no
|
|
164
|
+
* "What's New" part: while it is under RELEASE_NOTES_YOUNG_MS old, the next
|
|
165
|
+
* try waits RELEASE_NOTES_PENDING_RETRY_MS instead of a day.
|
|
166
|
+
*/
|
|
167
|
+
publishedAt?: number;
|
|
168
|
+
/**
|
|
169
|
+
* Set on the claim, gone once GitHub answers: a claim still marked was
|
|
170
|
+
* abandoned (its process quit mid-request), and is asked again after
|
|
171
|
+
* RELEASE_NOTES_PENDING_RETRY_MS, not read as an empty answer (24 h).
|
|
172
|
+
*/
|
|
173
|
+
inFlight?: boolean;
|
|
174
|
+
/**
|
|
175
|
+
* Set on a failure that may have been the network (not a rate limit, and
|
|
176
|
+
* no release check answered between the failure before it and this one):
|
|
177
|
+
* asked again RELEASE_NOTES_PENDING_RETRY_MS after it once a release check
|
|
178
|
+
* has answered since (the network is back), not after 6 h. One quick retry,
|
|
179
|
+
* then 6 h: a source that keeps failing while the network is up (or a
|
|
180
|
+
* network that drops again before the quick retry) waits 6 h.
|
|
181
|
+
*/
|
|
182
|
+
retryOnline?: boolean;
|
|
183
|
+
}
|
|
184
|
+
|
|
148
185
|
export interface UpdateState {
|
|
149
186
|
/** Auto-update's install window (epoch ms of the last check that got past its gates). */
|
|
150
187
|
lastCheckAt?: number;
|
|
@@ -217,17 +254,24 @@ export interface UpdateState {
|
|
|
217
254
|
* skipped from it, so "Also in the releases since X" survives the update.
|
|
218
255
|
*/
|
|
219
256
|
inboxPreviousVersion?: string;
|
|
257
|
+
/**
|
|
258
|
+
* The version an Inbox start found newer than `inboxVersion`, kept until its
|
|
259
|
+
* "What's new" link was offered with readable notes: a start on that same
|
|
260
|
+
* version shows the link too, so a restart before the notes were readable
|
|
261
|
+
* does not lose it.
|
|
262
|
+
*/
|
|
263
|
+
whatsNewPending?: string;
|
|
220
264
|
/**
|
|
221
265
|
* One release's notes fetched by its tag (fetchReleaseNotesFor), per
|
|
222
266
|
* version: claimed before the request, so concurrent processes ask once.
|
|
223
267
|
*/
|
|
224
|
-
notesFetch?: Record<string,
|
|
268
|
+
notesFetch?: Record<string, NotesFetchRecord>;
|
|
225
269
|
/**
|
|
226
270
|
* The Inbox's fetch of one release's images (packages/server/inbox-whats-new-images.ts),
|
|
227
|
-
* per version, with the same claim and back-off as `notesFetch
|
|
228
|
-
* Inbox writes it; nothing here fetches an image.
|
|
271
|
+
* per version, with the same claim and back-off as `notesFetch` (its
|
|
272
|
+
* `inFlight` mark too). Only the Inbox writes it; nothing here fetches an image.
|
|
229
273
|
*/
|
|
230
|
-
releaseImagesFetch?: Record<string,
|
|
274
|
+
releaseImagesFetch?: Record<string, Pick<NotesFetchRecord, "attemptedAt" | "failedAt" | "error" | "inFlight">>;
|
|
231
275
|
/**
|
|
232
276
|
* Safe updates: installs of `version` whose download did not match the
|
|
233
277
|
* checksum Plannotator fetched (install.sh exit 113), counted once each.
|
|
@@ -1045,17 +1089,41 @@ export function scheduleReleaseCheck(deps: ReleaseCheckDeps, then?: (outcome: Re
|
|
|
1045
1089
|
/** How many versions `notesFetch` remembers. */
|
|
1046
1090
|
const NOTES_FETCH_REMEMBERED = 10;
|
|
1047
1091
|
|
|
1092
|
+
/** The record names a release young enough for the quick retry (a clock far off either way does not). */
|
|
1093
|
+
function isYoungRelease(record: NotesFetchRecord, now: number): boolean {
|
|
1094
|
+
const published = record.publishedAt;
|
|
1095
|
+
return typeof published === "number" && Number.isFinite(published) && Math.abs(now - published) < RELEASE_NOTES_YOUNG_MS;
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
/**
|
|
1099
|
+
* A release check answered after `at` (the network is up again since then).
|
|
1100
|
+
* An answer dated after `now` (the clock moved back) does not count.
|
|
1101
|
+
*/
|
|
1102
|
+
function releaseCheckAnsweredAfter(state: UpdateState, at: number, now: number): boolean {
|
|
1103
|
+
const answered = state.latestRelease?.fetchedAt;
|
|
1104
|
+
return typeof answered === "number" && answered > at && answered <= now;
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1048
1107
|
/**
|
|
1049
1108
|
* Whether one version's notes may be asked for now: once, then after a
|
|
1050
|
-
* failure no sooner than 6 h
|
|
1051
|
-
*
|
|
1052
|
-
*
|
|
1109
|
+
* failure no sooner than 6 h (15 minutes once the network is back, see
|
|
1110
|
+
* `retryOnline`), after an answer that carried no "What's New" part no
|
|
1111
|
+
* sooner than 15 minutes while the release is under 2 days old (the release
|
|
1112
|
+
* body is edited after publishing), else 24 h. A claim nobody answered (its
|
|
1113
|
+
* process quit) waits 15 minutes. Never sooner than 15 minutes, never in a loop.
|
|
1053
1114
|
*/
|
|
1054
1115
|
export function isNotesFetchDue(state: UpdateState, version: string, now: number): boolean {
|
|
1055
1116
|
const record = state.notesFetch?.[version];
|
|
1056
1117
|
const at = record?.attemptedAt;
|
|
1057
1118
|
if (!record || typeof at !== "number" || !Number.isFinite(at) || at > now) return true;
|
|
1058
|
-
|
|
1119
|
+
const wait = record.failedAt
|
|
1120
|
+
? record.retryOnline && releaseCheckAnsweredAfter(state, record.failedAt, now)
|
|
1121
|
+
? RELEASE_NOTES_PENDING_RETRY_MS
|
|
1122
|
+
: RELEASE_ASSETS_FETCH_RETRY_MS
|
|
1123
|
+
: record.inFlight || isYoungRelease(record, now)
|
|
1124
|
+
? RELEASE_NOTES_PENDING_RETRY_MS
|
|
1125
|
+
: RELEASE_ASSETS_FETCH_INTERVAL_MS;
|
|
1126
|
+
return now - at >= wait;
|
|
1059
1127
|
}
|
|
1060
1128
|
|
|
1061
1129
|
export interface ReleaseNotesFetchDeps {
|
|
@@ -1088,7 +1156,7 @@ export async function fetchReleaseNotesFor(deps: ReleaseNotesFetchDeps): Promise
|
|
|
1088
1156
|
if (hasReleaseNotesContent(stored)) return { outcome: "present", notes: stored };
|
|
1089
1157
|
if (!deps.enabled()) return { outcome: "disabled" };
|
|
1090
1158
|
const now = (deps.now ?? Date.now)();
|
|
1091
|
-
const record = (patch:
|
|
1159
|
+
const record = (patch: NotesFetchRecord) =>
|
|
1092
1160
|
patchUpdateState((s) => {
|
|
1093
1161
|
const entries = Object.entries({ ...(s.notesFetch ?? {}), [version]: patch })
|
|
1094
1162
|
.filter(([, r]) => r && typeof r.attemptedAt === "number")
|
|
@@ -1096,10 +1164,18 @@ export async function fetchReleaseNotesFor(deps: ReleaseNotesFetchDeps): Promise
|
|
|
1096
1164
|
.slice(0, NOTES_FETCH_REMEMBERED);
|
|
1097
1165
|
return { ...s, notesFetch: Object.fromEntries(entries) };
|
|
1098
1166
|
}, dataDir);
|
|
1167
|
+
let pendingBefore = false;
|
|
1168
|
+
let previous: NotesFetchRecord | undefined;
|
|
1169
|
+
let state: UpdateState;
|
|
1099
1170
|
try {
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1171
|
+
state = readUpdateState(dataDir);
|
|
1172
|
+
if (!isNotesFetchDue(state, version, now)) return { outcome: "not-due" };
|
|
1173
|
+
// Claim the attempt before the network call so concurrent processes ask
|
|
1174
|
+
// once. Marked in flight, so an attempt that never finished (the process
|
|
1175
|
+
// quit) is tried again on the quick cadence, not in a day.
|
|
1176
|
+
previous = state.notesFetch?.[version];
|
|
1177
|
+
pendingBefore = !!previous && !previous.failedAt && isYoungRelease(previous, now);
|
|
1178
|
+
record({ attemptedAt: now, inFlight: true, ...(pendingBefore && { publishedAt: previous!.publishedAt }) });
|
|
1103
1179
|
} catch (err) {
|
|
1104
1180
|
const error = err instanceof Error ? err.message : String(err);
|
|
1105
1181
|
updateLog(`could not write update-state.json: ${error}`, dataDir);
|
|
@@ -1109,9 +1185,14 @@ export async function fetchReleaseNotesFor(deps: ReleaseNotesFetchDeps): Promise
|
|
|
1109
1185
|
const tag = result.ok ? normalizeStableVersion(result.release.tag) : null;
|
|
1110
1186
|
if (!result.ok || tag !== version) {
|
|
1111
1187
|
const error = result.ok ? `the release answered for ${JSON.stringify(result.release.tag).slice(0, 80)}` : result.error;
|
|
1112
|
-
|
|
1188
|
+
// Maybe the network (a laptop waking, a VPN): asked again 15 minutes after,
|
|
1189
|
+
// once a release check answers. Not after a rate limit, and not when this
|
|
1190
|
+
// source failed before with a release check answering since (it keeps
|
|
1191
|
+
// failing while the network is up: 6 h).
|
|
1192
|
+
const retryOnline = !(!result.ok && result.rateLimited) && !(previous?.failedAt && releaseCheckAnsweredAfter(state, previous.failedAt, now));
|
|
1193
|
+
updateLog(`could not read the release notes for ${version} (${error}); next try in ${retryOnline ? "15m once online, else 6h" : "6h"}`, dataDir);
|
|
1113
1194
|
try {
|
|
1114
|
-
record({ attemptedAt: now, failedAt: now, error: error.slice(0, 200) });
|
|
1195
|
+
record({ attemptedAt: now, failedAt: now, error: error.slice(0, 200), ...(retryOnline && { retryOnline }) });
|
|
1115
1196
|
} catch {}
|
|
1116
1197
|
return { outcome: "failed", error };
|
|
1117
1198
|
}
|
|
@@ -1123,6 +1204,24 @@ export async function fetchReleaseNotesFor(deps: ReleaseNotesFetchDeps): Promise
|
|
|
1123
1204
|
} catch (err) {
|
|
1124
1205
|
updateLog(`could not save the release notes: ${err instanceof Error ? err.message : String(err)}`, dataDir);
|
|
1125
1206
|
}
|
|
1207
|
+
if (!notes) {
|
|
1208
|
+
// GitHub's generated body, read before the written notes replaced it (or
|
|
1209
|
+
// a release with none): a young release is asked again in 15 minutes.
|
|
1210
|
+
const published = release.publishedAt ? Date.parse(release.publishedAt) : NaN;
|
|
1211
|
+
const pending: NotesFetchRecord = { attemptedAt: now, ...(Number.isFinite(published) && { publishedAt: published }) };
|
|
1212
|
+
try {
|
|
1213
|
+
record(pending);
|
|
1214
|
+
} catch {}
|
|
1215
|
+
// One line per version, not one per retry.
|
|
1216
|
+
if (isYoungRelease(pending, now) && !pendingBefore) {
|
|
1217
|
+
updateLog(`the release notes for ${version} have no "What's New" part yet; next try in ${formatWait(RELEASE_NOTES_PENDING_RETRY_MS)}`, dataDir);
|
|
1218
|
+
}
|
|
1219
|
+
} else {
|
|
1220
|
+
// Answered: no longer in flight (it matters only when the notes could not be saved).
|
|
1221
|
+
try {
|
|
1222
|
+
record({ attemptedAt: now });
|
|
1223
|
+
} catch {}
|
|
1224
|
+
}
|
|
1126
1225
|
// The release page names no publish date: take this answer's for the latest release.
|
|
1127
1226
|
if (release.publishedAt) {
|
|
1128
1227
|
try {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@plannotator/pi-extension",
|
|
3
|
-
"version": "0.28.
|
|
3
|
+
"version": "0.28.39",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Plannotator Pi extension - interactive plan review with annotations, annotate agent messages, and review code/PRs",
|
|
6
6
|
"author": "backnotprop",
|