@lmctl-ai/lmctl 0.1.342 → 0.2.3

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.
Files changed (182) hide show
  1. package/CHANGELOG.md +116 -0
  2. package/README.md +11 -51
  3. package/data/price-aliases.json +1 -0
  4. package/data/price-data.json +1 -1
  5. package/dist/cli/batch.d.ts +2 -0
  6. package/dist/cli/busy-evidence.d.ts +27 -0
  7. package/dist/cli/chat-cycle.d.ts +59 -0
  8. package/dist/cli/chat-exec.d.ts +83 -0
  9. package/dist/cli/chat-policy.d.ts +22 -0
  10. package/dist/cli/chat.d.ts +23 -0
  11. package/dist/cli/code-resolver.d.ts +8 -0
  12. package/dist/cli/common.d.ts +54 -0
  13. package/dist/cli/context.d.ts +12 -0
  14. package/dist/cli/cost.d.ts +2 -0
  15. package/dist/cli/export.d.ts +2 -0
  16. package/dist/cli/index.d.ts +3 -0
  17. package/dist/cli/index.js +1062 -1400
  18. package/dist/cli/infra.d.ts +78 -0
  19. package/dist/cli/ls.d.ts +2 -0
  20. package/dist/cli/mailbox-cli.d.ts +6 -0
  21. package/dist/cli/managed.d.ts +62 -0
  22. package/dist/cli/mcp-install.d.ts +23 -0
  23. package/dist/cli/mcp.d.ts +2 -0
  24. package/dist/cli/paths.d.ts +50 -0
  25. package/dist/cli/plan-wizard.d.ts +42 -0
  26. package/dist/cli/plan.d.ts +20 -0
  27. package/dist/cli/queue-delivery.d.ts +107 -0
  28. package/dist/cli/ratelimit.d.ts +3 -0
  29. package/dist/cli/remote-fetch.d.ts +7 -0
  30. package/dist/cli/run-queries.d.ts +82 -0
  31. package/dist/cli/running.d.ts +5 -0
  32. package/dist/cli/schema.sql +20 -1
  33. package/dist/cli/script.d.ts +6 -0
  34. package/dist/cli/script.js +2097 -0
  35. package/dist/cli/self-identity.d.ts +25 -0
  36. package/dist/cli/session-idle.d.ts +118 -0
  37. package/dist/cli/status.d.ts +22 -0
  38. package/dist/cli/tail.d.ts +18 -0
  39. package/dist/cli/target-resolver.d.ts +139 -0
  40. package/dist/cli/team-status.d.ts +91 -0
  41. package/dist/cli/teamfile-compat.d.ts +71 -0
  42. package/dist/cli/teamfile-store.d.ts +18 -0
  43. package/dist/cli/teamfile-template.d.ts +40 -0
  44. package/dist/cli/template-env.d.ts +25 -0
  45. package/dist/cli/terminal-lock.d.ts +25 -0
  46. package/dist/cli/terminal.d.ts +23 -0
  47. package/dist/cli/time-filter.d.ts +10 -0
  48. package/dist/cli/update-notifier.d.ts +11 -0
  49. package/dist/cli/version-util.d.ts +2 -0
  50. package/dist/config/seed_templates.d.ts +1 -0
  51. package/dist/db/backfill-event-log.d.ts +26 -0
  52. package/dist/db/delegation.d.ts +37 -0
  53. package/dist/db/escalation_vocab.d.ts +45 -0
  54. package/dist/db/event-log.d.ts +40 -0
  55. package/dist/db/index.d.ts +1213 -0
  56. package/dist/db/inflight-acquire.d.ts +70 -0
  57. package/dist/db/invocation-event-projections.d.ts +65 -0
  58. package/dist/db/ledger-identity.test.d.ts +1 -0
  59. package/dist/db/live-event-projections.d.ts +92 -0
  60. package/dist/db/migrate.d.ts +51 -0
  61. package/dist/db/open-readonly.d.ts +2 -0
  62. package/dist/db/process-liveness.d.ts +37 -0
  63. package/dist/db/sqlite.d.ts +47 -0
  64. package/dist/db/sqlite.test.d.ts +1 -0
  65. package/dist/db/terminal-lock.d.ts +14 -0
  66. package/dist/db/tracked-invocation-reconcile.d.ts +52 -0
  67. package/dist/db/tracked-invocation.d.ts +116 -0
  68. package/dist/mcp/dispatch.d.ts +45 -0
  69. package/dist/mcp/mediation.d.ts +128 -0
  70. package/dist/mcp/path-warning.d.ts +7 -0
  71. package/dist/mcp/server.d.ts +63 -0
  72. package/dist/mcp/tools.d.ts +24 -0
  73. package/dist/providers/adapter.d.ts +265 -0
  74. package/dist/providers/adapters/agy-adapter.d.ts +17 -0
  75. package/dist/providers/adapters/claude-adapter.d.ts +20 -0
  76. package/dist/providers/adapters/codex-adapter.d.ts +22 -0
  77. package/dist/providers/adapters/copilot-adapter.d.ts +16 -0
  78. package/dist/providers/adapters/dsh-adapter.d.ts +89 -0
  79. package/dist/providers/adapters/gemini-adapter.d.ts +44 -0
  80. package/dist/providers/adapters/hermes-adapter.d.ts +71 -0
  81. package/dist/providers/adapters/index.d.ts +35 -0
  82. package/dist/providers/adapters/kimi-adapter.d.ts +16 -0
  83. package/dist/providers/adapters/lmplayer-adapter.d.ts +14 -0
  84. package/dist/providers/adapters/opencode-adapter.d.ts +11 -0
  85. package/dist/providers/adapters/pi-adapter.d.ts +17 -0
  86. package/dist/providers/adapters/print-turn.d.ts +29 -0
  87. package/dist/providers/adapters/qwen-adapter.d.ts +11 -0
  88. package/dist/providers/agy.d.ts +32 -0
  89. package/dist/providers/base-adapter.d.ts +107 -0
  90. package/dist/providers/base.d.ts +12 -0
  91. package/dist/providers/binary-check.d.ts +102 -0
  92. package/dist/providers/bundle.d.ts +118 -0
  93. package/dist/providers/claude-mock.d.ts +90 -0
  94. package/dist/providers/claude.d.ts +21 -0
  95. package/dist/providers/codex-config.d.ts +15 -0
  96. package/dist/providers/codex.d.ts +25 -0
  97. package/dist/providers/config/model-selection-lint.d.ts +45 -0
  98. package/dist/providers/config/team-config.d.ts +183 -0
  99. package/dist/providers/config/types.d.ts +146 -0
  100. package/dist/providers/config/user-config.d.ts +88 -0
  101. package/dist/providers/copilot.d.ts +21 -0
  102. package/dist/providers/debug.d.ts +128 -0
  103. package/dist/providers/dsh-sidecar.d.ts +65 -0
  104. package/dist/providers/dsh-zstd.d.ts +27 -0
  105. package/dist/providers/dsh.d.ts +37 -0
  106. package/dist/providers/frontmatter.d.ts +46 -0
  107. package/dist/providers/gemini-session-reader.d.ts +151 -0
  108. package/dist/providers/gemini.d.ts +39 -0
  109. package/dist/providers/hermes.d.ts +90 -0
  110. package/dist/providers/index.d.ts +421 -0
  111. package/dist/providers/kimi.d.ts +37 -0
  112. package/dist/providers/model-catalog.d.ts +141 -0
  113. package/dist/providers/one-shot-timeout.d.ts +1 -0
  114. package/dist/providers/opencode-config.d.ts +62 -0
  115. package/dist/providers/opencode.d.ts +40 -0
  116. package/dist/providers/parsers/acp.d.ts +47 -0
  117. package/dist/providers/parsers/codex-app-server.d.ts +60 -0
  118. package/dist/providers/parsers/stream-json.d.ts +57 -0
  119. package/dist/providers/passive-readers/agy.d.ts +155 -0
  120. package/dist/providers/passive-readers/copilot.d.ts +173 -0
  121. package/dist/providers/passive-readers/dsh.d.ts +34 -0
  122. package/dist/providers/passive-readers/kimi.d.ts +44 -0
  123. package/dist/providers/passive-readers/opencode.d.ts +89 -0
  124. package/dist/providers/passive-readers/qwen.d.ts +151 -0
  125. package/dist/providers/passive-readers/shared.d.ts +138 -0
  126. package/dist/providers/passive-session-reader.d.ts +188 -0
  127. package/dist/providers/path-utils.d.ts +1 -0
  128. package/dist/providers/pi.d.ts +32 -0
  129. package/dist/providers/pricing.d.ts +107 -0
  130. package/dist/providers/qwen.d.ts +22 -0
  131. package/dist/providers/ratelimit-readings.d.ts +32 -0
  132. package/dist/providers/registry.d.ts +31 -0
  133. package/dist/providers/resume.d.ts +43 -0
  134. package/dist/providers/seed-instruction.d.ts +3 -0
  135. package/dist/providers/self-session-env.d.ts +21 -0
  136. package/dist/providers/session-limit.d.ts +9 -0
  137. package/dist/providers/strip-ansi.d.ts +5 -0
  138. package/dist/providers/transport-errors.d.ts +94 -0
  139. package/dist/providers/transport-lifecycle.d.ts +63 -0
  140. package/dist/providers/transport-wrappers.d.ts +110 -0
  141. package/dist/providers/transports/acp-profiles/config-option-step.d.ts +24 -0
  142. package/dist/providers/transports/acp-profiles/copilot.d.ts +12 -0
  143. package/dist/providers/transports/acp-profiles/gemini.d.ts +22 -0
  144. package/dist/providers/transports/acp-profiles/hermes.d.ts +27 -0
  145. package/dist/providers/transports/acp-profiles/index.d.ts +19 -0
  146. package/dist/providers/transports/acp-profiles/kimi.d.ts +24 -0
  147. package/dist/providers/transports/acp-profiles/opencode.d.ts +26 -0
  148. package/dist/providers/transports/acp-profiles/types.d.ts +258 -0
  149. package/dist/providers/transports/acp-rpc.d.ts +18 -0
  150. package/dist/providers/transports/acp.d.ts +215 -0
  151. package/dist/providers/transports/codex-app-server.d.ts +190 -0
  152. package/dist/providers/transports/hold-open.d.ts +120 -0
  153. package/dist/providers/transports/manager.d.ts +21 -0
  154. package/dist/providers/transports/stream-json.d.ts +162 -0
  155. package/dist/providers/transports/types.d.ts +56 -0
  156. package/dist/providers/types.d.ts +154 -0
  157. package/dist/providers/yolo-contract.d.ts +131 -0
  158. package/dist/script/adapter.d.ts +101 -0
  159. package/dist/script/batch-script.d.ts +7 -0
  160. package/dist/script/index.d.ts +3 -0
  161. package/dist/script/lmscript.d.ts +550 -0
  162. package/dist/shared/policy.d.ts +74 -0
  163. package/dist/shared-lib/db.d.ts +25 -0
  164. package/dist/shared-lib/passive-session-reader.d.ts +2 -0
  165. package/dist/shared-lib/provider-parse.d.ts +6 -0
  166. package/dist/templates/basedir.d.ts +52 -0
  167. package/dist/uri/lmctl_uri.d.ts +38 -0
  168. package/durable-memory/model_selection.md +122 -25
  169. package/examples/README.md +103 -0
  170. package/examples/anonymous-panel.lms +3 -0
  171. package/examples/batch-prompts.txt +9 -0
  172. package/examples/batchscript.lms +67 -0
  173. package/examples/n8n/agent-lib.lms +56 -0
  174. package/examples/n8n/git-lib.lms +93 -0
  175. package/examples/n8n/mock-support.db +0 -0
  176. package/examples/n8n/multi-agent-review.lms +107 -0
  177. package/examples/n8n/news-digest.lms +159 -0
  178. package/examples/n8n/rag-search.lms +163 -0
  179. package/examples/n8n/support-agent.lms +88 -0
  180. package/examples/n8n/ticket-triage.lms +156 -0
  181. package/package.json +13 -17
  182. package/dist/cli/side_effect_classifier.json +0 -19
@@ -0,0 +1,40 @@
1
+ import type { SessionProvider, SessionSummary, SessionDetail } from "./types.js";
2
+ type OpenCodeCompatibleProviderName = "opencode" | "lmplayer";
3
+ interface OpenCodeProviderOptions {
4
+ dbPath?: string;
5
+ providerName?: OpenCodeCompatibleProviderName;
6
+ startCommand?: string;
7
+ /** Read-only connection `PRAGMA busy_timeout` in ms. Default 5000; lower
8
+ * for tests that need to observe the retry-then-give-up path quickly. */
9
+ busyTimeoutMs?: number;
10
+ }
11
+ export declare class OpenCodeProvider implements SessionProvider {
12
+ readonly name: OpenCodeCompatibleProviderName;
13
+ private readonly startCommand;
14
+ private readonly dbPaths;
15
+ private readonly busyTimeoutMs;
16
+ constructor(opts?: OpenCodeProviderOptions);
17
+ private existingDbPaths;
18
+ private openDb;
19
+ discoverSessions(): Promise<SessionSummary[]>;
20
+ loadSession(sessionId: string): Promise<SessionDetail | null>;
21
+ private buildSessionDetail;
22
+ private extractText;
23
+ resolveSessionPath(sessionId: string): Promise<string | null>;
24
+ deleteSession(sessionId: string): Promise<boolean>;
25
+ private findSessionRow;
26
+ }
27
+ /**
28
+ * Bundle-identity probe (batch bundle mode): does the opencode session store
29
+ * hold a session with EXACTLY this id, and which directory does it belong
30
+ * to? opencode's `session/load` response does not echo the sessionId
31
+ * (verified on the wire, 2026-09-25), so the bundle driver verifies the
32
+ * resume target here instead. Read-only, with the same busy_timeout
33
+ * rationale as OpenCodeProvider.openDb.
34
+ */
35
+ export declare function opencodeSessionStoreEntry(sessionId: string, opts?: {
36
+ dbPath?: string;
37
+ }): {
38
+ directory: string;
39
+ } | null;
40
+ export {};
@@ -0,0 +1,47 @@
1
+ export interface AcpResult {
2
+ result: string;
3
+ sessionId: string;
4
+ durationMs: number;
5
+ stopReason: string;
6
+ exitCode: number | null;
7
+ /** Human-readable strings when this turn's provider silently dropped a
8
+ * declared model=/effort= (task #232) -- e.g. a `session/set_config_option`
9
+ * rejection the turn proceeded past anyway. Omitted when nothing was
10
+ * dropped. */
11
+ warnings?: string[];
12
+ }
13
+ export interface AcpOutputProcessorResult {
14
+ result: string;
15
+ sessionId: string;
16
+ stopReason: string;
17
+ }
18
+ export interface AcpOutputProcessor {
19
+ setSessionId(sessionId: string): void;
20
+ beginTurn(): void;
21
+ completeTurn(promptResponse: Record<string, unknown>): AcpOutputProcessorResult;
22
+ /**
23
+ * Abandon the in-flight turn WITHOUT recording a result (bundle
24
+ * cancellation): stops collecting, discards the partial text, and leaves
25
+ * `gotResult` untouched so a late-arriving prompt response cannot be
26
+ * mistaken for a completed turn. The next `beginTurn()` starts clean.
27
+ */
28
+ cancelTurn(): void;
29
+ processMessage(message: Record<string, unknown>): boolean;
30
+ finalize(): AcpOutputProcessorResult | null;
31
+ }
32
+ export declare function isolateGeminiLatestAgentMessage(text: string): string;
33
+ export declare function acpSessionIdFromResponse(response: Record<string, unknown>, fallback?: string): string;
34
+ export declare function acpStopReasonFromResponse(response: Record<string, unknown>): string;
35
+ export declare function createAcpOutputProcessor(opts?: {
36
+ tool?: string;
37
+ onDelta?: (text: string) => void;
38
+ /**
39
+ * Result-text isolation for providers that replay prior turns (gemini's
40
+ * latest-agent-message isolation). Supplied by the provider's ACP
41
+ * profile (transports/acp-profiles/); when absent, falls back to the
42
+ * historical `tool === "gemini"` keying so direct callers that pass
43
+ * only a tool name keep their behavior.
44
+ */
45
+ isolateResult?: (text: string) => string;
46
+ }): AcpOutputProcessor;
47
+ export declare function supportsAcp(tool: string): boolean;
@@ -0,0 +1,60 @@
1
+ export type CodexPermissionMode = "plan" | "yolo";
2
+ export declare function codexSandboxForPermissionMode(mode: CodexPermissionMode): string;
3
+ export declare function buildCodexThreadStartParams(opts: {
4
+ cwd: string;
5
+ permissionMode?: CodexPermissionMode;
6
+ model?: string;
7
+ }): Record<string, unknown>;
8
+ export declare function buildCodexThreadResumeParams(opts: {
9
+ threadId: string;
10
+ permissionMode?: CodexPermissionMode;
11
+ model?: string;
12
+ }): Record<string, unknown>;
13
+ export interface CodexInitializePayload extends Record<string, unknown> {
14
+ clientInfo: {
15
+ name: string;
16
+ title: string;
17
+ version: string;
18
+ };
19
+ capabilities: {
20
+ experimentalApi: boolean;
21
+ };
22
+ }
23
+ export declare function buildInitializePayload(version?: string): CodexInitializePayload;
24
+ export type AgentMessagePhase = "commentary" | "final_answer";
25
+ export interface AgentMessageItemState {
26
+ phase: AgentMessagePhase | undefined;
27
+ buffer: string;
28
+ }
29
+ export interface CodexAgentAccumulatorState {
30
+ agentItems: Map<string, AgentMessageItemState>;
31
+ resultText: string;
32
+ }
33
+ export interface CodexItemMismatchInfo {
34
+ itemId: string;
35
+ threadId?: string;
36
+ turnId?: string;
37
+ expectedFromItemText: string;
38
+ gotFromBuffer: string;
39
+ }
40
+ export declare function createCodexAgentAccumulator(): CodexAgentAccumulatorState;
41
+ export declare function processCodexAgentNotification(notification: Record<string, unknown>, state: CodexAgentAccumulatorState, onDelta?: (text: string) => void, onItemMismatch?: (info: CodexItemMismatchInfo) => void): boolean;
42
+ export declare function flushCodexAgentAccumulator(state: CodexAgentAccumulatorState): void;
43
+ export type CodexErrorDisposition = {
44
+ willRetry: true;
45
+ } | {
46
+ willRetry: false;
47
+ turnError: Error;
48
+ };
49
+ export declare function classifyCodexErrorDisposition(notification: Record<string, unknown>): CodexErrorDisposition;
50
+ export interface CodexTurnState {
51
+ pendingCodexError: Error | null;
52
+ }
53
+ export declare function createCodexTurnState(): CodexTurnState;
54
+ export declare function recordCodexErrorNotification(notification: Record<string, unknown>, state: CodexTurnState): CodexErrorDisposition;
55
+ export declare function resolveCodexTurnCompletion(notification: Record<string, unknown>, state: CodexTurnState): {
56
+ shouldReject: true;
57
+ turnError: Error;
58
+ } | {
59
+ shouldReject: false;
60
+ };
@@ -0,0 +1,57 @@
1
+ export interface StreamJsonResult {
2
+ result: string;
3
+ sessionId: string;
4
+ durationMs: number;
5
+ numTurns: number;
6
+ exitCode: number | null;
7
+ /** Set only by the result-grace-timeout fallback in transports/stream-json.ts
8
+ * (BUG-claude-cli-stalls-after-end-turn.md): the model finished responding
9
+ * but the CLI never emitted its own closing `result` event. Same convention
10
+ * as TurnResult.warnings -- informational, does not change the outcome. */
11
+ warnings?: string[];
12
+ }
13
+ export interface StreamJsonProcessor {
14
+ processLine(line: string): void;
15
+ sawResult(): boolean;
16
+ finalize(): {
17
+ result: string;
18
+ sessionId: string;
19
+ durationMs: number;
20
+ numTurns: number;
21
+ /**
22
+ * True when the aggregated turn produced NO usable text and at least
23
+ * one `result` event reported `is_error: true` — see
24
+ * AggregatedResultEvents.isError below for why this must not be
25
+ * conflated with "the model genuinely returned nothing."
26
+ */
27
+ isError: boolean;
28
+ errorMessage?: string;
29
+ } | null;
30
+ }
31
+ export interface AggregatedResultEvents {
32
+ resultTexts: string[];
33
+ durationMs: number;
34
+ numTurns: number;
35
+ sessionId: string;
36
+ /**
37
+ * A `result` event can report `is_error: true` with NO `result` field at
38
+ * all (confirmed live, 2026-09-16, against a real `claude` CLI resuming
39
+ * an evicted/unresolvable session id: subtype "error_during_execution",
40
+ * no "result" key, the actual message only in an `errors: string[]`
41
+ * array). Without this flag that shape reads as a genuine empty
42
+ * response — "status: ok, content: (empty)" — indistinguishable from a
43
+ * model that legitimately said nothing, when the two need completely
44
+ * different remedies (a member refresh vs. investigating the prompt).
45
+ * `errorMessage` is the joined `errors` array, or a subtype-based
46
+ * fallback when `errors` is absent.
47
+ */
48
+ isError: boolean;
49
+ errorMessage?: string;
50
+ }
51
+ export declare function joinResultTurns(texts: string[]): string;
52
+ export declare function createStreamJsonProcessor(opts?: {
53
+ onEvent?: (event: Record<string, unknown>) => void;
54
+ onText?: (text: string) => void;
55
+ }): StreamJsonProcessor;
56
+ export declare function aggregateResultEvents(events: Array<Record<string, unknown>>): AggregatedResultEvents;
57
+ export declare function supportsStreamJson(tool: string): boolean;
@@ -0,0 +1,155 @@
1
+ /**
2
+ * Passive (read-only, bounded, certified) reader for Antigravity CLI (`agy`)
3
+ * sessions.
4
+ *
5
+ * `agy` lives under `~/.gemini/antigravity-cli` but is NOT Gemini CLI — the
6
+ * shared parent directory is a naming coincidence, and nothing in this reader is
7
+ * shared with a Gemini/ACP path.
8
+ *
9
+ * ## The two candidate sources, and why only one is used
10
+ *
11
+ * 1. `conversations/<session>.db` — SQLite. Tables: `trajectory_meta`, `steps`,
12
+ * `gen_metadata`, `executor_metadata`, `parent_references`,
13
+ * `trajectory_metadata_blob`, `battle_mode_infos`. **Every payload column is
14
+ * an opaque protobuf blob** (`google3/third_party/jetski/cortex_pb`), not
15
+ * JSON. One `gen_metadata` row == one model generation.
16
+ * 2. `brain/<session>/.system_generated/logs/transcript_full.jsonl` (and the
17
+ * older compact `transcript.jsonl`) — readable JSONL.
18
+ *
19
+ * A census of every local transcript (141 files, 13 801 records) shows the JSONL
20
+ * carries ONLY: `step_index`, `source`, `type`, `status`, `created_at`,
21
+ * `content`, `tool_calls`, `thinking`, `exit_code`, `error`, `error_code`.
22
+ * There is **no token field, no model field and no per-response id anywhere in
23
+ * it**, so it cannot supply a `response_id` and cannot be the response source.
24
+ * It is therefore not read here at all; see the owner report for why joining it
25
+ * back to a generation is a separate, larger piece of work.
26
+ *
27
+ * ## What IS certified from the database
28
+ *
29
+ * Read with a schema-less protobuf wire walker (field numbers only — no
30
+ * generated stubs, nothing evaluated), cross-checked across 4 sessions and 2
31
+ * model families:
32
+ *
33
+ * - `gen_metadata.data` field 1 is the generation's chat-model metadata message.
34
+ * - Inside it, field 20 is a repeated `{1:key, 2:value}` string map. It contains
35
+ * `request_id` (`<trajectory_id>-<n>`), which is unique per generation and is
36
+ * used as `response_id`. It also contains `trajectory_id`, `last_step_index`,
37
+ * `model_enum`, `last_execution_id` and `used_claude`/`used_non_gemini_model`
38
+ * flags. Cross-checked: the top-level field-4 UUID is NOT per-generation (it
39
+ * repeats across rows) and is deliberately not used as an id.
40
+ * - Field 19 is the response model name string (`gemini-3.8-flash`,
41
+ * `gemini-pro-default` locally).
42
+ * - `last_step_index` indexes the `steps` table, whose `step_payload` field 5
43
+ * sub-field 1 is a `google.protobuf.Timestamp`. That join gives a real
44
+ * per-generation timestamp and is verified monotonic against generation order.
45
+ *
46
+ * ## What is NOT certified: tokens
47
+ *
48
+ * ## Tokens — RESOLVED via the installed client's own embedded schema
49
+ *
50
+ * Field 4 of the chat-model metadata was long treated as an unnamed counter
51
+ * block because no `.proto` was available and the standard `UsageMetadata`
52
+ * interpretation was directly ruled out (its assumed field 3 was smaller than
53
+ * its assumed field 2 — impossible for total-vs-candidates under that guess).
54
+ * That block is NOT a guess anymore: the installed `agy` binary is a Go build
55
+ * of `google.golang.org/protobuf`, which embeds each compiled `.proto` file's
56
+ * real `FileDescriptorProto` bytes for reflection. Locating the descriptor's
57
+ * self-declared name field (`third_party/jetski/cortex_pb/cortex.proto`,
58
+ * tag `0x0a` immediately preceding it — distinguishing it from the many
59
+ * `0x1a` dependency-reference copies of the same string elsewhere in the
60
+ * binary) and parsing the schema-less-walked byte range with the real
61
+ * `google.protobuf` library recovers the ACTUAL message: field 4 is
62
+ * `exa.codeium_common_pb.ModelUsageStats`, not a generic `UsageMetadata`.
63
+ * Its real fields (confirmed against 6,662 real local rows, independently
64
+ * re-derived and cross-checked by a second reviewer against the raw binary):
65
+ * 2 input_tokens, 3 output_tokens, 4 cache_write_tokens (never observed
66
+ * non-null locally — genuinely rare/unused, not unread), 5 cache_read_tokens
67
+ * (absent on some rows — no cache yet), 9 thinking_output_tokens,
68
+ * 10 response_output_tokens, 1 model (enum; the string alias is already
69
+ * separately captured from field 19 above), 7/11/12 assorted opaque id
70
+ * strings (message_id/response_id/provider_assigned_message_id — these,
71
+ * not a "bot id"/"session id", are what the pre-schema walk was seeing).
72
+ * `response_output_tokens + thinking_output_tokens === output_tokens` EXACTLY
73
+ * on all 6,662/6,662 rows carrying both — proof (not inference) that
74
+ * `output_tokens` is the total and `thinking_output_tokens` is its reasoning
75
+ * subset. `cache_write_tokens`'s relationship (if any) to `input_uncached` is
76
+ * deliberately left unasserted — see `agyUsage`'s own doc comment below; a
77
+ * shared field name across providers is not evidence of shared semantics.
78
+ * `input_tokens` is used directly as `input_uncached`, never subtracted from:
79
+ * real rows show `input_tokens < cache_read_tokens` (e.g. 3280 vs 12145),
80
+ * which is only possible if the two are independent additive counts, not an
81
+ * inclusive total/subset pair — ruling out subtraction the same way the
82
+ * original block-level guess was ruled out (an impossible relation under the
83
+ * wrong model). Missing/invalid buckets stay NULL per-field (never a guessed
84
+ * 0) — `cache_write_tokens` in particular is null on every locally observed
85
+ * row precisely because it has genuinely never been populated here, not
86
+ * because it is unread.
87
+ *
88
+ * `files_read` / `files_written` / `external_io` are NULL (unknown), not `[]`:
89
+ * tool calls live in `steps.step_payload`, a different opaque message, and in
90
+ * the un-joinable transcript. NULL correctly forces `read_bytes`/`write_bytes`
91
+ * to NULL rather than presenting "no tools observed" as "no I/O occurred".
92
+ */
93
+ import type { ReadLimits, Snapshot, Source, SourceState } from "../passive-session-reader.js";
94
+ import { type TokenValues } from "./shared.js";
95
+ export declare const AGY_PROVIDER = "agy";
96
+ export declare const AGY_EXTRACTION_REVISION_TAG = "agy-tokens-v1";
97
+ /**
98
+ * `steps.step_payload` → ISO timestamp, via field 5 (step metadata) sub-field 1
99
+ * (`google.protobuf.Timestamp` {1:seconds, 2:nanos}). Returns null when the
100
+ * shape is absent — never a fabricated or file-mtime-derived time.
101
+ */
102
+ export declare function agyStepTimestamp(stepPayload: Buffer): string | null;
103
+ /** One generation extracted from a `gen_metadata` row. */
104
+ export interface AgyGeneration {
105
+ /** Native `request_id` — `<trajectory_id>-<n>`, unique per generation. */
106
+ requestId: string;
107
+ model: string | null;
108
+ /** `steps.idx` this generation ended on; the timestamp join key. */
109
+ lastStepIndex: number | null;
110
+ tokens: TokenValues;
111
+ thinkingOutputTokens: number | null;
112
+ }
113
+ /**
114
+ * Extract the certified facts from one `gen_metadata.data` blob. Returns null
115
+ * when the row carries no `request_id` — without a native id the generation
116
+ * cannot be addressed, and inventing one is forbidden.
117
+ */
118
+ export declare function parseAgyGeneration(data: Buffer): AgyGeneration | null;
119
+ export interface AgyPassiveReaderOptions {
120
+ /** Defaults to `$HOME/.gemini/antigravity-cli/conversations`. */
121
+ agyConversationsDir?: string;
122
+ }
123
+ export declare class AgyPassiveReader {
124
+ private readonly conversationsDir;
125
+ private readonly sqliteBusyTimeoutMs;
126
+ constructor(options?: AgyPassiveReaderOptions & {
127
+ sqliteBusyTimeoutMs?: number;
128
+ });
129
+ /**
130
+ * Enumerate up to `cap` `<conversationsDir>/<session>.db` files ordered by real
131
+ * path. A flat listing, so there is no traversal budget to exhaust; `truncated`
132
+ * is always false and is returned only to match the other readers' shape.
133
+ *
134
+ * `.db-wal` / `.db-shm` companions are excluded by the exact `.db` suffix test:
135
+ * they are not sources, and treating one as a session would mint a bogus id.
136
+ */
137
+ discover(limits: ReadLimits, cap: number, afterPath?: string | null): Promise<{
138
+ sources: Source[];
139
+ truncated: boolean;
140
+ }>;
141
+ /**
142
+ * Open the conversation database read-only, after proving the path is a real,
143
+ * non-symlink file inside the configured conversations directory AND that its
144
+ * basename is the claimed session id.
145
+ *
146
+ * Returns null (→ `unavailable`) rather than throwing when the file is not a
147
+ * usable SQLite database. A live session's `-wal`/`-shm` companions are read
148
+ * through by SQLite itself; a read-only open that cannot reach them fails
149
+ * closed here instead of returning a half-visible snapshot.
150
+ */
151
+ private open;
152
+ inspect(source: Source, limits: ReadLimits): Promise<SourceState>;
153
+ read(source: Source, revision: string, limits: ReadLimits): Promise<Snapshot>;
154
+ }
155
+ export declare function createAgyPassiveReader(options?: AgyPassiveReaderOptions): AgyPassiveReader;
@@ -0,0 +1,173 @@
1
+ /**
2
+ * Passive (read-only, bounded, certified) reader for GitHub Copilot CLI sessions.
3
+ *
4
+ * ## What the native store actually is (real records, not assumed)
5
+ *
6
+ * Copilot writes one append-only event log per session:
7
+ * `$HOME/.copilot/session-state/<uuid>/events.jsonl` (+ a `workspace.yaml`).
8
+ * Each line is a JSON object `{ type, id, timestamp, data }`. The event TYPES seen
9
+ * locally: `session.start`, `session.model_change`, `session.info`,
10
+ * `system.message`, `user.message`, `assistant.turn_start`, `assistant.message`,
11
+ * `assistant.turn_end`, `tool.execution_start`, `tool.execution_complete`,
12
+ * `session.shutdown`.
13
+ *
14
+ * ## Token semantics — the copilot warning, verified on disk
15
+ *
16
+ * lmbi's directive: "Copilot has cumulative per-model shutdown totals and some
17
+ * per-message usage; NEVER distribute a session total over responses without
18
+ * evidence." Measured against real sessions:
19
+ *
20
+ * - `assistant.message.outputTokens` IS a genuine PER-RESPONSE count.
21
+ * - There is NO per-message input / cache / reasoning field anywhere on the wire.
22
+ * Those counters exist only in `session.shutdown.modelMetrics` snapshots.
23
+ *
24
+ * CORRECTED (real evidence, not the original assumption): `session.shutdown`
25
+ * is NOT a proven session-wide cumulative. A session can carry MANY shutdown
26
+ * events (measured: up to 24 in one real session), and a shutdown's
27
+ * `requests.count`/`outputTokens` can be far smaller than the session's real
28
+ * total turn count/output sum (measured: a 201-turn session's last shutdown
29
+ * reported `requests.count=7`, `outputTokens=1984` against a real per-message
30
+ * sum of 82,375 — a ~97.6% undercount). So each shutdown snapshot's semantics
31
+ * are `"reported_scope_unknown"`, never asserted cumulative.
32
+ *
33
+ * Therefore each response record here carries `tokens.output` (real, per-message)
34
+ * and leaves `input_uncached` / `input_cached` / `input_cache_write` NULL — the
35
+ * shutdown snapshot's input is NEVER divided across responses. Every shutdown
36
+ * snapshot is surfaced SEPARATELY and RAW as `CopilotSessionUsage` (see
37
+ * `readCopilot`/`readWithUsage`) — never spread over the per-response rows,
38
+ * never emitted as a fabricated pseudo-response, and never renamed/normalized
39
+ * into this reader's own canonical token vocabulary (that's lmbi's own
40
+ * downstream decision to make, not this reader's).
41
+ *
42
+ * ## Identity, revision, attribution
43
+ *
44
+ * - `response_id` = `assistant.message.data.messageId` (a real uuid); never
45
+ * invented. Repeated events for one messageId sum their `outputTokens`.
46
+ * - The revision is the sha256 of the exact `events.jsonl` bytes (extraction-
47
+ * semantics-tagged, see EXTRACTION_SEMANTICS) with
48
+ * a dev/ino/size/mtime recheck: a live append caught mid-write is `partial`,
49
+ * never a clean revision, and a file not ending on a record boundary is refused.
50
+ * - Prompt admission: `user.message.data.content` bytes attach to the FIRST
51
+ * assistant response of the same `interactionId`; later responses in that
52
+ * interaction are a real 0 (the prompt was already admitted). A response whose
53
+ * interaction has no visible user admission (truncated history / system-driven
54
+ * turn) gets an unknown coverage marker — "no input" cannot be certified.
55
+ *
56
+ * ## File / external-I/O (bx2), conservative by contract
57
+ *
58
+ * `create` ({path,file_text}) and `apply_patch` (<patch string>) are KNOWN writes
59
+ * with measurable added bytes. `view` ({path}) is a KNOWN read whose bytes are
60
+ * NULL — the result content is line-decorated with no clean chunk to count, so the
61
+ * honest size is unavailable (not guessed). `report_intent` is internal control
62
+ * (neither direction). Everything else opaque (`bash`/`read_bash`/`write_bash`/
63
+ * `stop_bash`/`list_bash`/`rg`/`glob`/`sql`/`fetch_*`/…) is unknown in BOTH
64
+ * directions, forcing that direction's aggregate NULL.
65
+ */
66
+ import type { ReadLimits, Snapshot, Source, SourceState } from "../passive-session-reader.js";
67
+ export declare const COPILOT_PROVIDER = "copilot";
68
+ /**
69
+ * One event line of a copilot `events.jsonl`. Only the fields this reader reads.
70
+ */
71
+ interface CopilotEvent {
72
+ type?: unknown;
73
+ id?: unknown;
74
+ timestamp?: unknown;
75
+ data?: unknown;
76
+ }
77
+ /**
78
+ * One native model's RAW `session.shutdown` counters. Field NAMES are copied
79
+ * directly from the native wire (`usage.inputTokens` -> `input_tokens`,
80
+ * `tokenDetails.input.tokenCount` -> `input_other_tokens`, etc.) — this reader
81
+ * does NOT compute, clamp, infer, or rename a "canonical uncached" figure here.
82
+ * Per lmbi's explicit instruction: raw naming preserves the native facts while
83
+ * this snapshot's own window/scope semantics remain unproven; normalizing
84
+ * (e.g. `input_uncached = input_tokens - cache_read_tokens`) is a downstream
85
+ * decision for the consumer, not this reader's to bake in.
86
+ */
87
+ export interface CopilotModelUsage {
88
+ model: string;
89
+ requests_count: number | null;
90
+ /** `usage.inputTokens`, copied as-is. */
91
+ input_tokens: number | null;
92
+ /** `tokenDetails.input.tokenCount`, copied as-is — a real native "fresh"
93
+ * figure, narrower than `input_tokens`. NOT a canonical uncached total. */
94
+ input_other_tokens: number | null;
95
+ cache_read_tokens: number | null;
96
+ cache_write_tokens: number | null;
97
+ output_tokens: number | null;
98
+ reasoning_tokens: number | null;
99
+ }
100
+ /**
101
+ * One native `session.shutdown` EVENT, with every model in its `modelMetrics`
102
+ * grouped underneath (mirrors the native shape: one event, many models) —
103
+ * surfaced beside, never inside, the per-response rows.
104
+ *
105
+ * CORRECTED (real evidence, not assumption): a session can carry MULTIPLE
106
+ * `session.shutdown` events (measured: up to 24 in one real session on this box),
107
+ * and a shutdown's `requests.count`/`usage.outputTokens` can be far smaller than
108
+ * the session's real total turn count/output sum (measured: a 201-turn session's
109
+ * last shutdown reported `requests.count=7`, `outputTokens=1984` against a real
110
+ * per-message sum of 82,375 — a ~97.6% undercount). So this is NOT a proven
111
+ * session-wide cumulative; `semantics: "reported_scope_unknown"` reflects that
112
+ * honestly. Every shutdown event is retained (never "last wins", exact
113
+ * duplicate `native_id` replays coalesce to one), each with its own native
114
+ * event id/timestamp, so later analysis isn't precluded by discarding earlier
115
+ * snapshots.
116
+ */
117
+ export interface CopilotSessionUsage {
118
+ /** The `session.shutdown` event's own top-level `id` (a real uuid). An event
119
+ * with no real id is never given a fabricated one — it is excluded from this
120
+ * array entirely (an explicit capture gap, not silently-fine metadata). */
121
+ native_id: string;
122
+ /** The `session.shutdown` event's own top-level `timestamp`, canonicalized. */
123
+ recorded_at: string | null;
124
+ kind: "copilot_session_shutdown";
125
+ semantics: "reported_scope_unknown";
126
+ models: CopilotModelUsage[];
127
+ }
128
+ export interface CopilotReadResult {
129
+ snapshot: Snapshot;
130
+ /** null when the session has no shutdown event with a real id yet (live/torn,
131
+ * or every shutdown event on this session lacked an id) — normal. */
132
+ sessionUsage: CopilotSessionUsage[] | null;
133
+ }
134
+ /**
135
+ * Extract the per-response snapshot AND the retained session-usage snapshots
136
+ * (scope-unknown, never asserted cumulative) from a parsed copilot event log.
137
+ * Pure and deterministic. A malformed event never throws; the
138
+ * whole snapshot is only refused (`partial`) when a response row count exceeds the
139
+ * budget — otherwise unrecognized shapes degrade to conservative NULL markers.
140
+ */
141
+ export declare function readCopilot(events: CopilotEvent[], source: Source, revision: string, limits: ReadLimits): CopilotReadResult;
142
+ export interface CopilotPassiveReaderOptions {
143
+ /** Defaults to `$HOME/.copilot/session-state`. */
144
+ copilotSessionStateDir?: string;
145
+ }
146
+ export declare class CopilotPassiveReader {
147
+ private readonly sessionStateDir;
148
+ constructor(options?: CopilotPassiveReaderOptions);
149
+ /**
150
+ * Enumerate up to `cap` sources ordered by real path. `afterKey` (a
151
+ * `source_id\0sessionid` sourceKey) continues a previous page. Discovery is a
152
+ * single-level scan of the session-state directory (no deep recursion), so
153
+ * there is no traversal budget to exhaust — `truncated` is always false.
154
+ */
155
+ discover(limits: ReadLimits, cap: number, afterKey?: string | null): Promise<{
156
+ sources: Source[];
157
+ truncated: boolean;
158
+ }>;
159
+ /**
160
+ * A source is ours only when its real path is `<dir>/events.jsonl` contained by
161
+ * the session-state directory AND `<dir>` basename is the claimed session id.
162
+ */
163
+ private matches;
164
+ inspect(source: Source, limits: ReadLimits): Promise<SourceState>;
165
+ /** Contract-shaped read: per-response Snapshot only. Use `readWithUsage` to also
166
+ * obtain the retained session-usage snapshots that must not be distributed
167
+ * over responses (see the module header — their scope is unproven, never
168
+ * asserted cumulative). */
169
+ read(source: Source, revision: string, limits: ReadLimits): Promise<Snapshot>;
170
+ readWithUsage(source: Source, revision: string, limits: ReadLimits): Promise<CopilotReadResult>;
171
+ }
172
+ export declare function createCopilotPassiveReader(options?: CopilotPassiveReaderOptions): CopilotPassiveReader;
173
+ export {};
@@ -0,0 +1,34 @@
1
+ import type { ReadLimits, Snapshot, Source, SourceState } from "../passive-session-reader.js";
2
+ export declare const DSH_PROVIDER = "dsh";
3
+ interface DshRecord {
4
+ type?: unknown;
5
+ seq?: unknown;
6
+ time?: unknown;
7
+ data?: unknown;
8
+ }
9
+ export interface DshPassiveReaderOptions {
10
+ /** Defaults to `$DSH_HOME/sessions`, then `~/.dsh/sessions`. */
11
+ dshHome?: string;
12
+ maxDiscoveryTraversalVisits?: number;
13
+ }
14
+ export declare function readDsh(records: DshRecord[], source: Source, revision: string, limits: ReadLimits): Snapshot;
15
+ export declare class DshPassiveReader {
16
+ private readonly sessionsDir;
17
+ private readonly maxDiscoveryTraversalVisits;
18
+ constructor(options?: DshPassiveReaderOptions);
19
+ /**
20
+ * Enumerate up to `cap` sources ordered by real path. `afterPath` continues a
21
+ * previous page. The caller owns cursor encoding and the cross-provider merge.
22
+ */
23
+ discover(limits: ReadLimits, cap: number, afterPath?: string | null): Promise<{
24
+ sources: Source[];
25
+ truncated: boolean;
26
+ }>;
27
+ /** Ours only when a real, non-symlink `session.jsonl.zstd` whose parent dir name
28
+ * is the claimed session id. */
29
+ private matches;
30
+ inspect(source: Source, limits: ReadLimits): Promise<SourceState>;
31
+ read(source: Source, revision: string, limits: ReadLimits): Promise<Snapshot>;
32
+ }
33
+ export declare function createDshPassiveReader(options?: DshPassiveReaderOptions): DshPassiveReader;
34
+ export {};
@@ -0,0 +1,44 @@
1
+ import type { ReadLimits, Snapshot, Source, SourceState } from "../passive-session-reader.js";
2
+ export declare const KIMI_PROVIDER = "kimi";
3
+ export interface KimiPassiveReaderOptions {
4
+ /** Defaults to `$KIMI_CODE_HOME`, then `~/.kimi-code`. */
5
+ kimiHome?: string;
6
+ maxDiscoveryTraversalVisits?: number;
7
+ }
8
+ interface WireLine {
9
+ type?: unknown;
10
+ agentId?: unknown;
11
+ event?: unknown;
12
+ time?: unknown;
13
+ model?: unknown;
14
+ modelAlias?: unknown;
15
+ turnStep?: unknown;
16
+ input?: unknown;
17
+ origin?: unknown;
18
+ promptId?: unknown;
19
+ protocol_version?: unknown;
20
+ usage?: unknown;
21
+ }
22
+ export declare function readKimi(lines: WireLine[], source: Source, revision: string, limits: ReadLimits): Snapshot;
23
+ export declare class KimiPassiveReader {
24
+ private readonly home;
25
+ private readonly sessionsDir;
26
+ private readonly indexPath;
27
+ private readonly maxDiscoveryTraversalVisits;
28
+ constructor(options?: KimiPassiveReaderOptions);
29
+ /**
30
+ * Enumerate up to `cap` agent-wire sources ordered by real path. `afterPath`
31
+ * continues a previous page. Unions the directory walk with the append-only
32
+ * index so a missing/damaged index still recovers sessions.
33
+ */
34
+ discover(limits: ReadLimits, cap: number, afterPath?: string | null): Promise<{
35
+ sources: Source[];
36
+ truncated: boolean;
37
+ }>;
38
+ private unionIndex;
39
+ private matches;
40
+ inspect(source: Source, limits: ReadLimits): Promise<SourceState>;
41
+ read(source: Source, revision: string, limits: ReadLimits): Promise<Snapshot>;
42
+ }
43
+ export declare function createKimiPassiveReader(options?: KimiPassiveReaderOptions): KimiPassiveReader;
44
+ export {};