@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,25 @@
1
+ import type { Dal } from '../db/index.js';
2
+ export type SelfIdentityInference = {
3
+ ok: true;
4
+ identity: string;
5
+ } | {
6
+ ok: false;
7
+ reason: 'unset' | 'not-found';
8
+ };
9
+ export type ResolvedSelfIdentity = {
10
+ ok: true;
11
+ identity: string;
12
+ source: 'session';
13
+ } | {
14
+ ok: false;
15
+ reason: 'unset' | 'not-found';
16
+ };
17
+ export declare function manualInvocationMessage(command: string): string;
18
+ export declare function unmatchedSelfSessionMessage(command: string): string;
19
+ export declare function removedFromOptionMessage(command: string): string;
20
+ export declare function extractSelfIdentityArg(args: string[], command: string): {
21
+ args: string[];
22
+ error?: string;
23
+ };
24
+ export declare function resolveSelfIdentity(env: NodeJS.ProcessEnv, dal: Pick<Dal, 'getRootTeam' | 'listRootTeams'>): ResolvedSelfIdentity;
25
+ export declare function inferSelfIdentityFromEnv(env: NodeJS.ProcessEnv, dal: Pick<Dal, 'getRootTeam' | 'listRootTeams'>): SelfIdentityInference;
@@ -0,0 +1,118 @@
1
+ /**
2
+ * Session-idle availability override for delivery decisions.
3
+ *
4
+ * Operator (2026-07-22): *"also check the last update of session (from
5
+ * sessionid), if it is been idle for 10 minute, then it is available (this
6
+ * does not change terminal pid)."*
7
+ *
8
+ * THRESHOLD: 60 minutes, NOT the 10 initially requested. Do not "fix" this
9
+ * back to 10 without re-running the numbers below.
10
+ *
11
+ * A misfire is not benign: the reclaim force-steals a lease from a LIVE holder
12
+ * and starts a second turn on the same sessionid — the concurrent-resume abort
13
+ * `b85c5a1` exists to prevent. So the threshold has to clear real turn
14
+ * durations. Measured over completed chat turns, SPLIT BY OUTCOME — the split
15
+ * is the whole point, an unsplit number is misleading in both directions:
16
+ *
17
+ * SELECT status, COUNT(*), SUM(CASE WHEN m>60 THEN 1 END), MAX(m)
18
+ * FROM (SELECT status, (julianday(ended_at)-julianday(started_at))*1440 AS m
19
+ * FROM tracked_invocation WHERE ended_at IS NOT NULL AND kind='chat')
20
+ * GROUP BY status;
21
+ *
22
+ * status='done' n=2056 >60min: 0 longest: 51.8 min
23
+ * status='error' n=1054 >60min: 191 longest: 2709 min (~45h)
24
+ * status='busy' n=727 >60min: 0
25
+ *
26
+ * NO SUCCESSFULLY-COMPLETED CHAT TURN HAS EVER EXCEEDED 60 MINUTES. The longest
27
+ * was 51.8 min, so 60min sits ~16% above the worst observed healthy turn. Every
28
+ * one of the 191 turns past 60min ended in `error` — hung, aborted, abandoned:
29
+ * exactly the population this feature exists to reclaim.
30
+ *
31
+ * Beware the unsplit figure: "5.0% of all turns exceed 60min" is arithmetically
32
+ * true and materially misleading, because it counts already-failed turns as if
33
+ * they were healthy ones at risk of a wrongful steal. Exposure among healthy
34
+ * turns is zero in the observed record. (Turn DURATION is also only an upper
35
+ * bound — the trigger needs a >60min GAP BETWEEN WRITES, and a turn making tool
36
+ * calls keeps `modifiedAt` fresh.) 60min also still beats the 8h inflight TTL
37
+ * by ~8x, which is the actual complaint being solved.
38
+ *
39
+ * Re-run the query rather than trusting these figures; if you retune, split by
40
+ * status or the number will mislead you.
41
+ * See `backlog/DESIGN-session-idle-availability-scope.md`.
42
+ *
43
+ * THE PROBLEM. An `in-flight` lease taken by `lmctl prompt`/relay delivery keeps
44
+ * a receiver unreachable until the holder releases it or the 8h TTL expires.
45
+ * A hung turn therefore blocks that receiver for hours. Holder-pid liveness
46
+ * alone cannot detect this: the process is alive, it is just not progressing.
47
+ *
48
+ * THE SIGNAL. `SessionDetail.modifiedAt` — the provider's own session file
49
+ * mtime, i.e. real conversation activity. Deliberately NOT the chat/terminal
50
+ * heartbeat (`tracked_invocation.last_heartbeat_at`), which a timer in the
51
+ * holder process refreshes every 30s whether the turn is working OR wedged.
52
+ * The heartbeat distinguishes process-alive (already covered by the pid
53
+ * check), not stuck-vs-working, so it cannot detect a hung turn at all.
54
+ *
55
+ * FAILS CLOSED, ALWAYS. Every failure path returns `false` = "not idle, keep
56
+ * treating as busy". A false "available" lets two deliveries resume the same
57
+ * held session and collide; a false "busy" only means the message waits
58
+ * longer. Those costs are not symmetric, so the ambiguous cases (session not
59
+ * found, ambiguous provider, read error, unresolvable member) all resolve to
60
+ * busy. Same lesson as the reboot/pid-reuse work: never fail open on a
61
+ * liveness question.
62
+ *
63
+ * SCOPE. This is an ADDITIVE override consulted only at the synchronous
64
+ * chat delivery decision point (`chat-exec`) and only for the `in-flight`
65
+ * busy reason. `terminal-held` is NEVER overridden at any idle duration — see
66
+ * `isInflightButSessionIdle` below for why. It does not touch
67
+ * `getReceiverBusyState`, the terminal lock, or any pid.
68
+ */
69
+ import type { Dal } from '../db/index.js';
70
+ /**
71
+ * Threshold override, matching the existing staleness-window convention in
72
+ * this codebase (`LMCTL_INFLIGHT_TTL_MS`, `LMCTL_TERMINAL_LOCK_STALE_MS`):
73
+ * ignore unset/blank/non-finite/non-positive values and fall back to the
74
+ * 60-minute default.
75
+ */
76
+ export declare function sessionIdleTimeoutMsFromEnv(env?: NodeJS.ProcessEnv): number;
77
+ /**
78
+ * Has this receiver's underlying provider session been quiet for at least
79
+ * `thresholdMs`?
80
+ *
81
+ * `true` ONLY when the session was found and its last activity is at or past
82
+ * the threshold. Anything else — including every error — is `false`.
83
+ */
84
+ export declare function isReceiverIdleTimedOut(dal: Pick<Dal, 'getRootTeam' | 'listRootTeams'>, teamfile: string, alias: string, thresholdMs?: number): Promise<boolean>;
85
+ /**
86
+ * How long has this receiver's session been quiet, in ms? `null` when that
87
+ * cannot be established for ANY reason — the fail-closed signal, and also what
88
+ * the reclaim log reports so a steal can be judged after the fact.
89
+ */
90
+ export declare function sessionIdleMs(dal: Pick<Dal, 'getRootTeam' | 'listRootTeams'>, teamfile: string, alias: string): Promise<number | null>;
91
+ /**
92
+ * The delivery-side question, in one call: given the busy state we already
93
+ * computed synchronously, may we deliver anyway because the session is idle?
94
+ *
95
+ * SCOPE — `in-flight` ONLY. Operator (2026-07-22): *"terminal-held is alway
96
+ * regarded as busy, no matter how long idle is. session idle is only for
97
+ * non-terminal session, that is through lmctl prompt."*
98
+ *
99
+ * `terminal-held` is NEVER overridden, at any idle duration. That case is a
100
+ * human's interactive terminal — including a live codex/opencode TUI with a
101
+ * persistent process attached — and declaring it available on a timestamp
102
+ * would reintroduce exactly the concurrent-resume collision `b85c5a1`
103
+ * ("Protect held sessions from stale lease steals") was written to prevent.
104
+ * A quiet terminal is someone thinking, not an absent holder.
105
+ *
106
+ * `in-flight`, by contrast, is a lease taken by `lmctl prompt`/relay delivery:
107
+ * bounded, ephemeral, non-interactive work. A live pid there with no session
108
+ * file activity for the full window is most plausibly a hung turn, so it
109
+ * is a reasonable reclaim target — the same spirit as the existing dead-pid
110
+ * reclaim, adding long-idle-with-live-pid as a second trigger on the
111
+ * chat-invoked path only.
112
+ *
113
+ * See `backlog/DESIGN-session-idle-availability-scope.md`.
114
+ */
115
+ export declare function isInflightButSessionIdle(dal: Pick<Dal, 'getRootTeam' | 'listRootTeams'>, busy: {
116
+ busy: boolean;
117
+ reason?: string;
118
+ }, teamfile: string, alias: string, thresholdMs?: number): Promise<boolean>;
@@ -0,0 +1,22 @@
1
+ import { type CliIo } from './common.js';
2
+ /**
3
+ * Path to the rolling intent log written by `status` (and, in future, by
4
+ * other verbs that want a breadcrumb trail). Plain JSONL — one event per
5
+ * line — so a troubleshooting bundle can ship it verbatim. Override via
6
+ * `LMCTL_INTENT_LOG` for tests / non-default homes.
7
+ */
8
+ export declare function resolveIntentLogPath(): string;
9
+ /**
10
+ * Append a single intent record to the rolling log. Failures are
11
+ * swallowed — the log is best-effort breadcrumbs, never load-bearing.
12
+ */
13
+ export declare function appendIntent(record: {
14
+ verb: string;
15
+ args: ReadonlyArray<string>;
16
+ cwd?: string;
17
+ ts?: string;
18
+ }): void;
19
+ export declare function runStatus(args: string[], io: CliIo): Promise<number>;
20
+ export declare function formatDuration(ms: number): string;
21
+ export declare function formatAge(iso: string): string;
22
+ export declare function shortIdentity(identity: string): string;
@@ -0,0 +1,18 @@
1
+ import type { ConversationMessage } from '../providers/types.js';
2
+ import { type CliIo } from './common.js';
3
+ export declare function runTail(args: string[], io?: CliIo): Promise<number>;
4
+ /** Plain renderer (no chalk/ANSI per design R3). Indents continuation
5
+ * lines so multi-line messages remain visually grouped. */
6
+ export declare function renderMessage(msg: ConversationMessage): string;
7
+ export declare function applyMaxBytes(output: string, maxBytes: number): string;
8
+ /**
9
+ * Same grammar as status.ts's parseStatusSince parser: a relative
10
+ * duration (<n>s|m|h|d) or an absolute ISO8601
11
+ * timestamp. Unlike status's version, `undefined` here means "no filter"
12
+ * (tail's existing default is unchanged), not a default window.
13
+ */
14
+ export declare function parseTailSince(value: string | undefined): {
15
+ date: Date | undefined;
16
+ } | {
17
+ error: string;
18
+ };
@@ -0,0 +1,139 @@
1
+ import type { Dal } from '../db/index.js';
2
+ /**
3
+ * Usage-class error for target-resolution failures (ported verbatim from
4
+ * lmctl0 resolve-target.ts:32-37). The filesystem-backed resolvers throw
5
+ * this; callers exit 2 on catch. The pure `classifyTarget` does NOT
6
+ * throw — it returns an `{ mode: 'error' }` discriminated variant.
7
+ */
8
+ export declare class ChatTargetError extends Error {
9
+ constructor(message: string);
10
+ }
11
+ /**
12
+ * Valid alias grammar (letter-initial, then alphanumerics + `_`/`-`).
13
+ * Matches lmctl-next's team-config alias grammar and lmctl0
14
+ * resolve-target.ts:84 verbatim. Permissive enough for `researcher1`,
15
+ * `backend-fixer`; strict enough to reject `./foo.lmctl:Coder;ls`.
16
+ */
17
+ export declare const ALIAS_RE: RegExp;
18
+ /** True iff `arg` is a UUID- or `ses_`-shaped native session id.
19
+ * (lmctl0 resolve-target.ts:86-88) */
20
+ export declare function looksLikeSessionId(arg: string): boolean;
21
+ /** Same alias validation surface as the colon parser. (lmctl0 :120-122) */
22
+ export declare function isValidAlias(arg: string): boolean;
23
+ /**
24
+ * Detect the `<teamfile>:<alias>` shape WITHOUT touching the disk.
25
+ * Returns the split parts if `arg` matches `<*.lmctl>:<alias>` with a
26
+ * valid alias, else null. Splits on the LAST `:` so a Windows-style
27
+ * absolute path like `C:\path\to\team.lmctl:Coder` still parses.
28
+ * (lmctl0 resolve-target.ts:103-113, ported verbatim.)
29
+ */
30
+ export declare function parseTeamfileColonAlias(arg: string): {
31
+ teamfile: string;
32
+ alias: string;
33
+ } | null;
34
+ /** Concrete result of resolving `<teamfile> <alias>` (or the colon
35
+ * shorthand) against a teamfile on disk. Mirrors lmctl0's `ResolvedMember`
36
+ * shape (`session` → `sessionid`). */
37
+ export interface ResolvedMember {
38
+ /** Absolute path to the teamfile that contained the alias. */
39
+ teamfile: string;
40
+ /** Canonical alias casing as recorded in the teamfile. */
41
+ alias: string;
42
+ provider: string;
43
+ sessionid: string;
44
+ sessiondir: string;
45
+ /** True when the concrete provider turn is a Lead session. */
46
+ isLead: boolean;
47
+ /** Model override from the concrete member line, if set. */
48
+ model?: string;
49
+ /** Effort / variant override from the concrete member line, if set. */
50
+ effort?: string;
51
+ }
52
+ /**
53
+ * Resolve `<teamfile>:<alias>` / `<teamfile> <alias>` against a real
54
+ * teamfile and return the member's session coordinates. Throws
55
+ * `ChatTargetError` on every usage failure (caller exits 2). Adapted
56
+ * from lmctl0 resolve-target.ts:163-225: team-config import swapped, the
57
+ * lmctl-next `TeamConfig.parseErrors` surface is honored (the copied
58
+ * parser reports parse failures as a field, not by throwing — mirror
59
+ * `src/cli/chat.ts:111`), and `member.session`/`member.sessiondir` are
60
+ * optional in lmctl-next's `MemberConfig`.
61
+ */
62
+ export declare function resolveTeamMember(teamfileArg: string, aliasArg: string, dal?: Pick<Dal, 'getRootTeam' | 'listRootTeams'>): ResolvedMember;
63
+ /**
64
+ * Smart-target dispatch for sessionid-scoped commands. Accepts the same
65
+ * three input shapes the verbs advertise. Throws `ChatTargetError` on
66
+ * usage failure. Ported from lmctl0 resolve-target.ts:249-276 (import
67
+ * adapted via `resolveTeamMember` above).
68
+ */
69
+ export declare function resolveSessionTarget(command: string, target: string, alias: string | undefined): {
70
+ sessionid: string;
71
+ providerOverride?: string;
72
+ };
73
+ /** Per-verb acceptance rules (preserve lmctl0's per-verb behavior). */
74
+ export interface VerbCaps {
75
+ /** Verb name, used verbatim in error messages. */
76
+ name: string;
77
+ /** A bare `<path>.lmctl` (no alias) is meaningful for this verb
78
+ * (e.g. `info` team rollup). When false a bare teamfile is a usage
79
+ * error (terminal/chat/tail require an alias). */
80
+ allowsBareTeamfile: boolean;
81
+ /** A bare `<sessionid>` (AI-team raw) is accepted (tail/info/chat).
82
+ * `terminal` is teamfile-only (lmctl0 terminal-cmd.ts:64-69). */
83
+ supportsRaw: boolean;
84
+ }
85
+ /** The classifier's input — the verb's already-split positionals + the
86
+ * managed/legacy flags. No disk state is consulted. */
87
+ export interface TargetInput {
88
+ /** First positional (teamfile / teamfile:alias / sessionid), if any. */
89
+ target?: string;
90
+ /** Second positional (the two-positional `<teamfile> <alias>` form). */
91
+ alias?: string;
92
+ flags: {
93
+ /** Managed selectors (NET-NEW vs lmctl0). */
94
+ run?: string;
95
+ project?: string;
96
+ workflow?: string;
97
+ /** Legacy raw form back-compat (lmctl0 `--session`/`--provider`). */
98
+ session?: string;
99
+ provider?: string;
100
+ };
101
+ }
102
+ export type ClassifiedTarget = {
103
+ mode: 'managed';
104
+ run?: string;
105
+ project?: string;
106
+ workflow?: string;
107
+ } | {
108
+ mode: 'ai-team';
109
+ teamfile: string;
110
+ alias?: string;
111
+ } | {
112
+ mode: 'ai-team-raw';
113
+ sessionid: string;
114
+ provider?: string;
115
+ } | {
116
+ mode: 'error';
117
+ message: string;
118
+ };
119
+ /**
120
+ * Classify `(flags, target?, alias?)` into exactly one of the 3 modes,
121
+ * per design §0.1 precedence (verbatim):
122
+ *
123
+ * 1. managed flag (`--run`/`--project`/`--workflow`) present → MANAGED,
124
+ * mutually exclusive with a positional target (both ⇒ error).
125
+ * 2. positional target present:
126
+ * 2a. parseTeamfileColonAlias matches → AI-TEAM
127
+ * 2b. target ends ".lmctl":
128
+ * - second positional alias present → AI-TEAM
129
+ * - no alias, verb allows bare-teamfile → AI-TEAM (rollup)
130
+ * - no alias, verb requires alias → error
131
+ * 2c. looksLikeSessionId & verb.supportsRaw → AI-TEAM RAW
132
+ * (else error: bare sessionid unsupported)
133
+ * 2d. none of the above → error (can't infer)
134
+ * 3. no positional, legacy --session set → AI-TEAM RAW
135
+ * 4. nothing → error (usage)
136
+ *
137
+ * Pure: no I/O, deterministic, never throws.
138
+ */
139
+ export declare function classifyTarget(verb: VerbCaps, input: TargetInput): ClassifiedTarget;
@@ -0,0 +1,91 @@
1
+ import type { Dal } from '../db/index.js';
2
+ import type { SessionLimitState } from '../providers/session-limit.js';
3
+ import type { SessionDetail } from '../providers/types.js';
4
+ import { type CliIo } from './common.js';
5
+ export declare function formatTokens(n: number): string;
6
+ export interface GitWorkingStatus {
7
+ uncommittedTracked: number | null;
8
+ untracked: number | null;
9
+ }
10
+ export type GitProgress = {
11
+ kind: 'unavailable';
12
+ teamDir: string;
13
+ reason: 'no-repo' | 'no-commits';
14
+ } | {
15
+ kind: 'repo';
16
+ teamDir: string;
17
+ since: Date;
18
+ commits: Array<{
19
+ ref: string;
20
+ timestamp: Date;
21
+ }>;
22
+ working: GitWorkingStatus;
23
+ };
24
+ export interface MemberLiveness {
25
+ state: 'idle' | 'busy';
26
+ reason: 'terminal-held' | 'in-flight' | null;
27
+ inFlight: {
28
+ senderTeamfile: string;
29
+ senderAlias: string;
30
+ startedAt: string;
31
+ activeCount: number;
32
+ holderPid: number | null;
33
+ } | null;
34
+ terminalLock: {
35
+ startedAt: string;
36
+ lastHeartbeat: string;
37
+ holderPid: number;
38
+ holderHost: string;
39
+ } | null;
40
+ terminalInvocation: {
41
+ id: number;
42
+ startedAt: string;
43
+ lastHeartbeat: string | null;
44
+ holderPid: number | null;
45
+ holderHost: string | null;
46
+ } | null;
47
+ reclaimable: {
48
+ source: 'terminal-lock' | 'terminal-invocation' | 'in-flight';
49
+ holderPid: number | null;
50
+ holderHost: string | null;
51
+ reason: 'dead-pid' | 'stale';
52
+ } | null;
53
+ }
54
+ export interface TeamHealthTarget {
55
+ teamfile: string;
56
+ alias: string;
57
+ reportPath: string;
58
+ provider: string;
59
+ /** Null for an unseeded member (planned but never seeded) -- the roster
60
+ * keeps the entry so `status <teamfile>` shows it explicitly as
61
+ * not-seeded; only session probing is gated on a sessionid existing. */
62
+ sessionid: string | null;
63
+ /** The teamfile's DECLARED model= for this member -- what was asked for,
64
+ * read straight from config with no live probe. Distinct from (and can
65
+ * disagree with) the OBSERVED model a live session probe reports, which
66
+ * is what the provider actually used -- real incident, 2026-09-29
67
+ * (math.lmctl differential test): agy/codex sessions running on models
68
+ * their teamfiles do not declare. Null when the member has no model=. */
69
+ model: string | null;
70
+ }
71
+ /** Task #232: the most recent tracked_invocation for this target that
72
+ * actually recorded a config-drop -- `ORDER BY started_at DESC LIMIT 1`
73
+ * scoped to rows where config_warnings is set, so a later CLEAN turn
74
+ * doesn't shadow an earlier drop the operator still needs to see, and a
75
+ * config-drop from days ago doesn't look like it just happened. */
76
+ export declare function latestConfigWarnings(io: CliIo, target: {
77
+ teamfile: string;
78
+ alias: string;
79
+ }): Promise<string[] | undefined>;
80
+ export declare function collectHealthTargets(teamfileArg: string, dal: Dal, seen: Set<string>, prefix: string[]): TeamHealthTarget[];
81
+ export declare function loadTeamLiveness(io: CliIo, targets: TeamHealthTarget[]): Promise<Map<string, MemberLiveness>>;
82
+ export declare function memberKey(teamfile: string, alias: string): string;
83
+ export declare function idleLiveness(reclaimable?: MemberLiveness['reclaimable']): MemberLiveness;
84
+ export declare function livenessLabel(value: MemberLiveness): string;
85
+ export declare function formatSessionLimitReset(value: SessionLimitState): string;
86
+ export declare function formatKnownNumber(value: number | undefined | null): string;
87
+ export declare function formatProviderRollupNumber(value: number | null | undefined | 'n/a'): string;
88
+ export declare function formatOptionalProviderNumber(value: number | undefined, loaded: boolean, format?: (value: number) => string): string;
89
+ export declare function readGitProgress(teamfile: string): GitProgress;
90
+ export declare function countMessagesSince(detail: SessionDetail, since: Date): number;
91
+ export declare function cumulativeSessionTokens(detail: SessionDetail | null): number | null;
@@ -0,0 +1,71 @@
1
+ import { type AgyModelsRunResult, type CodexModelsRunResult } from '../providers/config/model-selection-lint.js';
2
+ import type { CatalogSnapshot } from '../providers/pricing.js';
3
+ import { type OpenCodeAcpConfigProbeOptions, type OpenCodeAcpConfigProbeResult } from '../providers/transports/acp.js';
4
+ import type { ProviderName } from '../providers/types.js';
5
+ import type { Dal, RootTeamInput, RootTeamRow } from '../db/index.js';
6
+ export declare const GEMINI_PROVIDER_NOTICE = "notice: member \"%s\" uses provider=gemini \u2014 the Gemini CLI requires an API/enterprise Google account; personal Google subscriptions should use provider=agy (Antigravity).";
7
+ export interface TeamfileCliIo {
8
+ stdout: {
9
+ write(chunk: string): unknown;
10
+ };
11
+ stderr: {
12
+ write(chunk: string): unknown;
13
+ };
14
+ dal?: Dal;
15
+ env?: NodeJS.ProcessEnv;
16
+ }
17
+ type SeedFn = (opts: {
18
+ provider: ProviderName;
19
+ cwd: string;
20
+ model?: string;
21
+ effort?: string;
22
+ prompt?: string;
23
+ }) => Promise<{
24
+ sessionId?: string;
25
+ error?: string;
26
+ warnings?: string[];
27
+ }>;
28
+ type OpenCodeConfigProbeFn = (opts: OpenCodeAcpConfigProbeOptions) => Promise<OpenCodeAcpConfigProbeResult>;
29
+ interface LintSessiondirFsOps {
30
+ existsSync(path: string): boolean;
31
+ statSync(path: string): {
32
+ isDirectory(): boolean;
33
+ };
34
+ accessSync(path: string, mode: number): unknown;
35
+ }
36
+ /** Test-only seam: inject a fixed lmprice catalog snapshot so lint/seed
37
+ * catalog-resolution tests never touch the network/cache/bundled data.
38
+ * `null` restores the real loader (loadCatalogSnapshot). */
39
+ export declare function _setCatalogSnapshotForTests(snapshot: CatalogSnapshot | null): void;
40
+ /** Test-only seam: inject a fake `agy models` result so lint/seed tests
41
+ * don't depend on the real `agy` binary being installed/authenticated (or
42
+ * on its live catalog staying stable). `null` restores the real command. */
43
+ export declare function _setAgyModelsRunnerForTests(fn: (() => AgyModelsRunResult) | null): void;
44
+ /** Test-only seam: inject a fake `codex debug models` result, same
45
+ * rationale as `_setAgyModelsRunnerForTests` above. */
46
+ export declare function _setCodexModelsRunnerForTests(fn: (() => CodexModelsRunResult) | null): void;
47
+ export declare function _setTeamfileSeedForTests(fn: SeedFn | null): void;
48
+ export declare function _setLintSessiondirFsOpsForTests(ops: Partial<LintSessiondirFsOps> | null): void;
49
+ export declare function _setLintOpenCodeConfigProbeForTests(fn: OpenCodeConfigProbeFn | null): void;
50
+ export declare function runLint(args: string[], io?: TeamfileCliIo): Promise<number>;
51
+ export declare function runSeed(args: string[], io?: TeamfileCliIo): Promise<number>;
52
+ export interface RootTeamRegistrationInput {
53
+ teamfilePath: string;
54
+ name: string | null;
55
+ rawText: string;
56
+ now: string;
57
+ }
58
+ export declare function sha256Text(text: string): string;
59
+ export declare function buildRootTeamRegistrationRow(existing: RootTeamRow | null, input: RootTeamRegistrationInput): RootTeamInput;
60
+ export declare function registerRootTeam(dal: Dal, input: RootTeamRegistrationInput): RootTeamInput;
61
+ /**
62
+ * Shared member-spec validation for surfaces that build _MEMBER_ lines from
63
+ * user input (`lmctl plan --member`, the plan wizard): alias shape + known
64
+ * provider name. Returns an error message, or undefined when the spec is
65
+ * valid.
66
+ */
67
+ export declare function validateMemberSpec(spec: {
68
+ alias: string;
69
+ provider: string;
70
+ }): string | undefined;
71
+ export {};
@@ -0,0 +1,18 @@
1
+ import type { Dal } from '../db/index.js';
2
+ import { type TeamConfig } from '../providers/config/team-config.js';
3
+ type TeamfileLookupDal = Pick<Dal, 'getRootTeam' | 'listRootTeams'>;
4
+ type TeamfileStoreDal = Pick<Dal, 'getRootTeam' | 'listRootTeams' | 'upsertRootTeam'>;
5
+ type TeamfileRegistrationWarning = (warning: string) => void;
6
+ export declare function canonicalTeamfileKey(teamfile: string, dal?: TeamfileLookupDal): string;
7
+ export declare function parseStoredTeamConfig(rawText: string, teamfileKey: string): TeamConfig;
8
+ export declare function readTeamConfigFromStore(teamfile: string, dal?: TeamfileStoreDal | Pick<Dal, 'getRootTeam'>, warn?: TeamfileRegistrationWarning): TeamConfig;
9
+ /**
10
+ * Registration-time ownership guard for provider sessions.
11
+ *
12
+ * A session id identifies one concrete provider conversation, so two live
13
+ * registered teamfiles must never claim it as different members. Registry
14
+ * rows whose files have been renamed/deleted are inert and deliberately
15
+ * excluded: re-seeding the same team at its new path must remain possible.
16
+ */
17
+ export declare function assertNoForeignSessionidOwnership(dal: TeamfileLookupDal, key: string, config: TeamConfig): void;
18
+ export {};
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Teamfile construction primitives for `lmctl plan`:
3
+ * member-spec parsing (`Alias:provider[:model]`), the 1/2/3 member patterns,
4
+ * the canonical teamfile renderer, and the session-key stripper used by
5
+ * `--from` template mode. Kept free of I/O so both the flags path and the
6
+ * wizard render byte-identical documents.
7
+ */
8
+ export interface MemberSpec {
9
+ alias: string;
10
+ provider: string;
11
+ model?: string;
12
+ }
13
+ /** Named and numeric patterns → member alias lists. Pattern 1 is Lead-only. */
14
+ export declare const MEMBER_PATTERNS: Record<string, string[]>;
15
+ export declare const PATTERN_USAGE = "1|2|3|lead|lead-reviewer|lead-coder-reviewer";
16
+ export declare function resolvePattern(value: string): string[] | undefined;
17
+ /**
18
+ * Parse one `--member Alias:provider[:model]` value. Validation matches the
19
+ * shared member-spec rules (validateMemberSpec in teamfile-compat.ts).
20
+ */
21
+ export declare function parseMemberSpec(raw: string): {
22
+ spec?: MemberSpec;
23
+ error?: string;
24
+ };
25
+ export declare function renderMemberLine(member: MemberSpec): string;
26
+ /**
27
+ * Render the canonical starter teamfile. The header carries the no-timeout
28
+ * rule INLINE (not only as a skill-doc link) so a new Lead's first read
29
+ * already has it; the wording is association-first on purpose (a diagnosis
30
+ * to reach for after a dispatch dies, not a prohibition stated in advance).
31
+ * Session ids are never generated here — that is `lmctl seed`'s job.
32
+ */
33
+ export declare function renderTeamfile(opts: {
34
+ teamName: string;
35
+ members: MemberSpec[];
36
+ }): string;
37
+ /** Strip sessionid= and sessiondir= from _MEMBER_ lines (template mode). */
38
+ export declare function stripSessionKeys(text: string): string;
39
+ /** Read the member roster back out of teamfile text (reporting / --json). */
40
+ export declare function parseMembersFromTeamfile(text: string): MemberSpec[];
@@ -0,0 +1,25 @@
1
+ export interface TemplateEnvContext {
2
+ workdir: string;
3
+ teamfile: string;
4
+ date: string;
5
+ }
6
+ declare const TEMPLATE_ENV_REGISTRY: {
7
+ workdir: (ctx: TemplateEnvContext) => string;
8
+ teamfile: (ctx: TemplateEnvContext) => string;
9
+ teamname: (ctx: TemplateEnvContext) => string;
10
+ home: () => string;
11
+ date: (ctx: TemplateEnvContext) => string;
12
+ };
13
+ export type TemplateEnvName = keyof typeof TEMPLATE_ENV_REGISTRY;
14
+ export interface TemplateEnvSubstitution {
15
+ text: string;
16
+ unknownNames: string[];
17
+ }
18
+ export declare function substituteTemplateEnv(text: string, ctx: TemplateEnvContext): TemplateEnvSubstitution;
19
+ export declare function templateEnvContext(input: {
20
+ workdir: string;
21
+ teamfile: string;
22
+ date: Date;
23
+ }): TemplateEnvContext;
24
+ export declare function formatTemplateDate(date: Date): string;
25
+ export {};
@@ -0,0 +1,25 @@
1
+ import type { TerminalLockRecord } from '../db/terminal-lock.js';
2
+ export type TerminalLockLiveness = {
3
+ live: true;
4
+ reason: 'local-pid-alive' | 'remote-heartbeat-fresh';
5
+ } | {
6
+ live: false;
7
+ reason: 'dead-pid' | 'stale-heartbeat';
8
+ };
9
+ export declare function createTerminalLockRecord(input: {
10
+ teamfile: string;
11
+ alias: string;
12
+ now?: Date;
13
+ pid?: number;
14
+ host?: string;
15
+ ips?: string[];
16
+ }): TerminalLockRecord;
17
+ export declare function terminalLockHeartbeatMsFromEnv(env?: NodeJS.ProcessEnv): number;
18
+ export declare function terminalLockStaleMsFromEnv(env?: NodeJS.ProcessEnv): number;
19
+ export declare function evaluateTerminalLockLiveness(lock: TerminalLockRecord, opts?: {
20
+ now?: Date;
21
+ localHost?: string;
22
+ staleMs?: number;
23
+ isProcessAlive?: (pid: number) => boolean;
24
+ }): TerminalLockLiveness;
25
+ export declare function terminalHeldMessage(lock: TerminalLockRecord): string;
@@ -0,0 +1,23 @@
1
+ import { type CliIo } from './common.js';
2
+ /** Operator-decided non-blocking guide/trip-wire (design §4.3 / §10.A).
3
+ * NEVER gates or auto-acts — informational only. */
4
+ export declare const SIZE_WARN_EST_TOKENS = 200000;
5
+ export declare function runTerminal(args: string[], io: CliIo): Promise<number>;
6
+ /**
7
+ * Local, provider-agnostic token ESTIMATE (design §4.2). Σ message
8
+ * content chars (preferred) ÷ 4, else file bytes ÷ 4. NEVER a provider
9
+ * round-trip / `tokens` call. The /4 ("~4 chars/token") is the
10
+ * widely-cited BPE midpoint; this is a trip-wire, not a billing meter
11
+ * (bias documented in --help).
12
+ */
13
+ export declare function estimateTokens(detail: {
14
+ messages?: Array<{
15
+ content?: string;
16
+ }>;
17
+ filePath?: string;
18
+ } | null): {
19
+ chars: number;
20
+ est: number;
21
+ messages: number | null;
22
+ bytes: number | null;
23
+ };
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Shared `--since`/`--until` grammar: a relative duration (`<n>s|m|h|d`,
3
+ * e.g. `24h`) or an absolute ISO8601 timestamp. One parser so `tail` and
4
+ * `session` (and anything else that grows a time filter) can't drift.
5
+ */
6
+ export declare function parseSinceUntil(value: string | undefined, flagName: string): {
7
+ date: Date | undefined;
8
+ } | {
9
+ error: string;
10
+ };
@@ -0,0 +1,11 @@
1
+ import type { CliIo } from './common.js';
2
+ interface UpdateNotifierDeps {
3
+ now?: () => number;
4
+ currentVersion?: () => string;
5
+ cachePath?: string;
6
+ fetchLatest?: () => Promise<string>;
7
+ }
8
+ export declare function compareSemver(a: string, b: string): number;
9
+ export declare function shouldSkipUpdateNotifier(args: string[], io: CliIo): boolean;
10
+ export declare function maybeNotifyUpdate(io: CliIo, args?: string[], deps?: UpdateNotifierDeps): Promise<void>;
11
+ export {};
@@ -0,0 +1,2 @@
1
+ /** Resolve the package.json version of lmctl-next, with a safe fallback. */
2
+ export declare function readLmctlNextVersion(): string;
@@ -0,0 +1 @@
1
+ export declare const DURABLE_MEMORY_INSTRUCTION_BLOCK = "---\n\n# Durable memory \u2014 how to use it\n\nThis project has a durable-memory directory at `<project>/durable-memory/` (or DB-backed if durable_memory_mode='db' \u2014 same logical surface).\n\nON EVERY NEW TASK:\n1. Read `durable-memory/index.md` first. It lists project knowledge by topic.\n2. Pull only the chapters relevant to your current task. Don't load everything.\n3. If you learn something durable (a project convention, a decision, a fact worth preserving), write it back: create/update `<project>/durable-memory/<descriptive-name>.md` with YAML frontmatter (name/description/type) + body. The engine syncs the write automatically.\n\nDon't dump all of durable-memory into your context. The pattern is index-first, chapter-on-demand.";