@quantmind-br/pi-goal-x 0.0.0-stage → 0.35.1
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/LICENSE +21 -0
- package/README.md +131 -2
- package/assets/badge-dark.svg +18 -0
- package/assets/badge-light.svg +18 -0
- package/docs/advanced-usage.md +106 -0
- package/docs/agent-flow-design.md +306 -0
- package/docs/agentic-runtime-prd.md +773 -0
- package/docs/architecture.md +368 -0
- package/docs/controls.md +140 -0
- package/docs/pi-autoresearch-survey.md +50 -0
- package/docs/pi-goal-x-unified-dashboard-plan.md +1667 -0
- package/docs/prompt-caching.md +31 -0
- package/docs/unified-dashboard.md +286 -0
- package/extensions/auditor-selector.ts +74 -0
- package/extensions/goal-accounting.ts +107 -0
- package/extensions/goal-activity.ts +202 -0
- package/extensions/goal-auditor.ts +480 -0
- package/extensions/goal-commands.ts +866 -0
- package/extensions/goal-compaction.ts +197 -0
- package/extensions/goal-completion.ts +173 -0
- package/extensions/goal-context.ts +12 -0
- package/extensions/goal-contract.ts +58 -0
- package/extensions/goal-controls.ts +202 -0
- package/extensions/goal-core-tools.ts +549 -0
- package/extensions/goal-core.ts +78 -0
- package/extensions/goal-detail.ts +65 -0
- package/extensions/goal-dialog-controls.ts +31 -0
- package/extensions/goal-draft.ts +244 -0
- package/extensions/goal-drafting.ts +535 -0
- package/extensions/goal-events.ts +692 -0
- package/extensions/goal-format.ts +376 -0
- package/extensions/goal-interruption.ts +53 -0
- package/extensions/goal-ledger-index.ts +68 -0
- package/extensions/goal-ledger.ts +1007 -0
- package/extensions/goal-live-retention.ts +239 -0
- package/extensions/goal-oracle.ts +346 -0
- package/extensions/goal-policy.ts +370 -0
- package/extensions/goal-pool.ts +103 -0
- package/extensions/goal-prompt-cache.ts +80 -0
- package/extensions/goal-questionnaire.ts +1073 -0
- package/extensions/goal-record.ts +388 -0
- package/extensions/goal-recovery.ts +256 -0
- package/extensions/goal-runtime.ts +277 -0
- package/extensions/goal-scheduler-state.ts +100 -0
- package/extensions/goal-scheduler.ts +436 -0
- package/extensions/goal-service.ts +843 -0
- package/extensions/goal-session-health.ts +130 -0
- package/extensions/goal-session-safety.ts +65 -0
- package/extensions/goal-settings.ts +1370 -0
- package/extensions/goal-state.ts +1069 -0
- package/extensions/goal-status.ts +305 -0
- package/extensions/goal-task-confirmation.ts +174 -0
- package/extensions/goal-task-count.ts +59 -0
- package/extensions/goal-task-derive.ts +46 -0
- package/extensions/goal-task-index.ts +57 -0
- package/extensions/goal-task-tools.ts +595 -0
- package/extensions/goal-tool-inputs.ts +102 -0
- package/extensions/goal-tool-names.ts +73 -0
- package/extensions/goal-tools.ts +16 -0
- package/extensions/goal-widget.ts +455 -0
- package/extensions/goal.ts +42 -0
- package/extensions/network-error-backoff.ts +55 -0
- package/extensions/prompts/goal-prompts.ts +330 -0
- package/extensions/storage/atomic-rename.ts +52 -0
- package/extensions/storage/goal-files.ts +695 -0
- package/extensions/storage/goal-lock.ts +131 -0
- package/extensions/storage/goal-root.ts +95 -0
- package/extensions/widgets/auditor-dashboard-model.ts +174 -0
- package/extensions/widgets/dialog-scaffold.ts +26 -0
- package/extensions/widgets/goal-controls-dialog.ts +329 -0
- package/extensions/widgets/goal-dashboard-model.ts +530 -0
- package/extensions/widgets/goal-dashboard-renderer.ts +674 -0
- package/extensions/widgets/goal-escape-dialog.ts +148 -0
- package/extensions/widgets/goal-notifications.ts +9 -0
- package/extensions/widgets/goal-widget.ts +723 -0
- package/extensions/widgets/task-list-overlay.ts +409 -0
- package/extensions/widgets/text-cache.ts +30 -0
- package/package.json +117 -4
- package/scripts/recover-session-checkpoints.mjs +289 -0
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Issue #30 — checkpoint health diagnostics for existing session files.
|
|
3
|
+
*
|
|
4
|
+
* Read-only inspection of a session JSONL's persisted continuation
|
|
5
|
+
* checkpoints. Pre-fix sessions accumulated one FULL continuation prompt per
|
|
6
|
+
* auto-continue turn (~6.4K chars each); v2 checkpoints are bounded markers.
|
|
7
|
+
* The report quantifies what an offline repair with pi-goal-x-recover would
|
|
8
|
+
* save. This module never writes anything.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import * as fs from "node:fs";
|
|
12
|
+
import { GOAL_EVENT_ENTRY } from "./goal-format.ts";
|
|
13
|
+
import { asRecord } from "./goal-record.ts";
|
|
14
|
+
|
|
15
|
+
/** Exact shape written by checkpointTriggerPrompt() (v2 markers only). */
|
|
16
|
+
const V2_MARKER_PATTERN =
|
|
17
|
+
/^<pi_goal_continuation\s+goal_id="[^"]+"\s+kind="checkpoint"\s+v="2"\s*\/>$/;
|
|
18
|
+
|
|
19
|
+
/** Approximate persisted size of one repaired marker, used for projections. */
|
|
20
|
+
export const REPAIRED_MARKER_ESTIMATE_CHARS = "<pi_goal_continuation goal_id=xxxxxxxxxxxxxxxxxxxx kind=\"checkpoint\" v=\"2\"/>".length;
|
|
21
|
+
|
|
22
|
+
export interface CheckpointHealth {
|
|
23
|
+
total: number;
|
|
24
|
+
legacyFull: number;
|
|
25
|
+
v2Minimal: number;
|
|
26
|
+
totalContentChars: number;
|
|
27
|
+
recoverableChars: number;
|
|
28
|
+
largestCheckpointChars: number;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function emptyHealth(): CheckpointHealth {
|
|
32
|
+
return {
|
|
33
|
+
total: 0,
|
|
34
|
+
legacyFull: 0,
|
|
35
|
+
v2Minimal: 0,
|
|
36
|
+
totalContentChars: 0,
|
|
37
|
+
recoverableChars: 0,
|
|
38
|
+
largestCheckpointChars: 0,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function isCheckpointEntry(entry: unknown): { content: string; version: number | undefined } | null {
|
|
43
|
+
const raw = asRecord(entry);
|
|
44
|
+
if (!raw || raw.type !== "custom_message") return null;
|
|
45
|
+
if (raw.customType !== GOAL_EVENT_ENTRY) return null;
|
|
46
|
+
if (typeof raw.content !== "string") return null;
|
|
47
|
+
const details = asRecord(raw.details);
|
|
48
|
+
return { content: raw.content, version: typeof details?.version === "number" ? details.version : undefined };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Classify every pi-goal-event custom message in parsed session entries.
|
|
53
|
+
* v2 entries match the exact bounded marker shape AND carry details.version 2;
|
|
54
|
+
* everything else is legacy full-prompt payload that offline recovery can
|
|
55
|
+
* shrink.
|
|
56
|
+
*/
|
|
57
|
+
export function inspectCheckpointHealth(entries: readonly unknown[]): CheckpointHealth {
|
|
58
|
+
const health = emptyHealth();
|
|
59
|
+
for (const entry of entries) accumulateCheckpointHealth(health, entry);
|
|
60
|
+
return health;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function accumulateCheckpointHealth(health: CheckpointHealth, entry: unknown): void {
|
|
64
|
+
const checkpoint = isCheckpointEntry(entry);
|
|
65
|
+
if (!checkpoint) return;
|
|
66
|
+
health.total += 1;
|
|
67
|
+
health.totalContentChars += checkpoint.content.length;
|
|
68
|
+
if (checkpoint.content.length > health.largestCheckpointChars) {
|
|
69
|
+
health.largestCheckpointChars = checkpoint.content.length;
|
|
70
|
+
}
|
|
71
|
+
const isV2 = (checkpoint.version === 2 || checkpoint.version === 3) && V2_MARKER_PATTERN.test(checkpoint.content);
|
|
72
|
+
if (isV2) {
|
|
73
|
+
health.v2Minimal += 1;
|
|
74
|
+
} else {
|
|
75
|
+
health.legacyFull += 1;
|
|
76
|
+
health.recoverableChars += checkpoint.content.length;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Convenience wrapper: read a session JSONL file from disk (JSON per line,
|
|
82
|
+
* header included) and inspect its checkpoints. Returns null when the file
|
|
83
|
+
* does not exist or cannot be read/parsed at the line level — malformed lines
|
|
84
|
+
* are skipped, not fatal, mirroring the recovery CLI's tolerance.
|
|
85
|
+
*/
|
|
86
|
+
export function readSessionCheckpointHealth(sessionFile: string): CheckpointHealth | null {
|
|
87
|
+
let raw: string;
|
|
88
|
+
try {
|
|
89
|
+
raw = fs.readFileSync(sessionFile, "utf8");
|
|
90
|
+
} catch {
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
const health = emptyHealth();
|
|
94
|
+
// Parse and discard one entry at a time; do not retain the whole session object graph.
|
|
95
|
+
for (const rawLine of raw.split("\n")) {
|
|
96
|
+
const line = rawLine.trim();
|
|
97
|
+
if (line) try { accumulateCheckpointHealth(health, JSON.parse(line)); } catch { /* tolerate malformed lines */ }
|
|
98
|
+
}
|
|
99
|
+
return health;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** Human-readable projection of post-recovery checkpoint content size. */
|
|
103
|
+
export function projectedContentCharsAfterRecovery(health: CheckpointHealth): number {
|
|
104
|
+
return health.v2Minimal * REPAIRED_MARKER_ESTIMATE_CHARS;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function formatChars(chars: number): string {
|
|
108
|
+
if (chars >= 1_000_000) return `${(chars / 1_000_000).toFixed(1)} MB`;
|
|
109
|
+
if (chars >= 1_000) return `${(chars / 1_000).toFixed(1)} KB`;
|
|
110
|
+
return `${chars} chars`;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Render the read-only report section. The command layer appends this to
|
|
115
|
+
* /goal-recovery output and /goal-status health when checkpoint data exists.
|
|
116
|
+
*/
|
|
117
|
+
export function formatCheckpointHealthReport(health: CheckpointHealth, sessionFile?: string): string {
|
|
118
|
+
const lines: string[] = [];
|
|
119
|
+
lines.push("Session checkpoints:");
|
|
120
|
+
lines.push(` total: ${health.total}`);
|
|
121
|
+
lines.push(` legacy full checkpoints: ${health.legacyFull}`);
|
|
122
|
+
lines.push(` v2 minimal markers: ${health.v2Minimal}`);
|
|
123
|
+
lines.push(` checkpoint content: ${formatChars(health.totalContentChars)}`);
|
|
124
|
+
lines.push(` projected content after recovery: ${formatChars(projectedContentCharsAfterRecovery(health))}`);
|
|
125
|
+
if (health.legacyFull > 0 && sessionFile) {
|
|
126
|
+
lines.push(" close Pi, then run:");
|
|
127
|
+
lines.push(` pi-goal-x-recover --session <path> --dry-run`);
|
|
128
|
+
}
|
|
129
|
+
return lines.join("\n");
|
|
130
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { GOAL_AUDIT_ENTRY, goalEventMessageId } from "./goal-format.ts";
|
|
3
|
+
|
|
4
|
+
export function isStaleExtensionContextError(error: unknown): boolean {
|
|
5
|
+
return error instanceof Error && error.message.startsWith("This extension ctx is stale after session replacement or reload.");
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/** pi-subagents marks fresh, forked, resumed and nested worker processes. */
|
|
9
|
+
export function isDelegatedGoalSession(env: Record<string, string | undefined> = process.env): boolean {
|
|
10
|
+
const depth = Number(env.PI_SUBAGENT_DEPTH);
|
|
11
|
+
return env.PI_SUBAGENT_CHILD === "1" || (Number.isFinite(depth) && depth > 0);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** Display-only audits must never become user messages between a tool and its result. */
|
|
15
|
+
export function filterGoalSessionContext<T>(messages: readonly T[], child = false): T[] | null {
|
|
16
|
+
let filtered: T[] | null = null;
|
|
17
|
+
for (let i = 0; i < messages.length; i++) {
|
|
18
|
+
const message = messages[i]!;
|
|
19
|
+
const entry = message as { role?: string; customType?: string; details?: unknown; content?: unknown } | null;
|
|
20
|
+
const remove = entry && typeof entry === "object" && entry.role === "custom"
|
|
21
|
+
&& (entry.customType === GOAL_AUDIT_ENTRY || (child && goalEventMessageId(entry) !== null));
|
|
22
|
+
if (remove) filtered ??= messages.slice(0, i);
|
|
23
|
+
else filtered?.push(message);
|
|
24
|
+
}
|
|
25
|
+
return filtered;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
type TranscriptMessage = Parameters<ExtensionAPI["sendMessage"]>[0];
|
|
29
|
+
type SessionKey = string | ExtensionContext["sessionManager"];
|
|
30
|
+
const sessionKey = (ctx: ExtensionContext): SessionKey => {
|
|
31
|
+
const manager = ctx.sessionManager;
|
|
32
|
+
return manager.getSessionId?.() ?? manager;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/** Transcript output waits for the entire agent run, including tool results, to settle. */
|
|
36
|
+
export class GoalAuditMessages {
|
|
37
|
+
private pending: { session: SessionKey; message: TranscriptMessage }[] = [];
|
|
38
|
+
|
|
39
|
+
enqueue(ctx: ExtensionContext, message: TranscriptMessage): void {
|
|
40
|
+
this.pending.push({ session: sessionKey(ctx), message });
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
clear(): void {
|
|
44
|
+
this.pending = [];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** False means the entire settlement callback belongs to an invalidated host. */
|
|
48
|
+
flush(ctx: ExtensionContext, pi: ExtensionAPI): boolean {
|
|
49
|
+
try {
|
|
50
|
+
// Check both getters before changing the queue: a late callback from
|
|
51
|
+
// an old runner must not discard the replacement session's messages.
|
|
52
|
+
const session = sessionKey(ctx);
|
|
53
|
+
const idle = ctx.isIdle();
|
|
54
|
+
this.pending = this.pending.filter(item => item.session === session);
|
|
55
|
+
if (!idle) return true;
|
|
56
|
+
const pending = this.pending;
|
|
57
|
+
this.pending = [];
|
|
58
|
+
for (const item of pending) pi.sendMessage(item.message, { triggerTurn: false });
|
|
59
|
+
return true;
|
|
60
|
+
} catch (error) {
|
|
61
|
+
if (isStaleExtensionContextError(error)) return false;
|
|
62
|
+
throw error;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|