@herbertgao/pi-extensions 2026.8.9 → 2026.8.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/README.md +4 -2
- package/node_modules/@herbertgao/resume-from/LICENSE +21 -0
- package/node_modules/@herbertgao/resume-from/README.md +163 -0
- package/node_modules/@herbertgao/resume-from/dist/adapters/claude-code/adapter.d.ts +25 -0
- package/node_modules/@herbertgao/resume-from/dist/adapters/claude-code/adapter.js +60 -0
- package/node_modules/@herbertgao/resume-from/dist/adapters/claude-code/contract.d.ts +8 -0
- package/node_modules/@herbertgao/resume-from/dist/adapters/claude-code/contract.js +4 -0
- package/node_modules/@herbertgao/resume-from/dist/adapters/claude-code/entries.d.ts +30 -0
- package/node_modules/@herbertgao/resume-from/dist/adapters/claude-code/entries.js +107 -0
- package/node_modules/@herbertgao/resume-from/dist/adapters/claude-code/index.d.ts +3 -0
- package/node_modules/@herbertgao/resume-from/dist/adapters/claude-code/index.js +2 -0
- package/node_modules/@herbertgao/resume-from/dist/adapters/claude-code/layout.d.ts +29 -0
- package/node_modules/@herbertgao/resume-from/dist/adapters/claude-code/layout.js +80 -0
- package/node_modules/@herbertgao/resume-from/dist/adapters/claude-code/readback.d.ts +9 -0
- package/node_modules/@herbertgao/resume-from/dist/adapters/claude-code/readback.js +39 -0
- package/node_modules/@herbertgao/resume-from/dist/adapters/claude-code/reader.d.ts +37 -0
- package/node_modules/@herbertgao/resume-from/dist/adapters/claude-code/reader.js +424 -0
- package/node_modules/@herbertgao/resume-from/dist/adapters/claude-code/redaction.d.ts +26 -0
- package/node_modules/@herbertgao/resume-from/dist/adapters/claude-code/redaction.js +132 -0
- package/node_modules/@herbertgao/resume-from/dist/adapters/claude-code/validation.d.ts +4 -0
- package/node_modules/@herbertgao/resume-from/dist/adapters/claude-code/validation.js +39 -0
- package/node_modules/@herbertgao/resume-from/dist/adapters/claude-code/writer.d.ts +23 -0
- package/node_modules/@herbertgao/resume-from/dist/adapters/claude-code/writer.js +306 -0
- package/node_modules/@herbertgao/resume-from/dist/adapters/codex/contract.d.ts +15 -0
- package/node_modules/@herbertgao/resume-from/dist/adapters/codex/contract.js +4 -0
- package/node_modules/@herbertgao/resume-from/dist/adapters/codex/index.d.ts +6 -0
- package/node_modules/@herbertgao/resume-from/dist/adapters/codex/index.js +44 -0
- package/node_modules/@herbertgao/resume-from/dist/adapters/codex/read.d.ts +28 -0
- package/node_modules/@herbertgao/resume-from/dist/adapters/codex/read.js +270 -0
- package/node_modules/@herbertgao/resume-from/dist/adapters/codex/readback.d.ts +9 -0
- package/node_modules/@herbertgao/resume-from/dist/adapters/codex/readback.js +47 -0
- package/node_modules/@herbertgao/resume-from/dist/adapters/codex/redaction.d.ts +26 -0
- package/node_modules/@herbertgao/resume-from/dist/adapters/codex/redaction.js +132 -0
- package/node_modules/@herbertgao/resume-from/dist/adapters/codex/rollout.d.ts +75 -0
- package/node_modules/@herbertgao/resume-from/dist/adapters/codex/rollout.js +185 -0
- package/node_modules/@herbertgao/resume-from/dist/adapters/codex/validation.d.ts +7 -0
- package/node_modules/@herbertgao/resume-from/dist/adapters/codex/validation.js +78 -0
- package/node_modules/@herbertgao/resume-from/dist/adapters/codex/write.d.ts +17 -0
- package/node_modules/@herbertgao/resume-from/dist/adapters/codex/write.js +141 -0
- package/node_modules/@herbertgao/resume-from/dist/adapters/contract.d.ts +74 -0
- package/node_modules/@herbertgao/resume-from/dist/adapters/contract.js +4 -0
- package/node_modules/@herbertgao/resume-from/dist/adapters/pi/adapter.d.ts +15 -0
- package/node_modules/@herbertgao/resume-from/dist/adapters/pi/adapter.js +209 -0
- package/node_modules/@herbertgao/resume-from/dist/adapters/pi/contract.d.ts +24 -0
- package/node_modules/@herbertgao/resume-from/dist/adapters/pi/contract.js +4 -0
- package/node_modules/@herbertgao/resume-from/dist/adapters/pi/format.d.ts +162 -0
- package/node_modules/@herbertgao/resume-from/dist/adapters/pi/format.js +223 -0
- package/node_modules/@herbertgao/resume-from/dist/adapters/pi/index.d.ts +7 -0
- package/node_modules/@herbertgao/resume-from/dist/adapters/pi/index.js +10 -0
- package/node_modules/@herbertgao/resume-from/dist/adapters/pi/parse.d.ts +41 -0
- package/node_modules/@herbertgao/resume-from/dist/adapters/pi/parse.js +371 -0
- package/node_modules/@herbertgao/resume-from/dist/adapters/pi/redaction.d.ts +26 -0
- package/node_modules/@herbertgao/resume-from/dist/adapters/pi/redaction.js +132 -0
- package/node_modules/@herbertgao/resume-from/dist/adapters/pi/serialize.d.ts +22 -0
- package/node_modules/@herbertgao/resume-from/dist/adapters/pi/serialize.js +87 -0
- package/node_modules/@herbertgao/resume-from/dist/adapters/pi/validate.d.ts +14 -0
- package/node_modules/@herbertgao/resume-from/dist/adapters/pi/validate.js +134 -0
- package/node_modules/@herbertgao/resume-from/dist/bin.d.ts +35 -0
- package/node_modules/@herbertgao/resume-from/dist/bin.js +135 -0
- package/node_modules/@herbertgao/resume-from/dist/contract.d.ts +10 -0
- package/node_modules/@herbertgao/resume-from/dist/contract.js +4 -0
- package/node_modules/@herbertgao/resume-from/dist/host/agents.d.ts +29 -0
- package/node_modules/@herbertgao/resume-from/dist/host/agents.js +20 -0
- package/node_modules/@herbertgao/resume-from/dist/host/cli/args.d.ts +21 -0
- package/node_modules/@herbertgao/resume-from/dist/host/cli/args.js +187 -0
- package/node_modules/@herbertgao/resume-from/dist/host/cli/contract.d.ts +27 -0
- package/node_modules/@herbertgao/resume-from/dist/host/cli/contract.js +4 -0
- package/node_modules/@herbertgao/resume-from/dist/host/cli/index.d.ts +2 -0
- package/node_modules/@herbertgao/resume-from/dist/host/cli/index.js +1 -0
- package/node_modules/@herbertgao/resume-from/dist/host/cli/presentation.d.ts +2 -0
- package/node_modules/@herbertgao/resume-from/dist/host/cli/presentation.js +11 -0
- package/node_modules/@herbertgao/resume-from/dist/host/cli/render.d.ts +3 -0
- package/node_modules/@herbertgao/resume-from/dist/host/cli/render.js +70 -0
- package/node_modules/@herbertgao/resume-from/dist/host/cli/runner.d.ts +8 -0
- package/node_modules/@herbertgao/resume-from/dist/host/cli/runner.js +125 -0
- package/node_modules/@herbertgao/resume-from/dist/host/contract.d.ts +42 -0
- package/node_modules/@herbertgao/resume-from/dist/host/contract.js +4 -0
- package/node_modules/@herbertgao/resume-from/dist/host/entry.d.ts +52 -0
- package/node_modules/@herbertgao/resume-from/dist/host/entry.js +91 -0
- package/node_modules/@herbertgao/resume-from/dist/host/index.d.ts +11 -0
- package/node_modules/@herbertgao/resume-from/dist/host/index.js +10 -0
- package/node_modules/@herbertgao/resume-from/dist/host/pi-extension/command.d.ts +18 -0
- package/node_modules/@herbertgao/resume-from/dist/host/pi-extension/command.js +103 -0
- package/node_modules/@herbertgao/resume-from/dist/host/pi-extension/contract.d.ts +34 -0
- package/node_modules/@herbertgao/resume-from/dist/host/pi-extension/contract.js +4 -0
- package/node_modules/@herbertgao/resume-from/dist/host/pi-extension/index.d.ts +6 -0
- package/node_modules/@herbertgao/resume-from/dist/host/pi-extension/index.js +4 -0
- package/node_modules/@herbertgao/resume-from/dist/host/pi-extension/picker.d.ts +16 -0
- package/node_modules/@herbertgao/resume-from/dist/host/pi-extension/picker.js +35 -0
- package/node_modules/@herbertgao/resume-from/dist/host/pi-extension/presentation.d.ts +2 -0
- package/node_modules/@herbertgao/resume-from/dist/host/pi-extension/presentation.js +11 -0
- package/node_modules/@herbertgao/resume-from/dist/host/pi-extension/register.d.ts +25 -0
- package/node_modules/@herbertgao/resume-from/dist/host/pi-extension/register.js +11 -0
- package/node_modules/@herbertgao/resume-from/dist/host/pi-extension/ui.d.ts +20 -0
- package/node_modules/@herbertgao/resume-from/dist/host/pi-extension/ui.js +1 -0
- package/node_modules/@herbertgao/resume-from/dist/host/profile.d.ts +8 -0
- package/node_modules/@herbertgao/resume-from/dist/host/profile.js +31 -0
- package/node_modules/@herbertgao/resume-from/dist/host/registry.d.ts +8 -0
- package/node_modules/@herbertgao/resume-from/dist/host/registry.js +33 -0
- package/node_modules/@herbertgao/resume-from/dist/host/wiring.d.ts +41 -0
- package/node_modules/@herbertgao/resume-from/dist/host/wiring.js +67 -0
- package/node_modules/@herbertgao/resume-from/dist/import/confirmation.d.ts +6 -0
- package/node_modules/@herbertgao/resume-from/dist/import/confirmation.js +27 -0
- package/node_modules/@herbertgao/resume-from/dist/import/contract.d.ts +41 -0
- package/node_modules/@herbertgao/resume-from/dist/import/contract.js +4 -0
- package/node_modules/@herbertgao/resume-from/dist/import/discovery/contract.d.ts +58 -0
- package/node_modules/@herbertgao/resume-from/dist/import/discovery/contract.js +4 -0
- package/node_modules/@herbertgao/resume-from/dist/import/discovery/errors.d.ts +9 -0
- package/node_modules/@herbertgao/resume-from/dist/import/discovery/errors.js +12 -0
- package/node_modules/@herbertgao/resume-from/dist/import/discovery/finder.d.ts +10 -0
- package/node_modules/@herbertgao/resume-from/dist/import/discovery/finder.js +129 -0
- package/node_modules/@herbertgao/resume-from/dist/import/discovery/homes.d.ts +23 -0
- package/node_modules/@herbertgao/resume-from/dist/import/discovery/homes.js +83 -0
- package/node_modules/@herbertgao/resume-from/dist/import/discovery/index.d.ts +2 -0
- package/node_modules/@herbertgao/resume-from/dist/import/discovery/index.js +2 -0
- package/node_modules/@herbertgao/resume-from/dist/import/discovery/ordering.d.ts +3 -0
- package/node_modules/@herbertgao/resume-from/dist/import/discovery/ordering.js +21 -0
- package/node_modules/@herbertgao/resume-from/dist/import/errors.d.ts +22 -0
- package/node_modules/@herbertgao/resume-from/dist/import/errors.js +20 -0
- package/node_modules/@herbertgao/resume-from/dist/import/index.d.ts +3 -0
- package/node_modules/@herbertgao/resume-from/dist/import/index.js +5 -0
- package/node_modules/@herbertgao/resume-from/dist/import/landing/contract.d.ts +44 -0
- package/node_modules/@herbertgao/resume-from/dist/import/landing/contract.js +4 -0
- package/node_modules/@herbertgao/resume-from/dist/import/landing/errors.d.ts +15 -0
- package/node_modules/@herbertgao/resume-from/dist/import/landing/errors.js +15 -0
- package/node_modules/@herbertgao/resume-from/dist/import/landing/handover.d.ts +5 -0
- package/node_modules/@herbertgao/resume-from/dist/import/landing/handover.js +12 -0
- package/node_modules/@herbertgao/resume-from/dist/import/landing/index.d.ts +4 -0
- package/node_modules/@herbertgao/resume-from/dist/import/landing/index.js +4 -0
- package/node_modules/@herbertgao/resume-from/dist/import/landing/lander.d.ts +7 -0
- package/node_modules/@herbertgao/resume-from/dist/import/landing/lander.js +133 -0
- package/node_modules/@herbertgao/resume-from/dist/import/landing/marker.d.ts +9 -0
- package/node_modules/@herbertgao/resume-from/dist/import/landing/marker.js +46 -0
- package/node_modules/@herbertgao/resume-from/dist/import/pipeline.d.ts +17 -0
- package/node_modules/@herbertgao/resume-from/dist/import/pipeline.js +134 -0
- package/node_modules/@herbertgao/resume-from/dist/import/preview/builder.d.ts +6 -0
- package/node_modules/@herbertgao/resume-from/dist/import/preview/builder.js +76 -0
- package/node_modules/@herbertgao/resume-from/dist/import/preview/contract.d.ts +39 -0
- package/node_modules/@herbertgao/resume-from/dist/import/preview/contract.js +4 -0
- package/node_modules/@herbertgao/resume-from/dist/import/preview/format.d.ts +19 -0
- package/node_modules/@herbertgao/resume-from/dist/import/preview/format.js +45 -0
- package/node_modules/@herbertgao/resume-from/dist/import/preview/index.d.ts +2 -0
- package/node_modules/@herbertgao/resume-from/dist/import/preview/index.js +1 -0
- package/node_modules/@herbertgao/resume-from/dist/import/preview/warnings.d.ts +11 -0
- package/node_modules/@herbertgao/resume-from/dist/import/preview/warnings.js +80 -0
- package/node_modules/@herbertgao/resume-from/dist/import/transfer/contract.d.ts +48 -0
- package/node_modules/@herbertgao/resume-from/dist/import/transfer/contract.js +4 -0
- package/node_modules/@herbertgao/resume-from/dist/import/transfer/index.d.ts +2 -0
- package/node_modules/@herbertgao/resume-from/dist/import/transfer/index.js +1 -0
- package/node_modules/@herbertgao/resume-from/dist/import/transfer/rules.d.ts +3 -0
- package/node_modules/@herbertgao/resume-from/dist/import/transfer/rules.js +228 -0
- package/node_modules/@herbertgao/resume-from/dist/import/wiring.d.ts +24 -0
- package/node_modules/@herbertgao/resume-from/dist/import/wiring.js +27 -0
- package/node_modules/@herbertgao/resume-from/dist/index.d.ts +36 -0
- package/node_modules/@herbertgao/resume-from/dist/index.js +36 -0
- package/node_modules/@herbertgao/resume-from/dist/platform/config/contract.d.ts +35 -0
- package/node_modules/@herbertgao/resume-from/dist/platform/config/contract.js +4 -0
- package/node_modules/@herbertgao/resume-from/dist/platform/config/defaults.d.ts +16 -0
- package/node_modules/@herbertgao/resume-from/dist/platform/config/defaults.js +22 -0
- package/node_modules/@herbertgao/resume-from/dist/platform/config/errors.d.ts +9 -0
- package/node_modules/@herbertgao/resume-from/dist/platform/config/errors.js +12 -0
- package/node_modules/@herbertgao/resume-from/dist/platform/config/index.d.ts +5 -0
- package/node_modules/@herbertgao/resume-from/dist/platform/config/index.js +6 -0
- package/node_modules/@herbertgao/resume-from/dist/platform/config/loader.d.ts +10 -0
- package/node_modules/@herbertgao/resume-from/dist/platform/config/loader.js +47 -0
- package/node_modules/@herbertgao/resume-from/dist/platform/config/paths.d.ts +13 -0
- package/node_modules/@herbertgao/resume-from/dist/platform/config/paths.js +37 -0
- package/node_modules/@herbertgao/resume-from/dist/platform/config/validate.d.ts +9 -0
- package/node_modules/@herbertgao/resume-from/dist/platform/config/validate.js +117 -0
- package/node_modules/@herbertgao/resume-from/dist/platform/repo/contract.d.ts +30 -0
- package/node_modules/@herbertgao/resume-from/dist/platform/repo/contract.js +4 -0
- package/node_modules/@herbertgao/resume-from/dist/platform/repo/git.d.ts +21 -0
- package/node_modules/@herbertgao/resume-from/dist/platform/repo/git.js +74 -0
- package/node_modules/@herbertgao/resume-from/dist/platform/repo/index.d.ts +2 -0
- package/node_modules/@herbertgao/resume-from/dist/platform/repo/index.js +1 -0
- package/node_modules/@herbertgao/resume-from/dist/platform/repo/reader.d.ts +8 -0
- package/node_modules/@herbertgao/resume-from/dist/platform/repo/reader.js +130 -0
- package/node_modules/@herbertgao/resume-from/dist/platform/store/contract.d.ts +31 -0
- package/node_modules/@herbertgao/resume-from/dist/platform/store/contract.js +4 -0
- package/node_modules/@herbertgao/resume-from/dist/platform/store/file-committer.d.ts +3 -0
- package/node_modules/@herbertgao/resume-from/dist/platform/store/file-committer.js +391 -0
- package/node_modules/@herbertgao/resume-from/dist/platform/store/index.d.ts +2 -0
- package/node_modules/@herbertgao/resume-from/dist/platform/store/index.js +2 -0
- package/node_modules/@herbertgao/resume-from/dist/platform/tokens/contract.d.ts +12 -0
- package/node_modules/@herbertgao/resume-from/dist/platform/tokens/contract.js +4 -0
- package/node_modules/@herbertgao/resume-from/dist/platform/tokens/estimator.d.ts +12 -0
- package/node_modules/@herbertgao/resume-from/dist/platform/tokens/estimator.js +111 -0
- package/node_modules/@herbertgao/resume-from/dist/platform/tokens/index.d.ts +2 -0
- package/node_modules/@herbertgao/resume-from/dist/platform/tokens/index.js +1 -0
- package/node_modules/@herbertgao/resume-from/dist/session/contract.d.ts +109 -0
- package/node_modules/@herbertgao/resume-from/dist/session/contract.js +4 -0
- package/node_modules/@herbertgao/resume-from/package.json +94 -0
- package/node_modules/@herbertgao/resume-from/shims/pi/extensions/resume-from.js +118 -0
- package/package.json +6 -2
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Everything this tool knows about Pi's session file layout.
|
|
3
|
+
*
|
|
4
|
+
* Verified against the installed Pi 0.83.0 (`dist/core/session-manager.js`,
|
|
5
|
+
* `dist/config.js`), not assumed:
|
|
6
|
+
*
|
|
7
|
+
* - the home is Pi's agent directory: `$PI_CODING_AGENT_DIR`, else `~/.pi/agent`;
|
|
8
|
+
* - sessions live in `<home>/sessions/<encoded cwd>/<timestamp>_<id>.jsonl`;
|
|
9
|
+
* - a file is JSONL: one `session` header, then entries with `id`/`parentId`/`timestamp`;
|
|
10
|
+
* - an assistant message without a numeric `usage` object crashes Pi (C-11).
|
|
11
|
+
*
|
|
12
|
+
* When Pi changes, this file changes. Nothing outside this folder holds these facts.
|
|
13
|
+
*/
|
|
14
|
+
import { homedir } from "node:os";
|
|
15
|
+
import { join, resolve } from "node:path";
|
|
16
|
+
/** Pi's current session file version (`CURRENT_SESSION_VERSION`). */
|
|
17
|
+
export const PI_SESSION_VERSION = 3;
|
|
18
|
+
export const SESSIONS_DIR_NAME = "sessions";
|
|
19
|
+
const PI_CONFIG_DIR_NAME = ".pi";
|
|
20
|
+
const PI_AGENT_DIR_NAME = "agent";
|
|
21
|
+
/** Pi's own override of its agent directory (`ENV_AGENT_DIR` in Pi's config). */
|
|
22
|
+
const ENV_AGENT_DIR = "PI_CODING_AGENT_DIR";
|
|
23
|
+
/**
|
|
24
|
+
* The customType of the provenance entry. It is written as a `custom` entry, which Pi's
|
|
25
|
+
* `buildSessionContext` drops on the floor: the marker is stored and shown, never sent to
|
|
26
|
+
* the model (FR-47, FR-48). `src/host/pi-extension/` registers the renderer that shows it.
|
|
27
|
+
*/
|
|
28
|
+
export const MARKER_CUSTOM_TYPE = "resume-from-provenance";
|
|
29
|
+
/** Prefix of the title of a session file that cannot be read (T-PI-13). */
|
|
30
|
+
export const UNREADABLE_TITLE_PREFIX = "(unreadable) ";
|
|
31
|
+
/** What replaces a tool result body (FR-24, FR-25). */
|
|
32
|
+
export const DROPPED_BODY_NOTE = "(content dropped: imported session, may be stale)";
|
|
33
|
+
/** Context window assumed for Pi when configuration overrides none (FR-18). */
|
|
34
|
+
export const DEFAULT_WINDOW_TOKENS = 200_000;
|
|
35
|
+
/** What an imported assistant turn declares about itself. It cost nothing to import. */
|
|
36
|
+
export const IMPORTED_API = "resume-from";
|
|
37
|
+
export const IMPORTED_PROVIDER = "resume-from";
|
|
38
|
+
export const IMPORTED_MODEL = "imported-session";
|
|
39
|
+
const MAX_TITLE_LENGTH = 80;
|
|
40
|
+
const MAX_OUTCOME_ARGUMENTS = 60;
|
|
41
|
+
/** Pi's default agent directory. Mirrors Pi's own `getAgentDir()`. */
|
|
42
|
+
export function defaultPiHome() {
|
|
43
|
+
const override = process.env[ENV_AGENT_DIR];
|
|
44
|
+
if (override && override.trim().length > 0)
|
|
45
|
+
return resolve(override);
|
|
46
|
+
return join(homedir(), PI_CONFIG_DIR_NAME, PI_AGENT_DIR_NAME);
|
|
47
|
+
}
|
|
48
|
+
/** Pi's encoding of a working directory into one directory name. */
|
|
49
|
+
export function encodeCwd(cwd) {
|
|
50
|
+
const resolved = resolve(cwd);
|
|
51
|
+
return `--${resolved.replace(/^[/\\]/, "").replace(/[/\\:]/g, "-")}--`;
|
|
52
|
+
}
|
|
53
|
+
export function sessionsRoot(home) {
|
|
54
|
+
return join(home, SESSIONS_DIR_NAME);
|
|
55
|
+
}
|
|
56
|
+
export function sessionDirFor(home, cwd) {
|
|
57
|
+
return join(sessionsRoot(home), encodeCwd(cwd));
|
|
58
|
+
}
|
|
59
|
+
/** Pi's own file naming: the ISO timestamp with `:` and `.` replaced, then the id. */
|
|
60
|
+
export function sessionFileName(isoTimestamp, sessionId) {
|
|
61
|
+
return `${isoTimestamp.replace(/[:.]/g, "-")}_${sessionId}.jsonl`;
|
|
62
|
+
}
|
|
63
|
+
/** The session id Pi would read out of a session file name, or null. */
|
|
64
|
+
export function sessionIdFromFileName(fileName) {
|
|
65
|
+
const match = /^.*?_(.+)\.jsonl$/.exec(fileName);
|
|
66
|
+
return match?.[1] ?? null;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Entry types Pi writes that carry no turn: settings, bookmarks, and extension state.
|
|
70
|
+
* They are known, so they are not reported as skipped.
|
|
71
|
+
*
|
|
72
|
+
* `custom_message` is deliberately absent. Pi's `sessionEntryToContextMessages` turns it
|
|
73
|
+
* into a real context message, so an extension can put conversation content there. This
|
|
74
|
+
* adapter does not know that content's shape, so it is skipped — and counted, so the skip
|
|
75
|
+
* is visible rather than a silent shortening of the session.
|
|
76
|
+
*/
|
|
77
|
+
export const NON_TURN_ENTRY_TYPES = new Set([
|
|
78
|
+
"model_change",
|
|
79
|
+
"thinking_level_change",
|
|
80
|
+
"custom",
|
|
81
|
+
"label",
|
|
82
|
+
"session_info",
|
|
83
|
+
]);
|
|
84
|
+
// --- Small readers ----------------------------------------------------------------
|
|
85
|
+
export function isRecord(value) {
|
|
86
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
87
|
+
}
|
|
88
|
+
export function asHeader(value) {
|
|
89
|
+
if (!isRecord(value))
|
|
90
|
+
return null;
|
|
91
|
+
if (value.type !== "session")
|
|
92
|
+
return null;
|
|
93
|
+
if (typeof value.id !== "string" || value.id.length === 0)
|
|
94
|
+
return null;
|
|
95
|
+
const timestamp = typeof value.timestamp === "string" ? value.timestamp : "";
|
|
96
|
+
const cwd = typeof value.cwd === "string" ? value.cwd : "";
|
|
97
|
+
return {
|
|
98
|
+
type: "session",
|
|
99
|
+
...(typeof value.version === "number" ? { version: value.version } : {}),
|
|
100
|
+
id: value.id,
|
|
101
|
+
timestamp,
|
|
102
|
+
cwd,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
export function asEntry(value) {
|
|
106
|
+
if (!isRecord(value))
|
|
107
|
+
return null;
|
|
108
|
+
if (typeof value.type !== "string" || value.type.length === 0)
|
|
109
|
+
return null;
|
|
110
|
+
return value;
|
|
111
|
+
}
|
|
112
|
+
export function entryMessage(entry) {
|
|
113
|
+
if (entry.type !== "message")
|
|
114
|
+
return null;
|
|
115
|
+
return isRecord(entry.message) ? entry.message : null;
|
|
116
|
+
}
|
|
117
|
+
/** The visible text of a message content, hidden reasoning excluded (FR-28). */
|
|
118
|
+
export function visibleText(content) {
|
|
119
|
+
if (typeof content === "string")
|
|
120
|
+
return content;
|
|
121
|
+
if (!Array.isArray(content))
|
|
122
|
+
return "";
|
|
123
|
+
const parts = [];
|
|
124
|
+
for (const block of content) {
|
|
125
|
+
if (!isRecord(block))
|
|
126
|
+
continue;
|
|
127
|
+
if (block.type !== "text")
|
|
128
|
+
continue;
|
|
129
|
+
if (typeof block.text === "string")
|
|
130
|
+
parts.push(block.text);
|
|
131
|
+
}
|
|
132
|
+
return parts.join("\n");
|
|
133
|
+
}
|
|
134
|
+
export function toolCallBlocks(content) {
|
|
135
|
+
if (!Array.isArray(content))
|
|
136
|
+
return [];
|
|
137
|
+
const calls = [];
|
|
138
|
+
for (const block of content) {
|
|
139
|
+
if (!isRecord(block) || block.type !== "toolCall")
|
|
140
|
+
continue;
|
|
141
|
+
if (typeof block.name !== "string")
|
|
142
|
+
continue;
|
|
143
|
+
calls.push({
|
|
144
|
+
type: "toolCall",
|
|
145
|
+
id: typeof block.id === "string" ? block.id : "",
|
|
146
|
+
name: block.name,
|
|
147
|
+
arguments: isRecord(block.arguments) ? block.arguments : {},
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
return calls;
|
|
151
|
+
}
|
|
152
|
+
const READ_ONLY_TOOLS = new Set([
|
|
153
|
+
"read",
|
|
154
|
+
"ls",
|
|
155
|
+
"list",
|
|
156
|
+
"find",
|
|
157
|
+
"glob",
|
|
158
|
+
"grep",
|
|
159
|
+
"search",
|
|
160
|
+
"tree",
|
|
161
|
+
"fetch",
|
|
162
|
+
"webfetch",
|
|
163
|
+
"web_search",
|
|
164
|
+
"websearch",
|
|
165
|
+
]);
|
|
166
|
+
const MUTATING_TOOLS = new Set([
|
|
167
|
+
"write",
|
|
168
|
+
"edit",
|
|
169
|
+
"multiedit",
|
|
170
|
+
"apply_patch",
|
|
171
|
+
"applypatch",
|
|
172
|
+
"patch",
|
|
173
|
+
"create",
|
|
174
|
+
"delete",
|
|
175
|
+
"remove",
|
|
176
|
+
"move",
|
|
177
|
+
"rename",
|
|
178
|
+
"mkdir",
|
|
179
|
+
"notebookedit",
|
|
180
|
+
]);
|
|
181
|
+
/** Did the call change the repository? (FR-26) A shell is never assumed either way. */
|
|
182
|
+
export function toolEffectFor(toolName) {
|
|
183
|
+
const name = toolName.trim().toLowerCase();
|
|
184
|
+
if (READ_ONLY_TOOLS.has(name))
|
|
185
|
+
return "read-only";
|
|
186
|
+
if (MUTATING_TOOLS.has(name))
|
|
187
|
+
return "mutating";
|
|
188
|
+
return "unknown";
|
|
189
|
+
}
|
|
190
|
+
/** One line, whatever the input contained. */
|
|
191
|
+
export function oneLine(text) {
|
|
192
|
+
return text.replace(/\s+/g, " ").trim();
|
|
193
|
+
}
|
|
194
|
+
export function truncate(text, limit) {
|
|
195
|
+
return text.length <= limit ? text : `${text.slice(0, limit - 1)}…`;
|
|
196
|
+
}
|
|
197
|
+
export function shortArguments(argumentsText) {
|
|
198
|
+
return truncate(oneLine(argumentsText), MAX_OUTCOME_ARGUMENTS);
|
|
199
|
+
}
|
|
200
|
+
export function shortTitle(text) {
|
|
201
|
+
return truncate(oneLine(text), MAX_TITLE_LENGTH);
|
|
202
|
+
}
|
|
203
|
+
/** ISO-8601 UTC, or null when the source recorded nothing usable. */
|
|
204
|
+
export function toIsoUtc(value) {
|
|
205
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
206
|
+
return new Date(value).toISOString();
|
|
207
|
+
}
|
|
208
|
+
if (typeof value !== "string" || value.trim().length === 0)
|
|
209
|
+
return null;
|
|
210
|
+
const parsed = Date.parse(value);
|
|
211
|
+
return Number.isNaN(parsed) ? null : new Date(parsed).toISOString();
|
|
212
|
+
}
|
|
213
|
+
export function emptyUsage() {
|
|
214
|
+
return {
|
|
215
|
+
input: 0,
|
|
216
|
+
output: 0,
|
|
217
|
+
cacheRead: 0,
|
|
218
|
+
cacheWrite: 0,
|
|
219
|
+
reasoning: 0,
|
|
220
|
+
totalTokens: 0,
|
|
221
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
|
|
222
|
+
};
|
|
223
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Pi adapter's only export (FR-57). Everything Pi-specific stays inside this folder:
|
|
3
|
+
* the file layout, the `usage` requirement of C-11, and Pi's command context.
|
|
4
|
+
*/
|
|
5
|
+
import type { PiAdapterFactory } from "./contract.js";
|
|
6
|
+
export type { PiAdapterFactory, PiSwitchContext, PiSwitchOptions, PiSwitchResult, } from "./contract.js";
|
|
7
|
+
export declare const piAdapterFactory: PiAdapterFactory;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Pi adapter's only export (FR-57). Everything Pi-specific stays inside this folder:
|
|
3
|
+
* the file layout, the `usage` requirement of C-11, and Pi's command context.
|
|
4
|
+
*/
|
|
5
|
+
import { createPiAdapter } from "./adapter.js";
|
|
6
|
+
export const piAdapterFactory = {
|
|
7
|
+
create() {
|
|
8
|
+
return createPiAdapter();
|
|
9
|
+
},
|
|
10
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reading a Pi session file into the canonical vocabulary.
|
|
3
|
+
*
|
|
4
|
+
* Two rules decide everything here:
|
|
5
|
+
* - no result body crosses (FR-24). A tool result becomes one outcome line (FR-23, FR-25);
|
|
6
|
+
* - nothing is guessed. An entry type this adapter does not know is skipped and counted,
|
|
7
|
+
* and an unparsable line makes the whole file unreadable rather than shorter.
|
|
8
|
+
*/
|
|
9
|
+
import type { CanonicalTurn } from "./contract.js";
|
|
10
|
+
import { type PiEntry, type PiSessionHeader } from "./format.js";
|
|
11
|
+
export interface ParsedSessionFile {
|
|
12
|
+
header: PiSessionHeader | null;
|
|
13
|
+
entries: PiEntry[];
|
|
14
|
+
/** True when a line was not JSON: the file was cut, or written by something else. */
|
|
15
|
+
truncated: boolean;
|
|
16
|
+
}
|
|
17
|
+
export interface LoadedTurns {
|
|
18
|
+
turns: CanonicalTurn[];
|
|
19
|
+
/** Entry types this adapter does not know. Visible so the preview can warn (T-PI-14). */
|
|
20
|
+
skippedEntryTypes: string[];
|
|
21
|
+
skippedCount: number;
|
|
22
|
+
/** Set when the append-only entry graph cannot produce one trustworthy active branch. */
|
|
23
|
+
unreadable: string | null;
|
|
24
|
+
}
|
|
25
|
+
export interface ResolvedPiEntries {
|
|
26
|
+
/** The active leaf's complete parent chain, before compaction is applied. */
|
|
27
|
+
activePath: PiEntry[];
|
|
28
|
+
/** The entries Pi would put in model context after applying its latest compaction. */
|
|
29
|
+
contextEntries: PiEntry[];
|
|
30
|
+
unreadable: string | null;
|
|
31
|
+
}
|
|
32
|
+
export declare function parseSessionText(text: string): ParsedSessionFile;
|
|
33
|
+
/** Resolve Pi's active leaf and reproduce its compaction-aware context projection. */
|
|
34
|
+
export declare function resolveActiveEntries(entries: PiEntry[]): ResolvedPiEntries;
|
|
35
|
+
export declare function entriesToTurns(entries: PiEntry[]): LoadedTurns;
|
|
36
|
+
/** Short human title: the session's own name, else its first user message. */
|
|
37
|
+
export declare function titleFromEntries(entries: PiEntry[]): string | null;
|
|
38
|
+
/** Files named by raw Pi tool inputs, before canonical credential redaction. */
|
|
39
|
+
export declare function changedPathsFromEntries(entries: PiEntry[]): string[];
|
|
40
|
+
/** The last timestamp the file recorded, ISO-8601 UTC, or null. */
|
|
41
|
+
export declare function lastTimestamp(entries: PiEntry[]): string | null;
|
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reading a Pi session file into the canonical vocabulary.
|
|
3
|
+
*
|
|
4
|
+
* Two rules decide everything here:
|
|
5
|
+
* - no result body crosses (FR-24). A tool result becomes one outcome line (FR-23, FR-25);
|
|
6
|
+
* - nothing is guessed. An entry type this adapter does not know is skipped and counted,
|
|
7
|
+
* and an unparsable line makes the whole file unreadable rather than shorter.
|
|
8
|
+
*/
|
|
9
|
+
import { asEntry, asHeader, DROPPED_BODY_NOTE, entryMessage, isRecord, NON_TURN_ENTRY_TYPES, oneLine, shortArguments, toIsoUtc, toolCallBlocks, toolEffectFor, visibleText, } from "./format.js";
|
|
10
|
+
import { redactSensitiveStructure, redactSensitiveText } from "./redaction.js";
|
|
11
|
+
export function parseSessionText(text) {
|
|
12
|
+
const lines = text.split("\n").filter((line) => line.trim().length > 0);
|
|
13
|
+
const entries = [];
|
|
14
|
+
let header = null;
|
|
15
|
+
let truncated = false;
|
|
16
|
+
for (const [index, line] of lines.entries()) {
|
|
17
|
+
let value;
|
|
18
|
+
try {
|
|
19
|
+
value = JSON.parse(line);
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
truncated = true;
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
if (index === 0) {
|
|
26
|
+
header = asHeader(value);
|
|
27
|
+
if (header)
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
const entry = asEntry(value);
|
|
31
|
+
if (entry)
|
|
32
|
+
entries.push(entry);
|
|
33
|
+
else
|
|
34
|
+
truncated = true;
|
|
35
|
+
}
|
|
36
|
+
return { header, entries, truncated };
|
|
37
|
+
}
|
|
38
|
+
function retainedTailEntries(compaction) {
|
|
39
|
+
if (!Array.isArray(compaction.retainedTail))
|
|
40
|
+
return [];
|
|
41
|
+
return compaction.retainedTail.flatMap((message, index) => {
|
|
42
|
+
if (!isRecord(message))
|
|
43
|
+
return [];
|
|
44
|
+
return [
|
|
45
|
+
{
|
|
46
|
+
type: "message",
|
|
47
|
+
id: `${compaction.id}:retained:${index}`,
|
|
48
|
+
parentId: null,
|
|
49
|
+
timestamp: compaction.timestamp,
|
|
50
|
+
message,
|
|
51
|
+
},
|
|
52
|
+
];
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
/** Resolve Pi's active leaf and reproduce its compaction-aware context projection. */
|
|
56
|
+
export function resolveActiveEntries(entries) {
|
|
57
|
+
if (entries.length === 0) {
|
|
58
|
+
return { activePath: [], contextEntries: [], unreadable: null };
|
|
59
|
+
}
|
|
60
|
+
const byId = new Map();
|
|
61
|
+
for (const entry of entries) {
|
|
62
|
+
if (typeof entry.id !== "string" || entry.id.length === 0) {
|
|
63
|
+
return {
|
|
64
|
+
activePath: [],
|
|
65
|
+
contextEntries: [],
|
|
66
|
+
unreadable: `entry ${entry.type} has no id`,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
if (byId.has(entry.id)) {
|
|
70
|
+
return {
|
|
71
|
+
activePath: [],
|
|
72
|
+
contextEntries: [],
|
|
73
|
+
unreadable: `duplicate entry id ${entry.id}`,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
if (entry.parentId !== null && typeof entry.parentId !== "string") {
|
|
77
|
+
return {
|
|
78
|
+
activePath: [],
|
|
79
|
+
contextEntries: [],
|
|
80
|
+
unreadable: `entry ${entry.id} has a malformed parentId`,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
byId.set(entry.id, entry);
|
|
84
|
+
}
|
|
85
|
+
const reversed = [];
|
|
86
|
+
const visited = new Set();
|
|
87
|
+
let current = entries[entries.length - 1];
|
|
88
|
+
while (current !== undefined) {
|
|
89
|
+
if (visited.has(current.id)) {
|
|
90
|
+
return {
|
|
91
|
+
activePath: [],
|
|
92
|
+
contextEntries: [],
|
|
93
|
+
unreadable: `entry graph contains a cycle at ${current.id}`,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
visited.add(current.id);
|
|
97
|
+
reversed.push(current);
|
|
98
|
+
if (current.parentId === null)
|
|
99
|
+
break;
|
|
100
|
+
const parent = byId.get(current.parentId);
|
|
101
|
+
if (parent === undefined) {
|
|
102
|
+
return {
|
|
103
|
+
activePath: [],
|
|
104
|
+
contextEntries: [],
|
|
105
|
+
unreadable: `entry ${current.id} refers to missing parent ${current.parentId}`,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
current = parent;
|
|
109
|
+
}
|
|
110
|
+
const activePath = reversed.reverse();
|
|
111
|
+
let compactionIndex = -1;
|
|
112
|
+
for (let index = 0; index < activePath.length; index++) {
|
|
113
|
+
if (activePath[index]?.type === "compaction")
|
|
114
|
+
compactionIndex = index;
|
|
115
|
+
}
|
|
116
|
+
if (compactionIndex < 0) {
|
|
117
|
+
return { activePath, contextEntries: activePath, unreadable: null };
|
|
118
|
+
}
|
|
119
|
+
const compaction = activePath[compactionIndex];
|
|
120
|
+
const afterCompaction = activePath.slice(compactionIndex + 1);
|
|
121
|
+
const retainedTail = retainedTailEntries(compaction);
|
|
122
|
+
if (retainedTail.length > 0) {
|
|
123
|
+
return {
|
|
124
|
+
activePath,
|
|
125
|
+
contextEntries: [compaction, ...retainedTail, ...afterCompaction],
|
|
126
|
+
unreadable: null,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
const firstKeptId = typeof compaction.firstKeptEntryId === "string" ? compaction.firstKeptEntryId : null;
|
|
130
|
+
const firstKeptIndex = firstKeptId === null
|
|
131
|
+
? -1
|
|
132
|
+
: activePath.findIndex((entry, index) => index < compactionIndex && entry.id === firstKeptId);
|
|
133
|
+
const keptBefore = firstKeptIndex < 0 ? [] : activePath.slice(firstKeptIndex, compactionIndex);
|
|
134
|
+
return {
|
|
135
|
+
activePath,
|
|
136
|
+
contextEntries: [compaction, ...keptBefore, ...afterCompaction],
|
|
137
|
+
unreadable: null,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
/** The result bodies of a session, keyed by tool call id. Read once, never carried. */
|
|
141
|
+
function indexToolResults(entries) {
|
|
142
|
+
const results = new Map();
|
|
143
|
+
for (const entry of entries) {
|
|
144
|
+
const message = entryMessage(entry);
|
|
145
|
+
if (message?.role !== "toolResult")
|
|
146
|
+
continue;
|
|
147
|
+
const callId = typeof message.toolCallId === "string" ? message.toolCallId : "";
|
|
148
|
+
if (callId.length === 0)
|
|
149
|
+
continue;
|
|
150
|
+
results.set(callId, {
|
|
151
|
+
body: visibleText(message.content),
|
|
152
|
+
present: (typeof message.content === "string" && message.content.length > 0) ||
|
|
153
|
+
(Array.isArray(message.content) && message.content.length > 0) ||
|
|
154
|
+
isRecord(message.content),
|
|
155
|
+
isError: message.isError === true,
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
return results;
|
|
159
|
+
}
|
|
160
|
+
function outcomeOf(result) {
|
|
161
|
+
if (!result)
|
|
162
|
+
return { outcome: "no result recorded", bodyDropped: false };
|
|
163
|
+
if (result.body.trim().length === 0) {
|
|
164
|
+
if (result.present) {
|
|
165
|
+
return {
|
|
166
|
+
outcome: `${result.isError ? "error, result dropped" : "result recorded"} ${DROPPED_BODY_NOTE}`,
|
|
167
|
+
bodyDropped: true,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
return {
|
|
171
|
+
outcome: result.isError ? "error" : "no output",
|
|
172
|
+
bodyDropped: false,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
const lineCount = result.body.split("\n").length;
|
|
176
|
+
const what = result.isError ? "error" : `${lineCount} ${lineCount === 1 ? "line" : "lines"}`;
|
|
177
|
+
return { outcome: `${what} ${DROPPED_BODY_NOTE}`, bodyDropped: true };
|
|
178
|
+
}
|
|
179
|
+
function toolCallRecord(name, args, result) {
|
|
180
|
+
const argumentsText = JSON.stringify(redactSensitiveStructure(args));
|
|
181
|
+
const { outcome, bodyDropped } = outcomeOf(result);
|
|
182
|
+
return {
|
|
183
|
+
toolName: name,
|
|
184
|
+
argumentsText,
|
|
185
|
+
outcomeLine: redactSensitiveText(oneLine(`${name}(${shortArguments(argumentsText)}) → ${outcome}`)),
|
|
186
|
+
effect: toolEffectFor(name),
|
|
187
|
+
bodyDropped,
|
|
188
|
+
// FR-54: false when no toolResult entry existed for this call id (broken tail signal).
|
|
189
|
+
resultRecorded: result !== undefined,
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
function contextEntriesToTurns(entries) {
|
|
193
|
+
const results = indexToolResults(entries);
|
|
194
|
+
const turns = [];
|
|
195
|
+
const skippedEntryTypes = [];
|
|
196
|
+
let skippedCount = 0;
|
|
197
|
+
const push = (turn) => {
|
|
198
|
+
// FR-28, security: credentials pasted as message text must not cross to a different vendor.
|
|
199
|
+
// Tool-call turns carry text: "" so the condition is a no-op there (no double-redaction).
|
|
200
|
+
const text = turn.text.length > 0 ? redactSensitiveText(turn.text) : turn.text;
|
|
201
|
+
turns.push({ ...turn, text, index: turns.length });
|
|
202
|
+
};
|
|
203
|
+
const skip = (type) => {
|
|
204
|
+
skippedCount += 1;
|
|
205
|
+
if (!skippedEntryTypes.includes(type))
|
|
206
|
+
skippedEntryTypes.push(type);
|
|
207
|
+
};
|
|
208
|
+
for (const entry of entries) {
|
|
209
|
+
const timestamp = toIsoUtc(entry.timestamp);
|
|
210
|
+
if (entry.type === "compaction" || entry.type === "branch_summary") {
|
|
211
|
+
const summary = typeof entry.summary === "string" ? entry.summary : "";
|
|
212
|
+
if (summary.trim().length > 0) {
|
|
213
|
+
push({
|
|
214
|
+
role: "agent",
|
|
215
|
+
kind: "summary",
|
|
216
|
+
text: summary,
|
|
217
|
+
toolCall: null,
|
|
218
|
+
timestamp,
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
if (entry.type === "message") {
|
|
224
|
+
const message = entryMessage(entry);
|
|
225
|
+
if (!message) {
|
|
226
|
+
skip("message/malformed");
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
229
|
+
if (message.role === "toolResult")
|
|
230
|
+
continue;
|
|
231
|
+
if (message.role === "user") {
|
|
232
|
+
const text = visibleText(message.content);
|
|
233
|
+
if (text.trim().length > 0) {
|
|
234
|
+
push({
|
|
235
|
+
role: "user",
|
|
236
|
+
kind: "message",
|
|
237
|
+
text,
|
|
238
|
+
toolCall: null,
|
|
239
|
+
timestamp,
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
if (message.role === "assistant") {
|
|
245
|
+
const content = Array.isArray(message.content) ? message.content : [];
|
|
246
|
+
let buffered = "";
|
|
247
|
+
const flush = () => {
|
|
248
|
+
if (buffered.trim().length === 0) {
|
|
249
|
+
buffered = "";
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
push({
|
|
253
|
+
role: "agent",
|
|
254
|
+
kind: "message",
|
|
255
|
+
text: buffered,
|
|
256
|
+
toolCall: null,
|
|
257
|
+
timestamp,
|
|
258
|
+
});
|
|
259
|
+
buffered = "";
|
|
260
|
+
};
|
|
261
|
+
if (!Array.isArray(message.content)) {
|
|
262
|
+
buffered = visibleText(message.content);
|
|
263
|
+
}
|
|
264
|
+
for (const block of content) {
|
|
265
|
+
if (!isRecord(block))
|
|
266
|
+
continue;
|
|
267
|
+
if (block.type === "text" && typeof block.text === "string") {
|
|
268
|
+
buffered = buffered.length > 0 ? `${buffered}\n${block.text}` : block.text;
|
|
269
|
+
continue;
|
|
270
|
+
}
|
|
271
|
+
// "thinking" blocks are hidden reasoning and never cross (FR-28, NG-7).
|
|
272
|
+
if (block.type !== "toolCall")
|
|
273
|
+
continue;
|
|
274
|
+
flush();
|
|
275
|
+
const [call] = toolCallBlocks([block]);
|
|
276
|
+
if (!call)
|
|
277
|
+
continue;
|
|
278
|
+
push({
|
|
279
|
+
role: "agent",
|
|
280
|
+
kind: "tool-call",
|
|
281
|
+
text: "",
|
|
282
|
+
toolCall: toolCallRecord(call.name, call.arguments, results.get(call.id)),
|
|
283
|
+
timestamp,
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
flush();
|
|
287
|
+
continue;
|
|
288
|
+
}
|
|
289
|
+
skip(`message/${String(message.role)}`);
|
|
290
|
+
continue;
|
|
291
|
+
}
|
|
292
|
+
if (NON_TURN_ENTRY_TYPES.has(entry.type))
|
|
293
|
+
continue;
|
|
294
|
+
skip(entry.type);
|
|
295
|
+
}
|
|
296
|
+
return { turns, skippedEntryTypes, skippedCount };
|
|
297
|
+
}
|
|
298
|
+
export function entriesToTurns(entries) {
|
|
299
|
+
const resolved = resolveActiveEntries(entries);
|
|
300
|
+
if (resolved.unreadable !== null) {
|
|
301
|
+
return {
|
|
302
|
+
turns: [],
|
|
303
|
+
skippedEntryTypes: [],
|
|
304
|
+
skippedCount: 0,
|
|
305
|
+
unreadable: resolved.unreadable,
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
return {
|
|
309
|
+
...contextEntriesToTurns(resolved.contextEntries),
|
|
310
|
+
unreadable: null,
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
/** Short human title: the session's own name, else its first user message. */
|
|
314
|
+
export function titleFromEntries(entries) {
|
|
315
|
+
for (const entry of entries) {
|
|
316
|
+
if (entry.type !== "session_info")
|
|
317
|
+
continue;
|
|
318
|
+
if (typeof entry.name === "string" && entry.name.trim().length > 0)
|
|
319
|
+
return entry.name;
|
|
320
|
+
}
|
|
321
|
+
for (const entry of entries) {
|
|
322
|
+
const message = entryMessage(entry);
|
|
323
|
+
if (message?.role !== "user")
|
|
324
|
+
continue;
|
|
325
|
+
const text = visibleText(message.content);
|
|
326
|
+
// FR-28, security: the title flows into SourceProvenance and is shown in the selection list.
|
|
327
|
+
// A credential typed as the first message must not appear there unredacted.
|
|
328
|
+
if (text.trim().length > 0)
|
|
329
|
+
return redactSensitiveText(text);
|
|
330
|
+
}
|
|
331
|
+
return null;
|
|
332
|
+
}
|
|
333
|
+
/** Keys Pi's mutating tools name their target with. */
|
|
334
|
+
const PATH_ARGUMENT_KEYS = ["path", "file_path", "filePath", "file", "target"];
|
|
335
|
+
function changedPathOf(toolName, args) {
|
|
336
|
+
if (toolEffectFor(toolName) !== "mutating")
|
|
337
|
+
return null;
|
|
338
|
+
for (const key of PATH_ARGUMENT_KEYS) {
|
|
339
|
+
const value = args[key];
|
|
340
|
+
if (typeof value === "string" && value.trim().length > 0)
|
|
341
|
+
return value;
|
|
342
|
+
}
|
|
343
|
+
return null;
|
|
344
|
+
}
|
|
345
|
+
/** Files named by raw Pi tool inputs, before canonical credential redaction. */
|
|
346
|
+
export function changedPathsFromEntries(entries) {
|
|
347
|
+
const paths = new Set();
|
|
348
|
+
for (const entry of entries) {
|
|
349
|
+
const message = entryMessage(entry);
|
|
350
|
+
if (message?.role !== "assistant")
|
|
351
|
+
continue;
|
|
352
|
+
for (const call of toolCallBlocks(message.content)) {
|
|
353
|
+
const path = changedPathOf(call.name, call.arguments);
|
|
354
|
+
if (path !== null)
|
|
355
|
+
paths.add(path);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
return [...paths];
|
|
359
|
+
}
|
|
360
|
+
/** The last timestamp the file recorded, ISO-8601 UTC, or null. */
|
|
361
|
+
export function lastTimestamp(entries) {
|
|
362
|
+
for (let index = entries.length - 1; index >= 0; index -= 1) {
|
|
363
|
+
const entry = entries[index];
|
|
364
|
+
if (!entry)
|
|
365
|
+
continue;
|
|
366
|
+
const iso = toIsoUtc(entry.timestamp);
|
|
367
|
+
if (iso)
|
|
368
|
+
return iso;
|
|
369
|
+
}
|
|
370
|
+
return null;
|
|
371
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Credential redaction — shared implementation kept in three adapter subfolders.
|
|
3
|
+
*
|
|
4
|
+
* Files:
|
|
5
|
+
* src/adapters/claude-code/redaction.ts
|
|
6
|
+
* src/adapters/codex/redaction.ts
|
|
7
|
+
* src/adapters/pi/redaction.ts
|
|
8
|
+
*
|
|
9
|
+
* These three files are byte-identical by design. A fix in one MUST be applied to all three.
|
|
10
|
+
* src/adapters/boundary.test.ts asserts byte equality and will fail if they drift.
|
|
11
|
+
*
|
|
12
|
+
* Why not a single shared module: src/adapters/contract.ts is types-only (no behaviour). A
|
|
13
|
+
* platform service would be imported as behaviour, and T-ROO-7 requires adapter-to-platform
|
|
14
|
+
* cross-module imports to go through contract.js — which can only export types, not functions.
|
|
15
|
+
*/
|
|
16
|
+
/** Stable replacement used for credentials that must not cross an adapter boundary. */
|
|
17
|
+
export declare const REDACTED_VALUE = "[REDACTED]";
|
|
18
|
+
/**
|
|
19
|
+
* Redact credentials in JSON-like tool inputs without changing the caller's value.
|
|
20
|
+
* Adapter-specific parsing stays in each adapter; this function only handles secret values.
|
|
21
|
+
*/
|
|
22
|
+
export declare function redactSensitiveStructure(value: unknown): unknown;
|
|
23
|
+
/** Redact recognizable credentials from shell commands, headers, and unstructured arguments. */
|
|
24
|
+
export declare function redactSensitiveText(text: string): string;
|
|
25
|
+
/** Redact a source-recorded argument string, preserving valid JSON when it is JSON. */
|
|
26
|
+
export declare function redactSensitiveArgumentsText(text: string): string;
|