@codemem/pi-extension 0.0.0-alpha.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) CodeMem contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,90 @@
1
+ # @codemem/pi-extension
2
+
3
+ Pi coding-agent extension for [codemem](https://github.com/kunickiaj/codemem): session ingest and turn-local memory injection.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm i -g codemem
9
+ codemem setup
10
+ ```
11
+
12
+ `codemem setup` auto-detects pi and appends `npm:@codemem/pi-extension@<version>` to `~/.pi/agent/settings.json` `packages`. Useful flags:
13
+
14
+ | Flag | Purpose |
15
+ | --- | --- |
16
+ | `--pi-only` | Only configure pi |
17
+ | `--pi-mcp` | Opt into MCP via third-party `pi-mcp-adapter` (writes `mcp.json` only when detected) |
18
+ | `--pi-extension-path <path>` | Dev: write a local-path `packages` entry instead of the npm pin |
19
+
20
+ Dev / local-path install (equivalent to `--pi-extension-path`):
21
+
22
+ ```json
23
+ {
24
+ "packages": ["/absolute/path/to/codemem/packages/pi-extension"]
25
+ }
26
+ ```
27
+
28
+ Build first so `dist/index.js` exists (`pnpm --filter @codemem/pi-extension build`).
29
+
30
+ **Uninstall:** remove the `@codemem/pi-extension` packages entry and restart pi. The shared memory store is left intact.
31
+
32
+ ## What it does
33
+
34
+ - **Ingest** — captures pi session events (`session_start`/`session_shutdown`, user/assistant messages, tool calls/results) into codemem with `source: "pi"` via `POST /api/pi-hooks` (compat alias over the canonical ingest envelope), falling back to `codemem pi-hook-ingest` (spool when offline).
35
+ - **Injection** — on `before_agent_start`, appends a `## codemem memories` block to the **turn-local** `systemPrompt` (never the persistent `message` channel).
36
+ - **Tools** — registers the 14 `memory_*` tools natively (HTTP preferred, CLI fallback). Every HTTP operation first proves the viewer is target-aware (`GET /api/prompt-pack-profile` must report this process's `db_path` + `identity_target`) and re-sends the target on the query, so a foreign or stale older viewer on the port is never trusted (409 or unproven → targeted CLI path). Non-idempotent `memory_remember` does NOT CLI-replay on ambiguous HTTP failures (timeout/5xx/reset after send). No `pi-mcp-adapter` needed. Skipped when `pi.tools_mode` is `mcp-adapter`.
37
+ - **Compaction** — pi-only observe-only boundary: `session_before_compact` flushes extraction; never returns a custom compaction summary.
38
+ - **Fork/resume** — re-keys stream identity on every `session_start`; durable cursors via `pi.appendEntry`.
39
+ - **Cross-agent** — one shared, project-scoped store. Memories from OpenCode/Claude/Codex inject into pi and the reverse.
40
+ - **Dashboard** — pi rows appear in the source-agnostic feed/sessions/projects tabs with no extra setup.
41
+
42
+ ## Observer derivation (v1)
43
+
44
+ Setup can fill unset `observer_*` keys from pi's API-key providers (cheap-model-first). Credentials stay in memory only and are never written to codemem config. OAuth-only installs get an explicit `unconfigured (oauth-only)` status — never a silent failure. Set `observer_provider` / `observer_model` explicitly when needed. Explicit `observer_*` config/env always wins.
45
+
46
+ ## Configuration
47
+
48
+ Read from `~/.config/codemem/config.json` under the `pi` object, with `CODEMEM_PI_*` env overrides.
49
+
50
+ | Key / env | Default | Meaning |
51
+ | --- | --- | --- |
52
+ | `pi.tools_mode` / `CODEMEM_PI_TOOLS_MODE` | `native` | `native` registers tools here; `mcp-adapter` skips native registration (use pi-mcp-adapter + `codemem mcp`) |
53
+ | `pi.inject_prompts` / `CODEMEM_PI_INJECT_PROMPTS` | `true` | Append pack to system prompt on each turn |
54
+ | `pi.file_context` / `CODEMEM_PI_FILE_CONTEXT` | `true` | Attach file-related memories when pi `read`s a tracked file |
55
+
56
+ Shared viewer / inject knobs (same as other clients):
57
+
58
+ | Env | Default | Meaning |
59
+ | --- | --- | --- |
60
+ | `CODEMEM_VIEWER_HOST` | `127.0.0.1` | Viewer host |
61
+ | `CODEMEM_VIEWER_PORT` | `38888` | Viewer port |
62
+ | `CODEMEM_VIEWER` | `1` | Enable viewer use |
63
+ | `CODEMEM_VIEWER_AUTO` | `1` | Auto-start `codemem serve start` when needed |
64
+ | `CODEMEM_RAW_EVENTS_BACKOFF_MS` | `10000` | Backoff after HTTP stream failure before retrying |
65
+ | `CODEMEM_INJECT_LIMIT` | `8` | Pack item limit |
66
+ | `CODEMEM_INJECT_TOKEN_BUDGET` | `800` | Pack token budget |
67
+ | `CODEMEM_INJECT_MAX_CHARS` | `16000` | Max injection block chars |
68
+
69
+ Example config:
70
+
71
+ ```json
72
+ {
73
+ "pi": {
74
+ "tools_mode": "native",
75
+ "inject_prompts": true,
76
+ "file_context": true
77
+ }
78
+ }
79
+ ```
80
+
81
+ ## Lifecycle
82
+
83
+ Per pi extension rules: the factory only wires handlers. Viewer auto-start and other session resources begin on `session_start` and clean up idempotently on `session_shutdown`. Session state is re-keyed from `ctx.sessionManager.getSessionId()` on every `session_start`; durable ingest cursors persist via `pi.appendEntry`.
84
+
85
+ ## Peer dependencies
86
+
87
+ - `@earendil-works/pi-coding-agent` (extension host)
88
+ - `typebox` (tool parameter schemas)
89
+
90
+ No `@codemem/core` or native modules load inside the pi process.
@@ -0,0 +1,99 @@
1
+ /**
2
+ * PiCodememClient — HTTP preferred, CLI exec fallback.
3
+ * Never opens the store or imports @codemem/core.
4
+ */
5
+ import type { PiExtensionConfig } from "./config.js";
6
+ import { type ViewerRuntime } from "./viewer.js";
7
+ /** pi AgentToolResult shape (content + required details). */
8
+ export type ToolResultContent = {
9
+ content: Array<{
10
+ type: "text";
11
+ text: string;
12
+ }>;
13
+ details: Record<string, unknown>;
14
+ };
15
+ export declare function textResult(text: string, details?: Record<string, unknown>): ToolResultContent;
16
+ export declare function errorResult(message: string, details?: Record<string, unknown>): ToolResultContent;
17
+ export declare function jsonResult(value: unknown): ToolResultContent;
18
+ export declare function isToolErrorResult(result: ToolResultContent): boolean;
19
+ export type IngestOutcome = {
20
+ ok: boolean;
21
+ via: "http" | "cli" | "skipped";
22
+ inserted?: number;
23
+ skipped?: number;
24
+ };
25
+ export type ExecCodememFn = (args: string[], opts?: {
26
+ stdin?: string;
27
+ signal?: AbortSignal;
28
+ timeoutMs?: number;
29
+ cwd?: string;
30
+ }) => Promise<{
31
+ stdout: string;
32
+ stderr: string;
33
+ }>;
34
+ /**
35
+ * Memory-pack fetch result. `preformatted` tells the caller whether `text` is
36
+ * already the full `## codemem memories` block (CLI pi-hook-inject) or bare
37
+ * pack text (HTTP /api/pack, CLI pack --json) that must be framed via
38
+ * formatPiInjectionBlock. Never sniff the memory text to decide framing.
39
+ */
40
+ export type PackFetch = {
41
+ text: string;
42
+ preformatted: boolean;
43
+ };
44
+ /** CLI budget for pack/inject and file-context queries (was a 3s probe). */
45
+ export declare const CLI_PACK_TIMEOUT_MS = 8000;
46
+ /**
47
+ * Independent CLI budget for boundary flushes — a drain + LLM finish can
48
+ * exceed the short HTTP ingest timeout, so it must not reuse httpTimeoutMs.
49
+ */
50
+ export declare const BOUNDARY_CLI_TIMEOUT_MS = 30000;
51
+ export declare class PiCodememClient {
52
+ readonly config: PiExtensionConfig;
53
+ readonly runtime: ViewerRuntime;
54
+ cwd: string;
55
+ project: string | null;
56
+ sessionId: string | null;
57
+ /** Injectable for tests; defaults to spawning the codemem CLI. */
58
+ execImpl: ExecCodememFn | null;
59
+ constructor(config: PiExtensionConfig, runtime: ViewerRuntime, opts?: {
60
+ cwd?: string;
61
+ project?: string | null;
62
+ sessionId?: string | null;
63
+ execImpl?: ExecCodememFn | null;
64
+ });
65
+ rekey(sessionId: string, cwd: string, project: string | null): void;
66
+ ensureViewer(signal?: AbortSignal): Promise<boolean>;
67
+ /**
68
+ * POST /api/pi-hooks then CLI `codemem pi-hook-ingest` fallback.
69
+ * Honors stream backoff like the opencode plugin.
70
+ *
71
+ * Boundary events (`session_before_compact`, `session_shutdown`) always go
72
+ * through the CLI: HTTP can acknowledge a flush-only payload as
73
+ * `{inserted:0,skipped:1}` without ever running `flushRawEvents`. The CLI's
74
+ * `shouldForcePiBoundaryFlush` path performs the actual extraction flush
75
+ * (with spool fallback).
76
+ */
77
+ ingest(payload: Record<string, unknown>, signal?: AbortSignal): Promise<IngestOutcome>;
78
+ private tryHttpIngest;
79
+ private tryCliIngest;
80
+ /** Profile-proven POST /api/pack, then CLI pi-hook-inject / pack --json fallback. */
81
+ fetchPackText(context: string, signal?: AbortSignal): Promise<PackFetch>;
82
+ private tryHttpPack;
83
+ execCodemem(args: string[], opts?: {
84
+ stdin?: string;
85
+ signal?: AbortSignal;
86
+ timeoutMs?: number;
87
+ }): Promise<{
88
+ stdout: string;
89
+ stderr: string;
90
+ }>;
91
+ /**
92
+ * Project label from the nearest Git root: walks up for a directory `.git`,
93
+ * or a `gitdir:` file (linked worktree) resolved back to the primary
94
+ * checkout. Mirrors `resolveProject` in packages/core/src/project.ts —
95
+ * implemented locally so the runtime never imports @codemem/core.
96
+ */
97
+ static projectFromCwd(cwd: string): string | null;
98
+ }
99
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Pi extension config: ~/.config/codemem/config.json `pi.*` + CODEMEM_PI_* env.
3
+ *
4
+ * Thin client — does not import @codemem/core. Reads the JSON file directly.
5
+ */
6
+ export type PiToolsMode = "native" | "mcp-adapter";
7
+ export type PiExtensionConfig = {
8
+ toolsMode: PiToolsMode;
9
+ injectPrompts: boolean;
10
+ fileContext: boolean;
11
+ viewerHost: string;
12
+ viewerPort: number;
13
+ viewerEnabled: boolean;
14
+ viewerAutoStart: boolean;
15
+ rawEventsBackoffMs: number;
16
+ rawEventsStatusCheckMs: number;
17
+ injectLimit: number;
18
+ injectTokenBudget: number;
19
+ injectMaxChars: number;
20
+ httpTimeoutMs: number;
21
+ };
22
+ /**
23
+ * Load extension config. Safe to call from the factory (sync file read only).
24
+ */
25
+ export declare function loadPiExtensionConfig(env?: NodeJS.ProcessEnv): PiExtensionConfig;
26
+ /** Test helper: defaults without touching the filesystem. */
27
+ export declare function defaultPiExtensionConfig(overrides?: Partial<PiExtensionConfig>): PiExtensionConfig;
28
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAMH,MAAM,MAAM,WAAW,GAAG,QAAQ,GAAG,aAAa,CAAC;AAEnD,MAAM,MAAM,iBAAiB,GAAG;IAC/B,SAAS,EAAE,WAAW,CAAC;IACvB,aAAa,EAAE,OAAO,CAAC;IACvB,WAAW,EAAE,OAAO,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,OAAO,CAAC;IACvB,eAAe,EAAE,OAAO,CAAC;IACzB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,sBAAsB,EAAE,MAAM,CAAC;IAC/B,WAAW,EAAE,MAAM,CAAC;IACpB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;CACtB,CAAC;AA2JF;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,iBAAiB,CAuC7F;AAED,6DAA6D;AAC7D,wBAAgB,wBAAwB,CACvC,SAAS,GAAE,OAAO,CAAC,iBAAiB,CAAM,GACxC,iBAAiB,CAEnB"}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * @codemem/pi-extension — pi coding-agent extension factory.
3
+ *
4
+ * Lifecycle (pi enforced):
5
+ * - Factory body wires handlers only. No sockets/timers/watchers/child processes.
6
+ * - Viewer auto-start begins on session_start (or first need).
7
+ * - Idempotent cleanup on session_shutdown.
8
+ * - Re-key session state from ctx.sessionManager.getSessionId() every session_start.
9
+ * - Durable ingest cursors via pi.appendEntry.
10
+ *
11
+ * Surfaces:
12
+ * - Ingest → POST /api/pi-hooks → CLI pi-hook-ingest
13
+ * - Injection → before_agent_start systemPrompt append only (never message)
14
+ * - Tools → pi.registerTool × 14 when pi.tools_mode === "native"
15
+ * - session_before_compact → flush signal only (never return compaction)
16
+ */
17
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
18
+ import { type ExecCodememFn } from "./client.js";
19
+ /** @internal test helper — inject codemem CLI behavior without spawning. */
20
+ export declare function __setTestExecImpl(fn: ExecCodememFn | null): void;
21
+ /**
22
+ * Extension factory. Default export required by pi package loader.
23
+ */
24
+ export default function codememPiExtension(pi: ExtensionAPI): void;
25
+ export { errorResult, jsonResult, PiCodememClient, textResult } from "./client.js";
26
+ export { defaultPiExtensionConfig, loadPiExtensionConfig } from "./config.js";
27
+ export { MEMORY_LEARN_PAYLOAD } from "./learn.js";
28
+ export { buildBeforeCompactPayload, buildMessageEndPayload, buildSessionShutdownPayload, buildSessionStartPayload, buildToolCallPayload, buildToolResultPayload, CODEMEM_MEMORIES_HEADER, extractMessageRole, extractMessageText, formatPiInjectionBlock, stableMessageEntryId, } from "./payloads.js";
29
+ export { expectedToolNames, registerMemoryTools } from "./tools.js";
30
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAKH,OAAO,KAAK,EAEX,YAAY,EAQZ,MAAM,iCAAiC,CAAC;AACzC,OAAO,EAEN,KAAK,aAAa,EAIlB,MAAM,aAAa,CAAC;AAsBrB,4EAA4E;AAC5E,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,aAAa,GAAG,IAAI,GAAG,IAAI,CAEhE;AA0ZD;;GAEG;AACH,MAAM,CAAC,OAAO,UAAU,kBAAkB,CAAC,EAAE,EAAE,YAAY,GAAG,IAAI,CA2BjE;AA2CD,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEnF,OAAO,EAAE,wBAAwB,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAC9E,OAAO,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAClD,OAAO,EACN,yBAAyB,EACzB,sBAAsB,EACtB,2BAA2B,EAC3B,wBAAwB,EACxB,oBAAoB,EACpB,sBAAsB,EACtB,uBAAuB,EACvB,kBAAkB,EAClB,kBAAkB,EAClB,sBAAsB,EACtB,oBAAoB,GACpB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC"}