@huseynsnmz/pi-suite 0.0.0-stage → 0.2.0
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 +28 -0
- package/LICENSE +21 -0
- package/README.md +88 -2
- package/SECURITY.md +5 -0
- package/package.json +112 -4
- package/packages/board/CHANGELOG.md +340 -0
- package/packages/board/CONTRIBUTING.md +7 -0
- package/packages/board/LICENSE +21 -0
- package/packages/board/README.md +88 -0
- package/packages/board/SECURITY.md +19 -0
- package/packages/board/docs/how-it-works.md +31 -0
- package/packages/board/docs/reference.md +278 -0
- package/packages/board/package.json +81 -0
- package/packages/board/runner/job-runner-legacy.mjs +68 -0
- package/packages/board/runner/job-runner.mjs +834 -0
- package/packages/board/runner/pty-runner-legacy.mjs +50 -0
- package/packages/board/runner/pty-runner.mjs +2175 -0
- package/packages/board/runner/state-coordinator.mjs +673 -0
- package/packages/board/runner/state-runner.mjs +203 -0
- package/packages/board/runner/title-runner.mjs +80 -0
- package/packages/board/src/commands/agent-board.ts +394 -0
- package/packages/board/src/commands/attach-decision.mjs +66 -0
- package/packages/board/src/commands/attach-flow.ts +251 -0
- package/packages/board/src/commands/bg.ts +103 -0
- package/packages/board/src/commands/board-model.ts +21 -0
- package/packages/board/src/core/atomic.mjs +184 -0
- package/packages/board/src/core/attach-delete.mjs +24 -0
- package/packages/board/src/core/attach-frame-proof.mjs +870 -0
- package/packages/board/src/core/attach-frame-reporter.mjs +648 -0
- package/packages/board/src/core/attach-frame-stream.mjs +266 -0
- package/packages/board/src/core/attach-view-proof.mjs +709 -0
- package/packages/board/src/core/auto-state.mjs +377 -0
- package/packages/board/src/core/background-runs.mjs +273 -0
- package/packages/board/src/core/code-refs-store.mjs +315 -0
- package/packages/board/src/core/code-refs.mjs +913 -0
- package/packages/board/src/core/config.mjs +77 -0
- package/packages/board/src/core/control-protocol.mjs +482 -0
- package/packages/board/src/core/coordinator-client.mjs +633 -0
- package/packages/board/src/core/coordinator-journal.mjs +282 -0
- package/packages/board/src/core/coordinator-protocol.mjs +14 -0
- package/packages/board/src/core/cwd-stats.mjs +114 -0
- package/packages/board/src/core/dashboard-render.mjs +10 -0
- package/packages/board/src/core/derive.mjs +117 -0
- package/packages/board/src/core/diagnostics.mjs +133 -0
- package/packages/board/src/core/double-press.mjs +26 -0
- package/packages/board/src/core/editor-state-reporter.mjs +112 -0
- package/packages/board/src/core/events.mjs +286 -0
- package/packages/board/src/core/evidence.mjs +245 -0
- package/packages/board/src/core/follow-up-queue.mjs +211 -0
- package/packages/board/src/core/foreground-preview-cache.mjs +117 -0
- package/packages/board/src/core/foreground-projection-cache.mjs +99 -0
- package/packages/board/src/core/frame-witness-update.mjs +35 -0
- package/packages/board/src/core/heuristics.mjs +313 -0
- package/packages/board/src/core/hold-input.mjs +27 -0
- package/packages/board/src/core/host-coordination.mjs +300 -0
- package/packages/board/src/core/host-crash.mjs +79 -0
- package/packages/board/src/core/host-probe.mjs +207 -0
- package/packages/board/src/core/host-protocol.mjs +73 -0
- package/packages/board/src/core/ids.mjs +35 -0
- package/packages/board/src/core/ime-cursor-coalesce.mjs +193 -0
- package/packages/board/src/core/invocation.mjs +43 -0
- package/packages/board/src/core/launch-options.mjs +418 -0
- package/packages/board/src/core/launch.mjs +157 -0
- package/packages/board/src/core/load-config.mjs +4 -0
- package/packages/board/src/core/locks.mjs +530 -0
- package/packages/board/src/core/paths.mjs +183 -0
- package/packages/board/src/core/pid.mjs +75 -0
- package/packages/board/src/core/prewarm-schedule.mjs +41 -0
- package/packages/board/src/core/process-identity-probe.mjs +228 -0
- package/packages/board/src/core/prompt-transport.mjs +13 -0
- package/packages/board/src/core/pty-attach-jiggle-controller.mjs +356 -0
- package/packages/board/src/core/pty-attach-jiggle-retry.mjs +109 -0
- package/packages/board/src/core/pty-attach-reconnect.mjs +61 -0
- package/packages/board/src/core/pty-attach-render.mjs +229 -0
- package/packages/board/src/core/pty-links.mjs +81 -0
- package/packages/board/src/core/pty-scroll.mjs +522 -0
- package/packages/board/src/core/pty-support.mjs +327 -0
- package/packages/board/src/core/repo.mjs +103 -0
- package/packages/board/src/core/rows.mjs +414 -0
- package/packages/board/src/core/screen-log-gc.mjs +198 -0
- package/packages/board/src/core/screen-log.mjs +160 -0
- package/packages/board/src/core/session-view.mjs +174 -0
- package/packages/board/src/core/slash-commands.mjs +52 -0
- package/packages/board/src/core/state-commands.mjs +757 -0
- package/packages/board/src/core/status-consistency.mjs +98 -0
- package/packages/board/src/core/steering-prompts.mjs +34 -0
- package/packages/board/src/core/steering.mjs +133 -0
- package/packages/board/src/core/stop-latch.mjs +41 -0
- package/packages/board/src/core/store.mjs +523 -0
- package/packages/board/src/core/suspend.mjs +36 -0
- package/packages/board/src/core/terminal-attach-client.mjs +981 -0
- package/packages/board/src/core/terminal-attach-protocol.mjs +460 -0
- package/packages/board/src/core/terminal-model.mjs +222 -0
- package/packages/board/src/core/terminal-query-sequences.mjs +239 -0
- package/packages/board/src/core/terminal-reply-guard.mjs +222 -0
- package/packages/board/src/core/terminal-snapshot.mjs +440 -0
- package/packages/board/src/core/test-knobs.mjs +28 -0
- package/packages/board/src/core/title.mjs +43 -0
- package/packages/board/src/core/types.mjs +414 -0
- package/packages/board/src/core/warm-host-sweeper.mjs +168 -0
- package/packages/board/src/core/worktree.mjs +65 -0
- package/packages/board/src/index.ts +303 -0
- package/packages/board/src/runtime/service.mjs +3222 -0
- package/packages/board/src/ui/chrome.ts +20 -0
- package/packages/board/src/ui/dashboard-decisions.mjs +55 -0
- package/packages/board/src/ui/dashboard-evidence.mjs +85 -0
- package/packages/board/src/ui/dashboard.ts +2237 -0
- package/packages/board/src/ui/double-left.ts +30 -0
- package/packages/board/src/ui/external-editor.ts +25 -0
- package/packages/board/src/ui/focus.ts +32 -0
- package/packages/board/src/ui/pty-attach.ts +3184 -0
- package/packages/board/src/ui/waiting.ts +30 -0
- package/packages/commands/LICENSE +21 -0
- package/packages/commands/README.md +19 -0
- package/packages/commands/package.json +22 -0
- package/packages/commands/src/index.ts +43 -0
- package/packages/commands/src/security-review.ts +109 -0
- package/packages/frame/CHANGELOG.md +113 -0
- package/packages/frame/CONTRIBUTING.md +7 -0
- package/packages/frame/LICENSE +22 -0
- package/packages/frame/README.md +105 -0
- package/packages/frame/SECURITY.md +16 -0
- package/packages/frame/docs/how-it-works.md +53 -0
- package/packages/frame/docs/reference.md +78 -0
- package/packages/frame/package.json +72 -0
- package/packages/frame/src/data/git.ts +90 -0
- package/packages/frame/src/data/github.ts +140 -0
- package/packages/frame/src/data/gitlab.ts +63 -0
- package/packages/frame/src/data/mode.ts +49 -0
- package/packages/frame/src/data/past-prompts.ts +57 -0
- package/packages/frame/src/data/runner.ts +32 -0
- package/packages/frame/src/data/snapshot.ts +223 -0
- package/packages/frame/src/data/start-notes.ts +104 -0
- package/packages/frame/src/data/telemetry.ts +337 -0
- package/packages/frame/src/data/todos.ts +162 -0
- package/packages/frame/src/data/totals.ts +94 -0
- package/packages/frame/src/data/visits.ts +45 -0
- package/packages/frame/src/index.ts +696 -0
- package/packages/frame/src/kit/LICENSE +21 -0
- package/packages/frame/src/kit/app/attribution.ts +30 -0
- package/packages/frame/src/kit/app/builder.ts +186 -0
- package/packages/frame/src/kit/app/claim.ts +27 -0
- package/packages/frame/src/kit/app/feature.ts +91 -0
- package/packages/frame/src/kit/app/host.ts +214 -0
- package/packages/frame/src/kit/app/renderers.ts +62 -0
- package/packages/frame/src/kit/app/scope.ts +125 -0
- package/packages/frame/src/kit/board/model.ts +337 -0
- package/packages/frame/src/kit/contract.ts +79 -0
- package/packages/frame/src/kit/contracts/accounts.ts +33 -0
- package/packages/frame/src/kit/contracts/ask.ts +120 -0
- package/packages/frame/src/kit/contracts/board.ts +6 -0
- package/packages/frame/src/kit/contracts/by.ts +22 -0
- package/packages/frame/src/kit/contracts/calls.ts +46 -0
- package/packages/frame/src/kit/contracts/events.ts +89 -0
- package/packages/frame/src/kit/contracts/mode.ts +26 -0
- package/packages/frame/src/kit/contracts/names.ts +12 -0
- package/packages/frame/src/kit/contracts/peers.ts +77 -0
- package/packages/frame/src/kit/contracts/prompts.ts +8 -0
- package/packages/frame/src/kit/contracts/screen.ts +75 -0
- package/packages/frame/src/kit/contracts/skills.ts +81 -0
- package/packages/frame/src/kit/contracts/sources.ts +81 -0
- package/packages/frame/src/kit/contracts/views.ts +42 -0
- package/packages/frame/src/kit/control.ts +59 -0
- package/packages/frame/src/kit/dark-theme.ts +41 -0
- package/packages/frame/src/kit/decode.ts +210 -0
- package/packages/frame/src/kit/events/log.ts +313 -0
- package/packages/frame/src/kit/format.ts +25 -0
- package/packages/frame/src/kit/host.ts +4 -0
- package/packages/frame/src/kit/index.ts +25 -0
- package/packages/frame/src/kit/legacy-tasks.ts +39 -0
- package/packages/frame/src/kit/overlay-images.ts +36 -0
- package/packages/frame/src/kit/paths.ts +16 -0
- package/packages/frame/src/kit/screen/client.ts +74 -0
- package/packages/frame/src/kit/screen/feature.ts +64 -0
- package/packages/frame/src/kit/screen/model.ts +182 -0
- package/packages/frame/src/kit/screen/view.ts +838 -0
- package/packages/frame/src/kit/settings/files.ts +90 -0
- package/packages/frame/src/kit/settings/setting.ts +57 -0
- package/packages/frame/src/kit/settings/store.ts +192 -0
- package/packages/frame/src/kit/sources/file.ts +129 -0
- package/packages/frame/src/kit/sources/follow.ts +155 -0
- package/packages/frame/src/kit/testing.ts +260 -0
- package/packages/frame/src/kit/totals.ts +72 -0
- package/packages/frame/src/kit/ui/entry.ts +75 -0
- package/packages/frame/src/kit/ui/frame.ts +212 -0
- package/packages/frame/src/kit/ui/glyphs.ts +132 -0
- package/packages/frame/src/kit/ui/scroll.ts +14 -0
- package/packages/frame/src/render/diff.ts +157 -0
- package/packages/frame/src/render/fit.ts +86 -0
- package/packages/frame/src/render/format.ts +108 -0
- package/packages/frame/src/render/frame.ts +106 -0
- package/packages/frame/src/render/glyphs.ts +189 -0
- package/packages/frame/src/render/keys.ts +43 -0
- package/packages/frame/src/render/lead.ts +37 -0
- package/packages/frame/src/render/paint.ts +135 -0
- package/packages/frame/src/render/safe.ts +19 -0
- package/packages/frame/src/render/segments.ts +735 -0
- package/packages/frame/src/render/streaming.ts +223 -0
- package/packages/frame/src/render/timing.ts +106 -0
- package/packages/frame/src/render/tools.ts +167 -0
- package/packages/frame/src/settings.ts +105 -0
- package/packages/frame/src/ui/editor.ts +272 -0
- package/packages/frame/src/ui/effort.ts +36 -0
- package/packages/frame/src/ui/footer.ts +165 -0
- package/packages/frame/src/ui/header.ts +362 -0
- package/packages/frame/src/ui/history-search.ts +23 -0
- package/packages/frame/src/ui/interrupt.ts +21 -0
- package/packages/frame/src/ui/outputs.ts +170 -0
- package/packages/frame/src/ui/pi-entries.ts +298 -0
- package/packages/frame/src/ui/previews.ts +72 -0
- package/packages/frame/src/ui/queue.ts +75 -0
- package/packages/frame/src/ui/screen.ts +59 -0
- package/packages/frame/src/ui/timings.ts +48 -0
- package/packages/frame/src/ui/todos.ts +205 -0
- package/packages/frame/src/ui/tool-frame.ts +595 -0
- package/packages/frame/src/ui/working.ts +25 -0
- package/packages/frame/src/view/state.ts +97 -0
- package/packages/frame/src/view/transcript.ts +175 -0
- package/packages/frame/src/view/view.ts +162 -0
- package/packages/memory/CONTRIBUTING.md +7 -0
- package/packages/memory/LICENSE +21 -0
- package/packages/memory/README.md +44 -0
- package/packages/memory/SECURITY.md +20 -0
- package/packages/memory/package.json +55 -0
- package/packages/memory/skills/review-memory/SKILL.md +48 -0
- package/packages/memory/src/index.ts +163 -0
- package/packages/memory/src/prompt.ts +25 -0
- package/packages/memory/src/review.ts +150 -0
- package/packages/memory/src/secrets.ts +22 -0
- package/packages/memory/src/store.ts +173 -0
- package/packages/modes/CHANGELOG.md +104 -0
- package/packages/modes/CONTRIBUTING.md +7 -0
- package/packages/modes/LICENSE +21 -0
- package/packages/modes/README.md +174 -0
- package/packages/modes/SECURITY.md +17 -0
- package/packages/modes/package.json +55 -0
- package/packages/modes/skills/review-permissions/SKILL.md +54 -0
- package/packages/modes/src/bash.ts +218 -0
- package/packages/modes/src/deny-off.ts +62 -0
- package/packages/modes/src/git-safety.ts +72 -0
- package/packages/modes/src/index.ts +338 -0
- package/packages/modes/src/interview.ts +206 -0
- package/packages/modes/src/judge-gate.ts +113 -0
- package/packages/modes/src/modes.ts +121 -0
- package/packages/modes/src/review/apply.mjs +156 -0
- package/packages/modes/src/review/collect.mjs +208 -0
- package/packages/modes/src/review/tools.ts +71 -0
- package/packages/modes/src/sandbox.ts +37 -0
- package/packages/router/CHANGELOG.md +8 -0
- package/packages/router/LICENSE +21 -0
- package/packages/router/README.md +90 -0
- package/packages/router/package.json +51 -0
- package/packages/router/src/config.ts +65 -0
- package/packages/router/src/index.ts +172 -0
- package/packages/router/src/judge.ts +51 -0
- package/packages/router/src/route.ts +67 -0
- package/packages/web/CHANGELOG.md +36 -0
- package/packages/web/LICENSE +21 -0
- package/packages/web/README.md +87 -0
- package/packages/web/SECURITY.md +13 -0
- package/packages/web/package.json +59 -0
- package/packages/web/src/config.ts +109 -0
- package/packages/web/src/dnsfilter.ts +97 -0
- package/packages/web/src/extract.ts +65 -0
- package/packages/web/src/index.ts +217 -0
- package/packages/web/src/markdown.ts +122 -0
- package/packages/web/src/net.ts +109 -0
- package/packages/web/src/reader.ts +73 -0
- package/packages/web/src/sanitize.ts +50 -0
- package/packages/web/src/search.ts +76 -0
- package/packages/web/src/secrets.ts +22 -0
- package/packages/web/src/validate.ts +186 -0
- package/src/index.ts +156 -0
- package/src/settings.ts +79 -0
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
// The agent's checklist. Pure: the tool, the panel and the session all go through these.
|
|
2
|
+
import { plainText } from "../render/safe.ts";
|
|
3
|
+
|
|
4
|
+
export const TODO_ENTRY = "pi-frame:todo";
|
|
5
|
+
const MAX_ITEMS = 50;
|
|
6
|
+
const MAX_TEXT = 200;
|
|
7
|
+
|
|
8
|
+
const INVISIBLE = /[\u00ad\u034f\u061c\u180e\u200b-\u200f\u2060-\u206f\u3164\ufeff\ufff9-\ufffb\u{e0000}-\u{e007f}]/gu;
|
|
9
|
+
|
|
10
|
+
export type TodoStatus = "open" | "active" | "done";
|
|
11
|
+
|
|
12
|
+
export interface Todo {
|
|
13
|
+
id: number;
|
|
14
|
+
text: string;
|
|
15
|
+
status: TodoStatus;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface TodoList {
|
|
19
|
+
items: readonly Todo[];
|
|
20
|
+
nextId: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface TodoChange {
|
|
24
|
+
plan?: readonly string[];
|
|
25
|
+
add?: readonly string[];
|
|
26
|
+
drop?: readonly number[];
|
|
27
|
+
done?: readonly number[];
|
|
28
|
+
start?: number;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export const NO_TODOS: TodoList = { items: [], nextId: 1 };
|
|
32
|
+
|
|
33
|
+
/** Model text, drawn on one row, so it can't drive the terminal or the layout. */
|
|
34
|
+
function cleanText(text: string): string {
|
|
35
|
+
const clean = plainText(text).replace(INVISIBLE, "").replace(/\s+/g, " ").trim();
|
|
36
|
+
return clean.length > MAX_TEXT ? `${clean.slice(0, MAX_TEXT - 3)}...` : clean;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function added(texts: readonly string[], from: number): Todo[] {
|
|
40
|
+
return texts.map((text, index) => {
|
|
41
|
+
const clean = cleanText(text);
|
|
42
|
+
if (clean === "") throw new Error("A todo item needs text.");
|
|
43
|
+
return { id: from + index, text: clean, status: "open" };
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function checkedIds(list: TodoList, ids: readonly number[]): void {
|
|
48
|
+
const known = new Set(list.items.map((item) => item.id));
|
|
49
|
+
const missing = ids.filter((id) => !known.has(id));
|
|
50
|
+
if (missing.length > 0) throw new Error(`No todo item ${missing.join(", ")}.`);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** All or nothing: a bad id or text throws, with the list, which stays as it was. No arguments just reads it. */
|
|
54
|
+
export function changedTodos(list: TodoList, change: TodoChange): TodoList {
|
|
55
|
+
try {
|
|
56
|
+
return applied(list, change);
|
|
57
|
+
} catch (error) {
|
|
58
|
+
throw new Error(`${(error as Error).message} The list has not changed.\n${todoText(list)}`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function applied(list: TodoList, change: TodoChange): TodoList {
|
|
63
|
+
let items: readonly Todo[] = list.items;
|
|
64
|
+
let nextId = list.nextId;
|
|
65
|
+
if (change.plan !== undefined) {
|
|
66
|
+
items = added(change.plan, 1);
|
|
67
|
+
nextId = items.length + 1;
|
|
68
|
+
}
|
|
69
|
+
if (change.add !== undefined) {
|
|
70
|
+
items = [...items, ...added(change.add, nextId)];
|
|
71
|
+
nextId += change.add.length;
|
|
72
|
+
}
|
|
73
|
+
if (items.length > MAX_ITEMS) throw new Error(`A todo list holds at most ${MAX_ITEMS} items.`);
|
|
74
|
+
|
|
75
|
+
const next = (): TodoList => ({ items, nextId });
|
|
76
|
+
checkedIds(next(), change.drop ?? []);
|
|
77
|
+
items = items.filter((item) => !change.drop?.includes(item.id));
|
|
78
|
+
checkedIds(next(), change.done ?? []);
|
|
79
|
+
items = items.map((item) => (change.done?.includes(item.id) ? { ...item, status: "done" } : item));
|
|
80
|
+
|
|
81
|
+
const start = change.start;
|
|
82
|
+
if (start !== undefined) {
|
|
83
|
+
checkedIds(next(), [start]);
|
|
84
|
+
// Only one at a time, so starting one puts back whichever was active.
|
|
85
|
+
items = items.map((item) => {
|
|
86
|
+
if (item.id === start) return { ...item, status: "active" };
|
|
87
|
+
return item.status === "active" ? { ...item, status: "open" } : item;
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
return next();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function hasOpenTodos(list: TodoList): boolean {
|
|
94
|
+
return list.items.some((item) => item.status !== "done");
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function doneCount(list: TodoList): number {
|
|
98
|
+
return list.items.filter((item) => item.status === "done").length;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const MARKS: Record<TodoStatus, string> = { open: "[ ]", active: "[>]", done: "[x]" };
|
|
102
|
+
|
|
103
|
+
/** What the model reads. */
|
|
104
|
+
export function todoText(list: TodoList): string {
|
|
105
|
+
if (list.items.length === 0) return "The todo list is empty.";
|
|
106
|
+
const rows = list.items.map((item) => `${MARKS[item.status]} ${item.id} ${item.text}`);
|
|
107
|
+
return [`${doneCount(list)}/${list.items.length} done`, ...rows].join("\n");
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** The transcript's one line: what's being worked on, or how far it got. */
|
|
111
|
+
export function todoSummary(list: TodoList): string {
|
|
112
|
+
if (list.items.length === 0) return "list empty";
|
|
113
|
+
const active = list.items.find((item) => item.status === "active");
|
|
114
|
+
const count = `${doneCount(list)}/${list.items.length} done`;
|
|
115
|
+
return active === undefined ? count : `${count}, now: ${active.text}`;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function decoded(data: unknown): TodoList | undefined {
|
|
119
|
+
const list = data as { items?: unknown; nextId?: unknown } | null;
|
|
120
|
+
if (!Array.isArray(list?.items) || !Number.isSafeInteger(list.nextId)) return undefined;
|
|
121
|
+
if (list.items.length > MAX_ITEMS) return undefined;
|
|
122
|
+
const items: Todo[] = [];
|
|
123
|
+
const nextId = list.nextId as number;
|
|
124
|
+
const seen = new Set<number>();
|
|
125
|
+
for (const raw of list.items as Partial<Todo>[]) {
|
|
126
|
+
if (!Number.isSafeInteger(raw?.id) || typeof raw?.text !== "string") return undefined;
|
|
127
|
+
if (raw.status !== "open" && raw.status !== "active" && raw.status !== "done") return undefined;
|
|
128
|
+
const id = raw.id as number;
|
|
129
|
+
const text = cleanText(raw.text);
|
|
130
|
+
if (text === "" || id >= nextId || seen.has(id)) return undefined;
|
|
131
|
+
seen.add(id);
|
|
132
|
+
items.push({ id, text, status: raw.status });
|
|
133
|
+
}
|
|
134
|
+
return { items, nextId };
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** The last list saved on this branch. A session can come from anyone, so a bad entry is skipped. */
|
|
138
|
+
export function todosOnBranch(entries: Iterable<unknown>): TodoList {
|
|
139
|
+
let list = NO_TODOS;
|
|
140
|
+
for (const entry of entries) {
|
|
141
|
+
const custom = entry as { type?: string; customType?: string; data?: unknown };
|
|
142
|
+
if (custom.type !== "custom" || custom.customType !== TODO_ENTRY) continue;
|
|
143
|
+
list = decoded(custom.data) ?? list;
|
|
144
|
+
}
|
|
145
|
+
return list;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Compaction can take the tool's last answer out of the model's context while the list goes on.
|
|
150
|
+
* Then the list comes first, where it doesn't move between calls, which keeps most of the prompt cache.
|
|
151
|
+
* A failed call may not show the list, so only an answer that worked counts.
|
|
152
|
+
*/
|
|
153
|
+
export function withTodoReminder<M extends { role: string; toolName?: string; isError?: boolean }>(
|
|
154
|
+
messages: readonly M[],
|
|
155
|
+
list: TodoList,
|
|
156
|
+
reminder: (text: string) => M,
|
|
157
|
+
): M[] | undefined {
|
|
158
|
+
if (!hasOpenTodos(list)) return undefined;
|
|
159
|
+
const seen = (message: M) => message.role === "toolResult" && message.toolName === "todo" && message.isError !== true;
|
|
160
|
+
if (messages.some(seen)) return undefined;
|
|
161
|
+
return [reminder(`Your todo list, kept from earlier in this session:\n${todoText(list)}`), ...messages];
|
|
162
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { type UsageLike } from "../kit/index.ts";
|
|
2
|
+
|
|
3
|
+
export {
|
|
4
|
+
emptyTotals,
|
|
5
|
+
promptOf,
|
|
6
|
+
tokensOf,
|
|
7
|
+
totalsOf,
|
|
8
|
+
type Totals,
|
|
9
|
+
usageTotals,
|
|
10
|
+
} from "../kit/index.ts";
|
|
11
|
+
import type { SessionEntry } from "@earendil-works/pi-coding-agent";
|
|
12
|
+
|
|
13
|
+
export interface RequestRecord {
|
|
14
|
+
output: number;
|
|
15
|
+
prompt: number;
|
|
16
|
+
firstTokenMs: number;
|
|
17
|
+
generationMs: number;
|
|
18
|
+
/** For the line under the response. Older records don't have them. */
|
|
19
|
+
at?: number;
|
|
20
|
+
messageAt?: number;
|
|
21
|
+
serverMs?: number;
|
|
22
|
+
thoughtMs?: number;
|
|
23
|
+
totalMs?: number;
|
|
24
|
+
stallMs?: number;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const REQUEST_CHANGES = "pi-frame:request";
|
|
28
|
+
/** Entries from this package's old name. They have no `prompt`, so they count only for decode. */
|
|
29
|
+
const LEGACY_CHANGES = "pi-statusline:turn";
|
|
30
|
+
|
|
31
|
+
export interface Averages {
|
|
32
|
+
decode: number | undefined;
|
|
33
|
+
prefill: number | undefined;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function lastAssistantUsage(entries: readonly SessionEntry[]): UsageLike | undefined {
|
|
37
|
+
for (let index = entries.length - 1; index >= 0; index--) {
|
|
38
|
+
const entry = entries[index];
|
|
39
|
+
if (entry?.type !== "message" || entry.message.role !== "assistant") continue;
|
|
40
|
+
return entry.message.usage;
|
|
41
|
+
}
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function cacheHitPercent(entries: readonly SessionEntry[]): number | undefined {
|
|
46
|
+
const usage = lastAssistantUsage(entries);
|
|
47
|
+
if (usage === undefined) return undefined;
|
|
48
|
+
const prompt = usage.input + usage.cacheRead + usage.cacheWrite;
|
|
49
|
+
return prompt === 0 ? undefined : (usage.cacheRead / prompt) * 100;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function cacheWarming(entries: readonly SessionEntry[]): boolean {
|
|
53
|
+
const usage = lastAssistantUsage(entries);
|
|
54
|
+
return usage !== undefined && usage.cacheRead === 0 && usage.cacheWrite > 0;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function averagesOf(entries: readonly SessionEntry[]): Averages {
|
|
58
|
+
let output = 0;
|
|
59
|
+
let generationMs = 0;
|
|
60
|
+
let prompt = 0;
|
|
61
|
+
let waitingMs = 0;
|
|
62
|
+
|
|
63
|
+
for (const entry of entries) {
|
|
64
|
+
if (entry.type !== "custom") continue;
|
|
65
|
+
if (entry.customType !== REQUEST_CHANGES && entry.customType !== LEGACY_CHANGES) continue;
|
|
66
|
+
const record = entry.data;
|
|
67
|
+
if (!isRecord(record)) continue;
|
|
68
|
+
|
|
69
|
+
output += record.output;
|
|
70
|
+
generationMs += record.generationMs;
|
|
71
|
+
if (typeof record.prompt === "number" && record.prompt > 0) {
|
|
72
|
+
prompt += record.prompt;
|
|
73
|
+
waitingMs += record.firstTokenMs;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return { decode: perSecond(output, generationMs), prefill: perSecond(prompt, waitingMs) };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function perSecond(tokens: number, milliseconds: number): number | undefined {
|
|
81
|
+
return tokens > 0 && milliseconds > 0 ? tokens / (milliseconds / 1000) : undefined;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function isRecord(
|
|
85
|
+
data: unknown,
|
|
86
|
+
): data is Omit<RequestRecord, "prompt"> & { prompt?: number | undefined } {
|
|
87
|
+
if (typeof data !== "object" || data === null) return false;
|
|
88
|
+
const record = data as Partial<RequestRecord>;
|
|
89
|
+
return (
|
|
90
|
+
typeof record.output === "number" &&
|
|
91
|
+
typeof record.firstTokenMs === "number" &&
|
|
92
|
+
typeof record.generationMs === "number"
|
|
93
|
+
);
|
|
94
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// The start screen says what changed since the last session in a folder, so it remembers when that was.
|
|
2
|
+
import { mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { dirname } from "node:path";
|
|
4
|
+
|
|
5
|
+
import { isObject } from "../kit/index.ts";
|
|
6
|
+
|
|
7
|
+
import type { LastVisit } from "./start-notes.ts";
|
|
8
|
+
|
|
9
|
+
type Visits = Record<string, LastVisit>;
|
|
10
|
+
|
|
11
|
+
/** Records this visit too. */
|
|
12
|
+
export function visit(
|
|
13
|
+
file: string,
|
|
14
|
+
cwd: string,
|
|
15
|
+
version: string,
|
|
16
|
+
now: number,
|
|
17
|
+
): LastVisit | undefined {
|
|
18
|
+
const visits = read(file);
|
|
19
|
+
const last = visits[cwd];
|
|
20
|
+
visits[cwd] = { at: now, version };
|
|
21
|
+
// Two sessions starting together must not read each other's half-written file. It lists your folders.
|
|
22
|
+
const temporary = `${file}.${process.pid}.tmp`;
|
|
23
|
+
try {
|
|
24
|
+
mkdirSync(dirname(file), { recursive: true });
|
|
25
|
+
writeFileSync(temporary, JSON.stringify(visits), { mode: 0o600 });
|
|
26
|
+
renameSync(temporary, file);
|
|
27
|
+
} catch {
|
|
28
|
+
// A start screen that cannot remember only loses "since when".
|
|
29
|
+
rmSync(temporary, { force: true });
|
|
30
|
+
}
|
|
31
|
+
return isVisit(last) ? last : undefined;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function isVisit(value: unknown): value is LastVisit {
|
|
35
|
+
return isObject(value) && Number.isFinite(value.at) && typeof value.version === "string";
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function read(file: string): Visits {
|
|
39
|
+
try {
|
|
40
|
+
const value = JSON.parse(readFileSync(file, "utf8")) as unknown;
|
|
41
|
+
return isObject(value) ? (value as Visits) : {};
|
|
42
|
+
} catch {
|
|
43
|
+
return {};
|
|
44
|
+
}
|
|
45
|
+
}
|