@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,107 @@
1
+ import type { AliasTable } from "./model-catalog.js";
2
+ export declare const PRICE_DATA_URL = "https://lmctl.com/dataset/model_list.json";
3
+ export declare const PRICE_ALIASES_URL = "https://lmctl.com/dataset/aliases.json";
4
+ export type PriceDataSource = "network" | "cache" | "bundled" | "pinned" | "unavailable";
5
+ export interface PriceRecord {
6
+ source: string;
7
+ source_model_id: string;
8
+ provider?: string | null;
9
+ mode?: string | null;
10
+ record_id?: string | null;
11
+ gateway_id?: string | null;
12
+ context_window?: {
13
+ input_tokens?: number | null;
14
+ } | null;
15
+ pricing: {
16
+ input_per_1m_tokens: number | null;
17
+ output_per_1m_tokens: number | null;
18
+ input_cache_read_per_1m_tokens: number | null;
19
+ };
20
+ }
21
+ export interface PriceCatalog {
22
+ schema_version: number;
23
+ generated_at: string;
24
+ records: PriceRecord[];
25
+ }
26
+ export interface PriceDataResult {
27
+ catalog: PriceCatalog | null;
28
+ source: PriceDataSource;
29
+ generatedAt: string | null;
30
+ }
31
+ export interface CatalogSnapshot {
32
+ catalog: PriceCatalog | null;
33
+ aliases: AliasTable | null;
34
+ source: PriceDataSource;
35
+ generatedAt: string | null;
36
+ }
37
+ export declare function isValidCatalog(payload: unknown): payload is PriceCatalog;
38
+ export declare function isValidAliases(payload: unknown): payload is AliasTable;
39
+ export interface LoadCatalogOptions {
40
+ fetchImpl?: typeof fetch;
41
+ cacheDir?: string;
42
+ /** Override for the bundled model_list path; `null` disables that fallback. */
43
+ bundledPath?: string | null;
44
+ /** Override for the bundled aliases path; `null` disables that fallback. */
45
+ bundledAliasesPath?: string | null;
46
+ /** Override for LMCTL_PRICE_DATA; `null` forces unpinned. */
47
+ pinDir?: string | null;
48
+ /** Test seam for the TTL gate (defaults to Date.now()). */
49
+ now?: number;
50
+ /** Test seam overriding LMCTL_PRICE_TTL (ms). */
51
+ ttlMs?: number;
52
+ }
53
+ /** Dataset generated_at of the most recently loaded snapshot, for --debug surfaces. */
54
+ export declare function datasetGeneratedAt(): string | null;
55
+ /** Test seam: drop the memoized no-argument snapshot (process-level memo). */
56
+ export declare function _resetCatalogSnapshotMemoForTests(): void;
57
+ /**
58
+ * The one snapshot loader for the lmprice integration. Never throws. One
59
+ * snapshot load per process invocation: the no-argument call (the normal
60
+ * command path) is memoized module-wide, so lint/seed/cost members never
61
+ * trigger per-member re-downloads; calls with explicit options (tests,
62
+ * seams) always execute.
63
+ */
64
+ export declare function loadCatalogSnapshot(opts?: LoadCatalogOptions): Promise<CatalogSnapshot>;
65
+ /**
66
+ * The one function `lmctl cost`'s Phase 1 callers use. Never throws, never
67
+ * blocks longer than `VERSION_CHECK_TIMEOUT_MS` for the "is there anything
68
+ * new" check regardless of network state (per resource, outside the TTL) --
69
+ * a HEAD request specifically so a slow/large BODY download can never blow
70
+ * that budget. Thin wrapper over loadCatalogSnapshot: the model-list half
71
+ * of the snapshot with the same result shape as before the two-resource
72
+ * extension.
73
+ */
74
+ export declare function loadPriceData(opts?: LoadCatalogOptions): Promise<PriceDataResult>;
75
+ /**
76
+ * Records are source-specific and NOT reconciled across sources (lmprice's
77
+ * own documented behavior) -- the same model can appear many times (bare id,
78
+ * `vendor/id`, `region/vendor/id`, ...) with the litellm and openrouter
79
+ * sources potentially disagreeing. `records` is already sorted by
80
+ * (source, provider, source_model_id) -- "litellm" sorts before "openrouter"
81
+ * -- so preferring the first exact-bare-id match, then the first
82
+ * suffix (`/<modelId>`) match, is a deterministic, if arbitrary, tie-break
83
+ * rather than an average/reconciliation across disagreeing sources.
84
+ *
85
+ * Superseded for provider-aware resolution by model-catalog.ts's
86
+ * resolveModel(); kept for `lmctl cost` Phase 1 compatibility.
87
+ */
88
+ export declare function findModelPrice(catalog: PriceCatalog, modelId: string): PriceRecord["pricing"] | null;
89
+ /**
90
+ * Phase 1 cost model (approved design): the session's existing AGGREGATE
91
+ * token totals x one model's rate -- not a true per-model breakdown (see
92
+ * `runCost`'s `approximate` flag for when a session touched more than one
93
+ * model; that's a separate, later sub-task). `cachedInput` is priced at the
94
+ * cache-READ rate only -- lmprice's `input_cache_write_per_1m_tokens` isn't
95
+ * consumed here because lctl-src's own token accounting
96
+ * (`totalCachedInputTokens`) has never distinguished cache-write from
97
+ * cache-read tokens; conflating them into the (usually cheaper) read rate
98
+ * would UNDERSTATE cost for a cache-write-heavy session, but overstating via
99
+ * the (usually pricier) write rate for every cache hit is worse on average.
100
+ * Returns `null` when any needed price is missing -- never a partial/guessed
101
+ * number.
102
+ */
103
+ export declare function computeCostUsd(tokens: {
104
+ input: number;
105
+ output: number;
106
+ cachedInput: number;
107
+ }, pricing: PriceRecord["pricing"] | null): number | null;
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Qwen Code session provider.
3
+ *
4
+ * Qwen Code is a Claude Code fork — same JSONL session format in ~/.qwen/.
5
+ * This provider reuses the Claude provider logic with Qwen-specific paths.
6
+ */
7
+ import type { SessionProvider, SessionSummary, SessionDetail } from "./types.js";
8
+ interface QwenProviderOptions {
9
+ qwenDir?: string;
10
+ }
11
+ export declare class QwenProvider implements SessionProvider {
12
+ name: "qwen";
13
+ private baseDir;
14
+ constructor(opts?: QwenProviderOptions);
15
+ discoverSessions(): Promise<SessionSummary[]>;
16
+ private extractSummary;
17
+ loadSession(sessionId: string): Promise<SessionDetail | null>;
18
+ private loadFromFile;
19
+ resolveSessionPath(sessionId: string): Promise<string | null>;
20
+ deleteSession(sessionId: string): Promise<boolean>;
21
+ }
22
+ export {};
@@ -0,0 +1,32 @@
1
+ export declare const VALID_RATELIMIT_TARGETS: readonly ["all", "codex", "agy", "claude", "kimi"];
2
+ export interface RatelimitRunResult {
3
+ status: number | null;
4
+ stdout: string;
5
+ stderr: string;
6
+ signal?: string | null;
7
+ error?: (Error & {
8
+ code?: string;
9
+ }) | undefined;
10
+ }
11
+ export type RatelimitRunner = (bin: string, args: string[], timeoutMs: number) => RatelimitRunResult;
12
+ export interface RatelimitFsDeps {
13
+ exists(path: string): boolean;
14
+ }
15
+ export declare function _setRatelimitRunnerForTests(fn: RatelimitRunner | null): void;
16
+ export declare function _setRatelimitFsDepsForTests(deps: Partial<RatelimitFsDeps> | null): void;
17
+ export interface RatelimitReadingsOptions {
18
+ providers?: string[];
19
+ targets?: string[];
20
+ timeoutSeconds?: number;
21
+ env?: NodeJS.ProcessEnv;
22
+ progress?: (message: string) => void;
23
+ }
24
+ export type RatelimitReadingsResult = {
25
+ status: 'ok';
26
+ readings: Record<string, unknown>[];
27
+ } | {
28
+ status: 'error';
29
+ error: string;
30
+ readings: [];
31
+ };
32
+ export declare function queryRatelimitReadings(options?: RatelimitReadingsOptions): RatelimitReadingsResult;
@@ -0,0 +1,31 @@
1
+ import type { SessionProvider, SessionSummary, SessionDetail, ProviderName, WatchTarget } from "./types.js";
2
+ import type { ProviderAdapter } from "./adapter.js";
3
+ export type { ProviderAdapter };
4
+ export declare class ProviderRegistry {
5
+ private providers;
6
+ private adapters;
7
+ register(provider: SessionProvider): void;
8
+ listAll(filter?: {
9
+ provider?: ProviderName;
10
+ workspace?: string;
11
+ limit?: number;
12
+ from?: Date;
13
+ to?: Date;
14
+ }): Promise<SessionSummary[]>;
15
+ getSession(sessionId: string, provider?: ProviderName): Promise<SessionDetail | null>;
16
+ resolveSession(sessionId: string, provider?: ProviderName): Promise<{
17
+ provider: SessionProvider;
18
+ filePath: string;
19
+ } | null>;
20
+ resolveSessions(sessionId: string, provider?: ProviderName): Promise<Array<{
21
+ provider: SessionProvider;
22
+ filePath: string;
23
+ }>>;
24
+ getWatchTargets(): WatchTarget[];
25
+ deleteSession(sessionId: string, provider?: ProviderName): Promise<boolean>;
26
+ registerAdapter(adapter: ProviderAdapter): void;
27
+ getAdapter(name: ProviderName): ProviderAdapter | undefined;
28
+ getAllAdapters(): ProviderAdapter[];
29
+ }
30
+ export declare function createRegistry(...providers: SessionProvider[]): ProviderRegistry;
31
+ export declare function createAdapterRegistry(adapters: ProviderAdapter[]): ProviderRegistry;
@@ -0,0 +1,43 @@
1
+ import type { ProviderName, SessionDetail } from './types.js';
2
+ export interface ResumeModelIgnored {
3
+ provider: string;
4
+ model: string;
5
+ reason: string;
6
+ }
7
+ /**
8
+ * Defensive sessionid format guard for the step-in spawn (design §2.4 /
9
+ * §10.B B3). Provider-minted ids are UUID-/slug-class; anything else is
10
+ * refused before any spawn (no shell, no injection surface).
11
+ */
12
+ export declare const SESSIONID_RE: RegExp;
13
+ export declare function isResumableProvider(p: string): p is ProviderName;
14
+ /** Test-only: drop the cached registry between cases. */
15
+ export declare function _resetResumeRegistryForTests(): void;
16
+ /**
17
+ * The native resume incantation as an argv array (argv[0] is the PROVIDER
18
+ * binary — never lmctl). Returns null if `provider` has no registered
19
+ * adapter / resumable template. Tokenizes the native `resumeTemplate`
20
+ * (no quoted args in any template) and appends the YOLO suffix.
21
+ */
22
+ export declare function resumeIncantationArgv(provider: string, sessionId: string, opts?: {
23
+ isLead?: boolean;
24
+ model?: string;
25
+ onModelIgnored?: (ignored: ResumeModelIgnored) => void;
26
+ }): string[] | null;
27
+ /** Display form of {@link resumeIncantationArgv} (seed-print + >200K UX). */
28
+ export declare function resumeIncantationString(provider: string, sessionId: string, opts?: {
29
+ model?: string;
30
+ }): string | null;
31
+ /**
32
+ * Provider-agnostic session read (no provider round-trip, no `tokens`):
33
+ * the native per-provider session reader the registry already owns.
34
+ */
35
+ export declare function loadSessionDetail(sessionId: string, provider: ProviderName): Promise<SessionDetail | null>;
36
+ /**
37
+ * P2 — bare-sessionid auto-detect: ask the registry which provider(s)
38
+ * a sessionid resolves to. Returns matching provider names (0/1/N).
39
+ * Callers map 0 ⇒ not-found, 1 ⇒ use that provider, N ⇒ ambiguous (ask
40
+ * the user to pass `--provider`). Thin wrapper over the existing
41
+ * `registry().resolveSessions` — no new coupling.
42
+ */
43
+ export declare function findSessionProviders(sessionId: string): Promise<ProviderName[]>;
@@ -0,0 +1,3 @@
1
+ export declare const SEED_ACK = "ACK: waiting for explicit lmctl task.";
2
+ export declare const SEED_INITIAL_PROMPT: string;
3
+ export declare const MANUAL_SEED_POST_LAUNCH_NOTE = "send this exact lmctl seed instruction: \"lmctl seed initial connection only. Create or pin this provider session, then stop. Do not inspect files, run commands, infer a task, delegate, or edit. A dispatch that dies mid-turn is usually a timeout you set -- use --idle-timeout, not a shell timeout. Reply exactly: ACK: waiting for explicit lmctl task.\"";
@@ -0,0 +1,21 @@
1
+ export declare const LMCTL_SELF_SESSIONID = "LMCTL_SELF_SESSIONID";
2
+ export declare const LMCTL_LAUNCH_SOURCE = "LMCTL_LAUNCH_SOURCE";
3
+ export type LmctlLaunchSource = 'chat' | 'terminal' | 'unknown';
4
+ /**
5
+ * A shell/profile can export `CLAUDE_CODE_EXECPATH` pointing at a binary
6
+ * for the WRONG platform (a real incident: a WSL environment inherited a
7
+ * Windows `claude.exe` from a cross-platform npm install). The `claude`
8
+ * CLI itself trusts this override unconditionally and re-execs into it,
9
+ * so a bad value there breaks every claude-provider spawn with a generic
10
+ * "native binary not installed" error that reads as a global install
11
+ * problem rather than one bad env var. lmctl can't fix the CLI's own
12
+ * re-exec logic, but it fully controls the env it hands the child, so it
13
+ * strips an override that's clearly wrong for the current platform
14
+ * (non-Windows + `.exe`, or a path that doesn't exist) before spawning —
15
+ * the child then falls back to its own default binary discovery instead
16
+ * of blindly trusting a broken pointer.
17
+ */
18
+ export declare function sanitizeClaudeExecPathEnv(env: NodeJS.ProcessEnv): NodeJS.ProcessEnv;
19
+ export declare function withProviderCwdEnv(env: NodeJS.ProcessEnv | undefined, cwd: string): NodeJS.ProcessEnv;
20
+ export declare function withSelfSessionEnv(env: NodeJS.ProcessEnv | undefined, sessionId: string | undefined): NodeJS.ProcessEnv | undefined;
21
+ export declare function withProviderSpawnEnv(env: NodeJS.ProcessEnv | undefined, cwd: string, sessionId: string | undefined, launchSource?: LmctlLaunchSource): NodeJS.ProcessEnv;
@@ -0,0 +1,9 @@
1
+ import type { ConversationMessage } from './types.js';
2
+ export interface SessionLimitState {
3
+ state: 'LIMITED';
4
+ resets_at: string | null;
5
+ reset_time: string;
6
+ timezone: string | null;
7
+ detected_at: string;
8
+ }
9
+ export declare function detectSessionLimit(messages: ConversationMessage[]): SessionLimitState | null;
@@ -0,0 +1,5 @@
1
+ /** Strips ANSI/VT100 escape sequences (color codes, etc.) so provider error
2
+ * text captured from a child process's stderr reads as plain text instead
3
+ * of literal escape-code noise wherever it's surfaced (a captured log, a
4
+ * terminal that doesn't render them, JSON output). */
5
+ export declare function stripAnsiCodes(text: string): string;
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Named error taxonomy for the transport layer (claude stream-json,
3
+ * codex app-server, ACP). Replaces the prior `new Error("RPC error: ...")`
4
+ * string-dump pattern with typed classes that preserve structured fields
5
+ * so downstream callers can branch on numeric codes / inspect the raw
6
+ * line / read the original cause without re-parsing the message.
7
+ *
8
+ * Shape mirrors the claude-agent-sdk-python error hierarchy
9
+ * (CLIConnectionError / CLINotFoundError / ProcessError /
10
+ * CLIJSONDecodeError / MessageParseError) flagged in
11
+ * `tasks/83-claude-sdk-research.md` §4 takeaway 3, and the
12
+ * `createRpcError` helper that the codex-app-server transport
13
+ * adopted from openai/codex-plugin-cc (research:
14
+ * `tasks/83-codex-transport-compare.md`).
15
+ *
16
+ * All classes extend `TransportError`, which extends `Error` — every
17
+ * instance still satisfies `instanceof Error` so existing
18
+ * `try/catch (e: unknown)` sites continue to work without changes.
19
+ * The added discriminator is `instanceof TransportError` (and the
20
+ * specific subclasses) for callers who want the structured fields.
21
+ *
22
+ * The existing `TurnAbortError` (in `base-adapter.ts`) is a separate
23
+ * concern — user-/idle-/provider-cancel reasons surfaced by the
24
+ * higher-level chat orchestrator — and lives outside this taxonomy.
25
+ */
26
+ /**
27
+ * Base class for transport-layer failures. Carries `name` set to the
28
+ * concrete subclass name (so error messages on uncaught throws
29
+ * identify the subtype) and propagates `cause` per the standard
30
+ * Error options shape.
31
+ */
32
+ export declare class TransportError extends Error {
33
+ constructor(message: string, options?: {
34
+ cause?: unknown;
35
+ });
36
+ }
37
+ /**
38
+ * Failed to establish or maintain the subprocess/stdio channel.
39
+ * Examples: `spawn` ENOENT, `spawn` permission denied, write to
40
+ * already-closed stdin during a transport-level shutdown.
41
+ */
42
+ export declare class TransportConnectionError extends TransportError {
43
+ }
44
+ /**
45
+ * The subprocess exited unexpectedly — either before the protocol
46
+ * reached its terminal state, or with a non-zero exit code that the
47
+ * transport considers an error condition.
48
+ *
49
+ * `exitCode` is `null` when the child was killed by signal without an
50
+ * explicit exit code; `signal` carries the POSIX signal name in that
51
+ * case (e.g. `"SIGKILL"`). `stderr` is best-effort — the transport
52
+ * captures the tail of the child's stderr if it was piped.
53
+ */
54
+ export declare class TransportProcessError extends TransportError {
55
+ readonly exitCode: number | null;
56
+ readonly signal: string | null;
57
+ readonly stderr?: string | undefined;
58
+ constructor(message: string, exitCode: number | null, signal?: string | null, stderr?: string | undefined);
59
+ }
60
+ /**
61
+ * A line read off the wire could not be JSON-parsed. The raw `line`
62
+ * is preserved (truncated to a sane length by the caller before
63
+ * construction if it could be very large) so investigators can see
64
+ * exactly what the transport rejected. `cause` is the original
65
+ * `SyntaxError` from `JSON.parse`.
66
+ */
67
+ export declare class TransportJsonError extends TransportError {
68
+ readonly line: string;
69
+ constructor(message: string, line: string, cause: unknown);
70
+ }
71
+ /**
72
+ * The peer responded with a JSON-RPC error object. `rpcCode` is the
73
+ * numeric code from the JSON-RPC `error.code` field when present, and
74
+ * `rpcData` carries the entire `error` payload (including `message`,
75
+ * `data`, and any provider-specific fields like
76
+ * codex-plugin-cc's broker-busy sentinel `-32001`).
77
+ *
78
+ * Callers who care about specific codes branch on `err.rpcCode`;
79
+ * generic catchers see a plain `Error` with a human-readable message.
80
+ */
81
+ export declare class TransportRpcError extends TransportError {
82
+ readonly rpcCode?: number;
83
+ readonly rpcData?: unknown;
84
+ constructor(message: string, rpcCode?: number, rpcData?: unknown);
85
+ }
86
+ /**
87
+ * Convenience constructor that mirrors the prior `createRpcError`
88
+ * helper signature in `codex-app-server.ts`: pass the human message
89
+ * plus the raw JSON-RPC error object, and the helper extracts
90
+ * `code` (if numeric) and stores the whole object as `rpcData`.
91
+ *
92
+ * This is the migration target for `createRpcError` callers.
93
+ */
94
+ export declare function transportRpcErrorFromResponse(message: string, errorObj?: unknown): TransportRpcError;
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Graceful-shutdown ladder for transport child processes.
3
+ *
4
+ * Replaces the prior `child.kill()` (synchronous, hard SIGTERM, no
5
+ * grace) pattern across the claude / codex / ACP transports. The bug
6
+ * this fixes is documented in the claude-agent-sdk-python transport
7
+ * (`subprocess_cli.py:548-568` and the linked issue #625): when
8
+ * `claude` is killed mid-flush its session JSONL loses the last
9
+ * assistant message. The same risk class applies to any provider CLI
10
+ * that persists state on shutdown — codex (`thread/*.jsonl`),
11
+ * opencode (`opencode.db`), gemini (`~/.gemini/tmp/.../chats/`).
12
+ *
13
+ * Ladder:
14
+ * 1. (optional) close stdin so the child observes EOF.
15
+ * 2. wait `softDelayMs` for the child to exit on its own.
16
+ * 3. send SIGTERM (or `taskkill /T /F /PID <pid>` on Windows) and
17
+ * wait `killDelayMs`.
18
+ * 4. send SIGKILL (no-op on Windows, taskkill is already hard).
19
+ * 5. wait up to a fixed final grace for the exit event before
20
+ * returning.
21
+ *
22
+ * Returns a `TerminateResult` describing which path actually ended
23
+ * the child and total elapsed wall-clock — useful for tests and for
24
+ * future telemetry on shutdown latency.
25
+ */
26
+ import type { ChildProcess } from "node:child_process";
27
+ export interface TerminateOptions {
28
+ /** Call `child.stdin?.end()` first to signal clean EOF. Default: true. */
29
+ stdinEnd?: boolean;
30
+ /** Wait ms for child to exit naturally after stdin.end. Default: 5000. */
31
+ softDelayMs?: number;
32
+ /** Wait ms after SIGTERM before SIGKILL. Default: 5000. */
33
+ killDelayMs?: number;
34
+ /**
35
+ * Test seam — replace the Windows process-tree killer
36
+ * (`taskkill /T /F /PID <pid>`) with a no-op or recorder. Defaults
37
+ * to a real `spawnSync("taskkill", ...)` call on Windows; ignored
38
+ * on Unix.
39
+ */
40
+ runTaskkill?: (pid: number) => void;
41
+ /** Test seam — override `process.platform`. */
42
+ platform?: NodeJS.Platform;
43
+ }
44
+ /** Which step in the ladder actually finished the child. */
45
+ export type TerminateSignal = "natural" | "stdin" | "SIGTERM" | "SIGKILL" | "abandoned";
46
+ export interface TerminateResult {
47
+ signal: TerminateSignal;
48
+ elapsedMs: number;
49
+ }
50
+ /**
51
+ * Gracefully terminate a transport child process.
52
+ *
53
+ * Safe to call on an already-exited child (returns immediately with
54
+ * `signal: "natural"`). Safe to call multiple times concurrently
55
+ * — each call observes the same exit event independently.
56
+ *
57
+ * Errors are swallowed deliberately: this function is meant to be
58
+ * called from cleanup paths (abort handlers, IIFE catch blocks,
59
+ * timeout firings) where re-throwing would mask the original
60
+ * failure. Failures during termination are reflected in the returned
61
+ * `TerminateResult.signal` (`"abandoned"` is the only failure shape).
62
+ */
63
+ export declare function terminateGracefully(child: ChildProcess, opts?: TerminateOptions): Promise<TerminateResult>;
@@ -0,0 +1,110 @@
1
+ /**
2
+ * Transport wrappers for use by provider adapters.
3
+ * These wrap the existing transport modules, mapping their result types
4
+ * to the adapter's TurnResult.
5
+ *
6
+ * Each wrapper also classifies the transport's native event stream into
7
+ * normalized ProgressEvents (see adapter.ts) so that downstream consumers
8
+ * (idle timer, stderr printer) have a single format to deal with.
9
+ */
10
+ import { type AcpMode } from "./transports/acp.js";
11
+ import type { ProgressEvent, TurnOptions, TurnResult } from "./adapter.js";
12
+ import type { BundleSession, BundleSessionOpenOptions } from "./bundle.js";
13
+ /**
14
+ * Translate one stream-json event into zero or more ProgressEvents.
15
+ * Stream-json uses Claude's native message format:
16
+ * - {"type":"system","subtype":"init",...}
17
+ * - {"type":"assistant","message":{"content":[{type:"text"|"tool_use"|"thinking"...}]}}
18
+ * - {"type":"user","message":{"content":[{type:"tool_result",...}]}}
19
+ * - {"type":"result",...}
20
+ */
21
+ /**
22
+ * Stateful factory for classifyStreamJsonEvent. The returned closure
23
+ * carries a per-turn id→startMs Map that lets it populate `elapsedMs`
24
+ * on tool_result events by correlating against the matching tool_use
25
+ * (Claude stream-json doesn't supply duration directly).
26
+ *
27
+ * Production callers (`streamJsonTurn`) instantiate ONCE per turn and
28
+ * reuse across all events. Unit tests that exercise a single event in
29
+ * isolation can keep using the bare `classifyStreamJsonEvent` free
30
+ * function below — they just won't get elapsedMs populated, which is
31
+ * fine because there's no matching pair to compute it from.
32
+ */
33
+ export declare function makeStreamJsonClassifier(): (event: Record<string, unknown>, emit: (e: ProgressEvent) => void) => void;
34
+ export declare function classifyStreamJsonEvent(event: Record<string, unknown>, emit: (e: ProgressEvent) => void): void;
35
+ export declare function streamJsonTurn(prompt: string, opts: TurnOptions & {
36
+ binary: string;
37
+ }): Promise<TurnResult>;
38
+ /**
39
+ * Open a persistent stream-json bundle session (batch bundle mode)
40
+ * for the claude/qwen adapters' `openSession`. Bundle resume is strict:
41
+ * the session file must exist and its header must carry the requested id.
42
+ */
43
+ export declare function openStreamJsonBundleSession(opts: BundleSessionOpenOptions & {
44
+ binary: string;
45
+ }): Promise<BundleSession>;
46
+ /**
47
+ * Classify ACP session/update notifications. ACP wraps everything in
48
+ * {"method":"session/update","params":{"update":{"sessionUpdate":"<kind>",...}}}
49
+ *
50
+ * Common sessionUpdate kinds (per the ACP spec used by Zed and friends):
51
+ * - agent_message_chunk → text (already streamed via onDelta)
52
+ * - agent_thought_chunk → thinking
53
+ * - tool_call → tool_use
54
+ * - tool_call_update → tool_result (completed/failed), other "running…" (in_progress)
55
+ * - plan → other
56
+ */
57
+ /**
58
+ * Stateful factory for classifyAcpEvent. Same shape and rationale as
59
+ * makeStreamJsonClassifier — see that doc. Carries per-turn id→startMs
60
+ * for the tool_call → tool_call_update correlation that drives
61
+ * `elapsedMs` on tool_result events.
62
+ */
63
+ export declare function makeAcpClassifier(): (event: Record<string, unknown>, emit: (e: ProgressEvent) => void) => void;
64
+ export declare function classifyAcpEvent(event: Record<string, unknown>, emit: (e: ProgressEvent) => void): void;
65
+ export declare function acpTurn(prompt: string, opts: TurnOptions & {
66
+ binary: string;
67
+ acpTool?: string;
68
+ acpMode?: AcpMode;
69
+ }): Promise<TurnResult>;
70
+ /**
71
+ * Open a persistent ACP bundle session (batch bundle mode) for a
72
+ * provider adapter's `openSession` implementation. `acpTool`/`acpMode`
73
+ * mirror `acpTurn`; the lifecycle contract is in
74
+ * `src/providers/bundle.ts` and the engine in `transports/acp.ts`'s
75
+ * `openAcpSession`.
76
+ *
77
+ * Bundle resume is ALWAYS strict: requireResume is forced and the loaded
78
+ * session id must equal the requested id exactly.
79
+ */
80
+ export declare function openAcpBundleSession(opts: BundleSessionOpenOptions & {
81
+ binary: string;
82
+ acpTool?: string;
83
+ acpMode?: AcpMode;
84
+ }): Promise<BundleSession>;
85
+ /**
86
+ * Classify Codex app-server JSON-RPC notifications.
87
+ *
88
+ * Per codex-rs/app-server/README.md (Turn events / Items), the per-item
89
+ * lifecycle is always: `item/started` → zero or more item-specific deltas
90
+ * → `item/completed`. Items are a tagged union (`item.type`) including
91
+ * `commandExecution`, `fileChange`, `mcpToolCall`, `webSearch`, `reasoning`,
92
+ * `agentMessage`, etc.
93
+ *
94
+ * Item-specific deltas we care about:
95
+ * - item/agentMessage/delta → text (already streamed via onDelta)
96
+ * - item/reasoning/summaryTextDelta → thinking
97
+ * - item/reasoning/textDelta → thinking
98
+ * - item/commandExecution/outputDelta → text (suppressed; lifecycle covers it)
99
+ */
100
+ export declare function classifyCodexEvent(event: Record<string, unknown>, emit: (e: ProgressEvent) => void): void;
101
+ export declare function codexAppServerTurn(prompt: string, opts: TurnOptions & {
102
+ binary: string;
103
+ }): Promise<TurnResult>;
104
+ /**
105
+ * Open a persistent codex app-server bundle session (batch bundle
106
+ * mode) for the codex adapter's `openSession`. Bundle resume is
107
+ * strict: thread/resume must succeed and return the exact requested
108
+ * thread id; the thread/start fallback is disabled in bundle mode.
109
+ */
110
+ export declare function openCodexBundleSession(opts: BundleSessionOpenOptions): Promise<BundleSession>;
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Shared `session/set_config_option` step factory for the profiles whose
3
+ * per-session model/effort surface is an ACP config option (opencode, kimi).
4
+ *
5
+ * The failure semantics are NON-FATAL by contract (task #232): the turn
6
+ * proceeds with whatever config was already active, and the drop is
7
+ * recorded on all three channels — stderr line, `acp_config_option_failed`
8
+ * errorLog record, and a warnings-channel entry that lands on
9
+ * `AcpResult.warnings`.
10
+ *
11
+ * "_failed", not "_rejected": the driver's catch wraps `sendRpc`, so it
12
+ * also fires on transport disconnect, not only on the provider rejecting
13
+ * the value. Naming it "rejected" would conflate provider-validation noise
14
+ * with channel failure — exactly the misreading these counts exist to
15
+ * prevent.
16
+ */
17
+ import type { AcpSessionContext, AcpSessionStep } from "./types.js";
18
+ export declare function setConfigOptionStep(opts: {
19
+ id: string;
20
+ provider: string;
21
+ configId: string;
22
+ /** The requested value; undefined ⇒ the step does not apply. */
23
+ value: (ctx: AcpSessionContext) => string | undefined;
24
+ }): AcpSessionStep;
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Copilot ACP profile.
3
+ *
4
+ * Copilot's `--acp` mode has no plan-mode flag and no per-session model
5
+ * selection surface (verified against copilot-cli 1.0.35). Both gaps are
6
+ * surfaced once per spawn — a stderr line the user sees plus an
7
+ * unconditional errorLog record fleet tooling can count — and the turn
8
+ * proceeds on the yolo/default surface rather than hanging on permission
9
+ * prompts it cannot answer.
10
+ */
11
+ import type { AcpProviderProfile } from "./types.js";
12
+ export declare const copilotProfile: AcpProviderProfile;
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Gemini ACP profile.
3
+ *
4
+ * Permission posture is argv-based: `-y` (yolo) works before or after
5
+ * `--acp`, and the companion `--skip-trust` is passed unconditionally —
6
+ * gemini's workspace-trust gate (~/.gemini/trustedFolders.json) downgrades
7
+ * approval mode to "default" in untrusted folders, silently disarming
8
+ * `-y`; `--skip-trust` is the per-invocation escape hatch (no-op in
9
+ * already-trusted folders). Plan mode swaps the `-y` token for
10
+ * `--approval-mode plan`, keeping companions intact.
11
+ *
12
+ * Caveat: gemini's `--approval-mode plan` only filters `write_file` by
13
+ * workspace path — the agent can still escape via `run_shell_command`
14
+ * (documented in yolo-contract.ts; a tighter sandbox is a follow-up).
15
+ *
16
+ * Gemini replays prior assistant chunks during `session/load` and
17
+ * concatenates earlier image turns into the reply text; keeping only the
18
+ * latest agent message is the output processor's job (parsers/acp.ts),
19
+ * wired in via `isolateResult`.
20
+ */
21
+ import type { AcpProviderProfile } from "./types.js";
22
+ export declare const geminiProfile: AcpProviderProfile;
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Hermes (Hermes Agent) ACP profile.
3
+ *
4
+ * `hermes acp` IS the ACP stdio server (same launcher as the `hermes-acp`
5
+ * console script); it takes no permission flag. The posture is two-part:
6
+ *
7
+ * 1. Shell gate: `HERMES_YOLO_MODE=1` on the process env (yolo mode only;
8
+ * hermes REFUSES plan mode outright).
9
+ * 2. Edit gate: ACP `session/set_mode` — Hermes's mode ladder
10
+ * (`HermesACPAgent._MODES`, `acp_adapter/server.py:233-243`) governs
11
+ * the file-EDIT approval gate, which `HERMES_YOLO_MODE` does NOT
12
+ * approve. An unset mode leaves every edit waiting on a
13
+ * `session/request_permission` round-trip lmctl cannot answer, so the
14
+ * mode is applied after session/new|load, before the model, on EVERY
15
+ * spawn (the mode lives on the in-memory session), FAIL-CLOSED.
16
+ *
17
+ * Model selection is `session/set_model` (also fail-closed), NOT
18
+ * `session/set_config_option`: hermes's config-option handler stores an
19
+ * unrecognized configId verbatim and applies nothing
20
+ * (`acp_adapter/server.py:958-977`). Its model ids are the ENCODED
21
+ * `provider:model` form it advertises in session/new
22
+ * (`acp_adapter/model_catalog.py:146-152`) — a bare model name resolves
23
+ * the wrong provider. An unchanged model is not re-set (avoids rebuilding
24
+ * the Hermes agent).
25
+ */
26
+ import type { AcpProviderProfile } from "./types.js";
27
+ export declare const hermesProfile: AcpProviderProfile;