@lmctl-ai/lmctl 0.1.342 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +116 -0
- package/README.md +11 -51
- package/data/price-aliases.json +1 -0
- package/data/price-data.json +1 -1
- package/dist/cli/batch.d.ts +2 -0
- package/dist/cli/busy-evidence.d.ts +27 -0
- package/dist/cli/chat-cycle.d.ts +59 -0
- package/dist/cli/chat-exec.d.ts +83 -0
- package/dist/cli/chat-policy.d.ts +22 -0
- package/dist/cli/chat.d.ts +23 -0
- package/dist/cli/code-resolver.d.ts +8 -0
- package/dist/cli/common.d.ts +54 -0
- package/dist/cli/context.d.ts +12 -0
- package/dist/cli/cost.d.ts +2 -0
- package/dist/cli/export.d.ts +2 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.js +1062 -1400
- package/dist/cli/infra.d.ts +78 -0
- package/dist/cli/ls.d.ts +2 -0
- package/dist/cli/mailbox-cli.d.ts +6 -0
- package/dist/cli/managed.d.ts +62 -0
- package/dist/cli/mcp-install.d.ts +23 -0
- package/dist/cli/mcp.d.ts +2 -0
- package/dist/cli/paths.d.ts +50 -0
- package/dist/cli/plan-wizard.d.ts +42 -0
- package/dist/cli/plan.d.ts +20 -0
- package/dist/cli/queue-delivery.d.ts +107 -0
- package/dist/cli/ratelimit.d.ts +3 -0
- package/dist/cli/remote-fetch.d.ts +7 -0
- package/dist/cli/run-queries.d.ts +82 -0
- package/dist/cli/running.d.ts +5 -0
- package/dist/cli/schema.sql +20 -1
- package/dist/cli/script.d.ts +6 -0
- package/dist/cli/script.js +2097 -0
- package/dist/cli/self-identity.d.ts +25 -0
- package/dist/cli/session-idle.d.ts +118 -0
- package/dist/cli/status.d.ts +22 -0
- package/dist/cli/tail.d.ts +18 -0
- package/dist/cli/target-resolver.d.ts +139 -0
- package/dist/cli/team-status.d.ts +91 -0
- package/dist/cli/teamfile-compat.d.ts +71 -0
- package/dist/cli/teamfile-store.d.ts +18 -0
- package/dist/cli/teamfile-template.d.ts +40 -0
- package/dist/cli/template-env.d.ts +25 -0
- package/dist/cli/terminal-lock.d.ts +25 -0
- package/dist/cli/terminal.d.ts +23 -0
- package/dist/cli/time-filter.d.ts +10 -0
- package/dist/cli/update-notifier.d.ts +11 -0
- package/dist/cli/version-util.d.ts +2 -0
- package/dist/config/seed_templates.d.ts +1 -0
- package/dist/db/backfill-event-log.d.ts +26 -0
- package/dist/db/delegation.d.ts +37 -0
- package/dist/db/escalation_vocab.d.ts +45 -0
- package/dist/db/event-log.d.ts +40 -0
- package/dist/db/index.d.ts +1213 -0
- package/dist/db/inflight-acquire.d.ts +70 -0
- package/dist/db/invocation-event-projections.d.ts +65 -0
- package/dist/db/ledger-identity.test.d.ts +1 -0
- package/dist/db/live-event-projections.d.ts +92 -0
- package/dist/db/migrate.d.ts +51 -0
- package/dist/db/open-readonly.d.ts +2 -0
- package/dist/db/process-liveness.d.ts +37 -0
- package/dist/db/sqlite.d.ts +47 -0
- package/dist/db/sqlite.test.d.ts +1 -0
- package/dist/db/terminal-lock.d.ts +14 -0
- package/dist/db/tracked-invocation-reconcile.d.ts +52 -0
- package/dist/db/tracked-invocation.d.ts +116 -0
- package/dist/mcp/dispatch.d.ts +45 -0
- package/dist/mcp/mediation.d.ts +128 -0
- package/dist/mcp/path-warning.d.ts +7 -0
- package/dist/mcp/server.d.ts +63 -0
- package/dist/mcp/tools.d.ts +24 -0
- package/dist/providers/adapter.d.ts +265 -0
- package/dist/providers/adapters/agy-adapter.d.ts +17 -0
- package/dist/providers/adapters/claude-adapter.d.ts +20 -0
- package/dist/providers/adapters/codex-adapter.d.ts +22 -0
- package/dist/providers/adapters/copilot-adapter.d.ts +16 -0
- package/dist/providers/adapters/dsh-adapter.d.ts +89 -0
- package/dist/providers/adapters/gemini-adapter.d.ts +44 -0
- package/dist/providers/adapters/hermes-adapter.d.ts +71 -0
- package/dist/providers/adapters/index.d.ts +35 -0
- package/dist/providers/adapters/kimi-adapter.d.ts +16 -0
- package/dist/providers/adapters/lmplayer-adapter.d.ts +14 -0
- package/dist/providers/adapters/opencode-adapter.d.ts +11 -0
- package/dist/providers/adapters/pi-adapter.d.ts +17 -0
- package/dist/providers/adapters/print-turn.d.ts +29 -0
- package/dist/providers/adapters/qwen-adapter.d.ts +11 -0
- package/dist/providers/agy.d.ts +32 -0
- package/dist/providers/base-adapter.d.ts +107 -0
- package/dist/providers/base.d.ts +12 -0
- package/dist/providers/binary-check.d.ts +102 -0
- package/dist/providers/bundle.d.ts +118 -0
- package/dist/providers/claude-mock.d.ts +90 -0
- package/dist/providers/claude.d.ts +21 -0
- package/dist/providers/codex-config.d.ts +15 -0
- package/dist/providers/codex.d.ts +25 -0
- package/dist/providers/config/model-selection-lint.d.ts +45 -0
- package/dist/providers/config/team-config.d.ts +183 -0
- package/dist/providers/config/types.d.ts +146 -0
- package/dist/providers/config/user-config.d.ts +88 -0
- package/dist/providers/copilot.d.ts +21 -0
- package/dist/providers/debug.d.ts +128 -0
- package/dist/providers/dsh-sidecar.d.ts +65 -0
- package/dist/providers/dsh-zstd.d.ts +27 -0
- package/dist/providers/dsh.d.ts +37 -0
- package/dist/providers/frontmatter.d.ts +46 -0
- package/dist/providers/gemini-session-reader.d.ts +151 -0
- package/dist/providers/gemini.d.ts +39 -0
- package/dist/providers/hermes.d.ts +90 -0
- package/dist/providers/index.d.ts +421 -0
- package/dist/providers/kimi.d.ts +37 -0
- package/dist/providers/model-catalog.d.ts +141 -0
- package/dist/providers/one-shot-timeout.d.ts +1 -0
- package/dist/providers/opencode-config.d.ts +62 -0
- package/dist/providers/opencode.d.ts +40 -0
- package/dist/providers/parsers/acp.d.ts +47 -0
- package/dist/providers/parsers/codex-app-server.d.ts +60 -0
- package/dist/providers/parsers/stream-json.d.ts +57 -0
- package/dist/providers/passive-readers/agy.d.ts +155 -0
- package/dist/providers/passive-readers/copilot.d.ts +173 -0
- package/dist/providers/passive-readers/dsh.d.ts +34 -0
- package/dist/providers/passive-readers/kimi.d.ts +44 -0
- package/dist/providers/passive-readers/opencode.d.ts +89 -0
- package/dist/providers/passive-readers/qwen.d.ts +151 -0
- package/dist/providers/passive-readers/shared.d.ts +138 -0
- package/dist/providers/passive-session-reader.d.ts +188 -0
- package/dist/providers/path-utils.d.ts +1 -0
- package/dist/providers/pi.d.ts +32 -0
- package/dist/providers/pricing.d.ts +107 -0
- package/dist/providers/qwen.d.ts +22 -0
- package/dist/providers/ratelimit-readings.d.ts +32 -0
- package/dist/providers/registry.d.ts +31 -0
- package/dist/providers/resume.d.ts +43 -0
- package/dist/providers/seed-instruction.d.ts +3 -0
- package/dist/providers/self-session-env.d.ts +21 -0
- package/dist/providers/session-limit.d.ts +9 -0
- package/dist/providers/strip-ansi.d.ts +5 -0
- package/dist/providers/transport-errors.d.ts +94 -0
- package/dist/providers/transport-lifecycle.d.ts +63 -0
- package/dist/providers/transport-wrappers.d.ts +110 -0
- package/dist/providers/transports/acp-profiles/config-option-step.d.ts +24 -0
- package/dist/providers/transports/acp-profiles/copilot.d.ts +12 -0
- package/dist/providers/transports/acp-profiles/gemini.d.ts +22 -0
- package/dist/providers/transports/acp-profiles/hermes.d.ts +27 -0
- package/dist/providers/transports/acp-profiles/index.d.ts +19 -0
- package/dist/providers/transports/acp-profiles/kimi.d.ts +24 -0
- package/dist/providers/transports/acp-profiles/opencode.d.ts +26 -0
- package/dist/providers/transports/acp-profiles/types.d.ts +258 -0
- package/dist/providers/transports/acp-rpc.d.ts +18 -0
- package/dist/providers/transports/acp.d.ts +215 -0
- package/dist/providers/transports/codex-app-server.d.ts +190 -0
- package/dist/providers/transports/hold-open.d.ts +120 -0
- package/dist/providers/transports/manager.d.ts +21 -0
- package/dist/providers/transports/stream-json.d.ts +162 -0
- package/dist/providers/transports/types.d.ts +56 -0
- package/dist/providers/types.d.ts +154 -0
- package/dist/providers/yolo-contract.d.ts +131 -0
- package/dist/script/adapter.d.ts +101 -0
- package/dist/script/batch-script.d.ts +7 -0
- package/dist/script/index.d.ts +3 -0
- package/dist/script/lmscript.d.ts +550 -0
- package/dist/shared/policy.d.ts +74 -0
- package/dist/shared-lib/db.d.ts +25 -0
- package/dist/shared-lib/passive-session-reader.d.ts +2 -0
- package/dist/shared-lib/provider-parse.d.ts +6 -0
- package/dist/templates/basedir.d.ts +52 -0
- package/dist/uri/lmctl_uri.d.ts +38 -0
- package/durable-memory/model_selection.md +108 -25
- package/examples/README.md +103 -0
- package/examples/anonymous-panel.lms +3 -0
- package/examples/batch-prompts.txt +9 -0
- package/examples/batchscript.lms +67 -0
- package/examples/n8n/agent-lib.lms +56 -0
- package/examples/n8n/git-lib.lms +93 -0
- package/examples/n8n/mock-support.db +0 -0
- package/examples/n8n/multi-agent-review.lms +107 -0
- package/examples/n8n/news-digest.lms +159 -0
- package/examples/n8n/rag-search.lms +163 -0
- package/examples/n8n/support-agent.lms +88 -0
- package/examples/n8n/ticket-triage.lms +156 -0
- package/package.json +13 -17
- package/dist/cli/side_effect_classifier.json +0 -19
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ACP provider profile registry.
|
|
3
|
+
*
|
|
4
|
+
* The generic ACP engine (transports/acp.ts) looks up the profile for a
|
|
5
|
+
* tool here; everything provider-specific lives in the profile modules.
|
|
6
|
+
*/
|
|
7
|
+
import type { AcpProviderProfile } from "./types.js";
|
|
8
|
+
export declare function acpProfileForTool(tool: string): AcpProviderProfile | undefined;
|
|
9
|
+
/**
|
|
10
|
+
* Read-only view of the per-profile `{ binary, baseArgs }` table for
|
|
11
|
+
* tests that pin the per-provider argv shape (e.g. flag-position
|
|
12
|
+
* regression checks). Production code reads the profiles directly; this
|
|
13
|
+
* alias exists only so the test suite can import a frozen view without
|
|
14
|
+
* making the table mutable from the outside.
|
|
15
|
+
*/
|
|
16
|
+
export declare const ACP_TOOLS_FOR_TEST: Readonly<Record<string, Readonly<{
|
|
17
|
+
binary: string;
|
|
18
|
+
args: readonly string[];
|
|
19
|
+
}>>>;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kimi ACP profile.
|
|
3
|
+
*
|
|
4
|
+
* Kimi's ACP subcommand has no argv permission flag: the posture is
|
|
5
|
+
* protocol-level (`session/set_config_option` with configId "mode"),
|
|
6
|
+
* applied BEFORE any model config and BEFORE the first prompt, and it is
|
|
7
|
+
* FAIL-CLOSED — lmctl cannot answer `session/request_permission`, so a
|
|
8
|
+
* rejected mode change would leave the subprocess hung on its first tool
|
|
9
|
+
* call. Ordering and policy are the `sessionSetup` data below.
|
|
10
|
+
*
|
|
11
|
+
* Kimi 0.28.x exposes only an on/off thinking toggle, so a declared
|
|
12
|
+
* `effort=` cannot be honored here and is silently ignored.
|
|
13
|
+
*/
|
|
14
|
+
import type { AcpProviderProfile } from "./types.js";
|
|
15
|
+
export declare function detectKimiRuntimeDead(resultText: string): string | null;
|
|
16
|
+
/**
|
|
17
|
+
* Kimi logs a multi-line `Error handling notification … -32601 Method not
|
|
18
|
+
* found` block for the mandatory `notifications/initialized` notification
|
|
19
|
+
* on every spawn. It is routine, not a failure — strip exactly that block
|
|
20
|
+
* (and only that block) from the stderr tail attached to a no-result
|
|
21
|
+
* error report.
|
|
22
|
+
*/
|
|
23
|
+
export declare function filterKimiExpectedStderr(stderr: string): string;
|
|
24
|
+
export declare const kimiProfile: AcpProviderProfile;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenCode ACP profile.
|
|
3
|
+
*
|
|
4
|
+
* OpenCode's ACP mode rejects every permission-bypass argv flag
|
|
5
|
+
* (`--dangerously-skip-permissions` is a `run`-subcommand-only flag,
|
|
6
|
+
* verified empirically). YOLO is satisfied via
|
|
7
|
+
* `<cwd>/.opencode/opencode.json` carrying `"permission": "allow"`,
|
|
8
|
+
* installed defensively before every chat spawn (preSpawn) so even
|
|
9
|
+
* pre-fix-seeded teams get the YOLO key without a re-create. The install
|
|
10
|
+
* no longer writes lmctl MCP entries.
|
|
11
|
+
*
|
|
12
|
+
* The profile also owns `probeOpenCodeAcpConfig` — a separate probe entry
|
|
13
|
+
* point (purpose "probe") used by lint/seed via teamfile-compat.ts. It
|
|
14
|
+
* intentionally differs from the chat spawn: no runtime permission-config
|
|
15
|
+
* install (initialize/session creation and config-option validation
|
|
16
|
+
* cannot request tools or incur an LLM call), and a rejected model/effort
|
|
17
|
+
* option is reported as probe evidence rather than absorbed as a warning.
|
|
18
|
+
*/
|
|
19
|
+
import type { AcpProviderProfile, OpenCodeAcpConfigProbeOptions, OpenCodeAcpConfigProbeResult } from "./types.js";
|
|
20
|
+
export declare const openCodeProfile: AcpProviderProfile;
|
|
21
|
+
/**
|
|
22
|
+
* Verify opencode's live per-session model/effort contract without prompting.
|
|
23
|
+
* No project permission config is installed: initialize/session creation and
|
|
24
|
+
* config-option validation cannot request tools or incur an LLM call.
|
|
25
|
+
*/
|
|
26
|
+
export declare function probeOpenCodeAcpConfig(opts: OpenCodeAcpConfigProbeOptions): Promise<OpenCodeAcpConfigProbeResult>;
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `AcpProviderProfile` — the per-provider half of the ACP transport.
|
|
3
|
+
*
|
|
4
|
+
* transports/acp.ts is the generic protocol engine (RPC channel,
|
|
5
|
+
* lifecycle/abort/hold-open scaffolding, initialize → session/load|new →
|
|
6
|
+
* session/prompt skeleton, resume contract). Everything that used to be a
|
|
7
|
+
* per-provider `if (tool === …)` branch lives behind this interface in
|
|
8
|
+
* `acp-profiles/{kimi,opencode,copilot,gemini,hermes}.ts`.
|
|
9
|
+
*
|
|
10
|
+
* Conventions (binding, from the batch-redesign v2 review):
|
|
11
|
+
* - Hooks receive ONE typed context object (no two-argument hooks):
|
|
12
|
+
* permissionMode, model, effort, acpMode, purpose ('chat' | 'probe'),
|
|
13
|
+
* and a warnings channel. Hook-specific inputs arrive on an extension
|
|
14
|
+
* of that object (AcpSpawnContext, AcpSessionContext, …).
|
|
15
|
+
* - Ordering/failure policy of post-open configuration is explicit
|
|
16
|
+
* per-profile DATA: `sessionSetup` array order is the wire order and
|
|
17
|
+
* each step declares `onFailure: "fail-closed" | "non-fatal"`. The
|
|
18
|
+
* driver interprets the policy; profiles never branch on it inline.
|
|
19
|
+
* - Result handling uses stateful hooks: gemini's replay isolation lives
|
|
20
|
+
* in the output processor via `isolateResult`; kimi's runtime-dead
|
|
21
|
+
* detection is the `onExitFinalization` telemetry hook. There are no
|
|
22
|
+
* stateless classifyResult/filterStderr result transforms.
|
|
23
|
+
* - `requireResume` is the generic driver's resume contract, NOT a
|
|
24
|
+
* profile capability.
|
|
25
|
+
*/
|
|
26
|
+
import type { PermissionMode } from "../../yolo-contract.js";
|
|
27
|
+
import type { AcpRpcChannel } from "../acp-rpc.js";
|
|
28
|
+
/**
|
|
29
|
+
* ACP session mode ids this transport may set. Kept as a closed type so a
|
|
30
|
+
* typo cannot reach the wire as an unrecognized mode id.
|
|
31
|
+
*/
|
|
32
|
+
export type AcpMode = "default" | "accept_edits" | "dont_ask";
|
|
33
|
+
/** Why an ACP process is being spawned. */
|
|
34
|
+
export type AcpPurpose = "chat" | "probe";
|
|
35
|
+
/**
|
|
36
|
+
* Typed context handed to every profile hook.
|
|
37
|
+
*/
|
|
38
|
+
export interface AcpProfileContext {
|
|
39
|
+
/** Resolved permission mode for this spawn ("yolo" is the default). */
|
|
40
|
+
readonly permissionMode: PermissionMode;
|
|
41
|
+
/** Per-session model override, when the caller declared one. */
|
|
42
|
+
readonly model?: string;
|
|
43
|
+
/** Per-session effort / variant override, when the caller declared one. */
|
|
44
|
+
readonly effort?: string;
|
|
45
|
+
/**
|
|
46
|
+
* Explicit ACP `session/set_mode` mode id, validated by the driver
|
|
47
|
+
* against the profile's `modeVocabulary` before spawn (fail-closed).
|
|
48
|
+
*/
|
|
49
|
+
readonly acpMode?: AcpMode;
|
|
50
|
+
readonly purpose: AcpPurpose;
|
|
51
|
+
/**
|
|
52
|
+
* Warnings channel. Strings pushed here are human-readable records of
|
|
53
|
+
* provider config the turn proceeded past; for purpose "chat" they land
|
|
54
|
+
* on `AcpResult.warnings` (task #232), for purpose "probe" they are
|
|
55
|
+
* probe evidence.
|
|
56
|
+
*/
|
|
57
|
+
readonly warnings: {
|
|
58
|
+
push(warning: string): void;
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
/** preSpawn hook context: the profile context plus the spawn target. */
|
|
62
|
+
export interface AcpSpawnContext extends AcpProfileContext {
|
|
63
|
+
readonly cwd: string;
|
|
64
|
+
/** The already-built spawn environment; profiles may mutate it. */
|
|
65
|
+
readonly env: NodeJS.ProcessEnv;
|
|
66
|
+
}
|
|
67
|
+
/** afterSessionOpen step context: the profile context plus the open session. */
|
|
68
|
+
export interface AcpSessionContext extends AcpProfileContext {
|
|
69
|
+
readonly sessionId: string;
|
|
70
|
+
/** Raw `session/new` / `session/load` response (model catalogs, currentModelId…). */
|
|
71
|
+
readonly response: Record<string, unknown>;
|
|
72
|
+
readonly sendRpc: AcpRpcChannel["sendRpc"];
|
|
73
|
+
}
|
|
74
|
+
/** onExitFinalization hook context: the finalized turn result. */
|
|
75
|
+
export interface AcpFinalizeContext extends AcpProfileContext {
|
|
76
|
+
readonly sessionId: string;
|
|
77
|
+
readonly result: string;
|
|
78
|
+
readonly stopReason: string;
|
|
79
|
+
readonly exitCode: number | null;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Bundle-open identity verification context: everything a profile needs to
|
|
83
|
+
* prove the requested session was genuinely loaded.
|
|
84
|
+
*/
|
|
85
|
+
export interface AcpSessionIdentityContext extends AcpProfileContext {
|
|
86
|
+
readonly requestedSessionId: string;
|
|
87
|
+
/** Raw `session/load` response (its sessionId echo, if any, was already
|
|
88
|
+
* checked by the driver: a DIFFERENT echoed id always fails before this
|
|
89
|
+
* hook runs). */
|
|
90
|
+
readonly loadResponse: Record<string, unknown>;
|
|
91
|
+
readonly cwd: string;
|
|
92
|
+
/** The spawn env — provider-home overrides (KIMI_CODE_HOME etc.) reach
|
|
93
|
+
* the verifier through here. */
|
|
94
|
+
readonly env: NodeJS.ProcessEnv;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Per-profile session-identity strategy for BUNDLE open (`openAcpSession`
|
|
98
|
+
* strict mode). The driver never falls back to "assume the requested id":
|
|
99
|
+
* a profile must declare how a genuine load is proven.
|
|
100
|
+
*
|
|
101
|
+
* - Providers whose session/load echoes the session id need nothing here:
|
|
102
|
+
* the driver's exact-echo check is the whole strategy (default when this
|
|
103
|
+
* field is absent — and the absence of an echo then fails closed).
|
|
104
|
+
* - Providers whose load does NOT echo an id declare a hook:
|
|
105
|
+
* kimi verifies via its on-disk session store; hermes relies on its
|
|
106
|
+
* fail-closed load (a non-throwing load IS the proof — see the profile).
|
|
107
|
+
*/
|
|
108
|
+
export interface AcpSessionIdentity {
|
|
109
|
+
/** Verify at bundle open that the requested session was genuinely loaded.
|
|
110
|
+
* Throw on failure. Only called when the load response carried no
|
|
111
|
+
* contradicting echo. */
|
|
112
|
+
verifyLoadedSession(ctx: AcpSessionIdentityContext): Promise<void> | void;
|
|
113
|
+
/**
|
|
114
|
+
* Cheap between-messages re-check (catches an external re-seed/delete
|
|
115
|
+
* mid-bundle before the next prompt is sent). Optional — providers whose
|
|
116
|
+
* identity is pinned by the live process need nothing here (the per-turn
|
|
117
|
+
* expectedSession check upstream catches teamfile rotation).
|
|
118
|
+
*/
|
|
119
|
+
recheckSession?(ctx: {
|
|
120
|
+
requestedSessionId: string;
|
|
121
|
+
cwd: string;
|
|
122
|
+
env: NodeJS.ProcessEnv;
|
|
123
|
+
}): Promise<void> | void;
|
|
124
|
+
}
|
|
125
|
+
/** Structured description of a session-step failure, built by the step. */
|
|
126
|
+
export interface AcpStepFailure {
|
|
127
|
+
/** errorLog event name. The driver merges `{ event, provider, ...fields }`. */
|
|
128
|
+
readonly event: string;
|
|
129
|
+
readonly fields: Record<string, unknown>;
|
|
130
|
+
/** Human-readable stderr line (trailing newline included), when surfaced there. */
|
|
131
|
+
readonly stderrLine?: string;
|
|
132
|
+
/** Warnings-channel entry — the config the turn proceeded past. */
|
|
133
|
+
readonly warning?: string;
|
|
134
|
+
/** Thrown message; used only by fail-closed steps. */
|
|
135
|
+
readonly message: string;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* One ordered post-open configuration step (after session/new|load, before
|
|
139
|
+
* the first prompt).
|
|
140
|
+
*/
|
|
141
|
+
export interface AcpSessionStep {
|
|
142
|
+
/** Stable id for logs and tests, e.g. "permission-mode" | "session-mode" | "model" | "effort". */
|
|
143
|
+
readonly id: string;
|
|
144
|
+
/**
|
|
145
|
+
* Failure policy — profile DATA, interpreted by the driver:
|
|
146
|
+
* - "fail-closed": a rejected step aborts the turn before
|
|
147
|
+
* session/prompt (a provider that refused the posture can hang on its
|
|
148
|
+
* first tool call).
|
|
149
|
+
* - "non-fatal": record the failure (errorLog + stderr + warnings
|
|
150
|
+
* channel) and proceed with whatever config was already active.
|
|
151
|
+
*/
|
|
152
|
+
readonly onFailure: "fail-closed" | "non-fatal";
|
|
153
|
+
applies(ctx: AcpSessionContext): boolean;
|
|
154
|
+
run(ctx: AcpSessionContext): Promise<void>;
|
|
155
|
+
describeFailure(ctx: AcpSessionContext, error: unknown): AcpStepFailure;
|
|
156
|
+
/** dlogv line on success (debug-gated). */
|
|
157
|
+
successLog?(ctx: AcpSessionContext): string;
|
|
158
|
+
}
|
|
159
|
+
/** Options for the opencode profile's probe entry point. */
|
|
160
|
+
export interface OpenCodeAcpConfigProbeOptions {
|
|
161
|
+
cwd: string;
|
|
162
|
+
model: string;
|
|
163
|
+
effort?: string;
|
|
164
|
+
env?: NodeJS.ProcessEnv;
|
|
165
|
+
binaryPath?: string;
|
|
166
|
+
timeoutMs?: number;
|
|
167
|
+
}
|
|
168
|
+
export type OpenCodeAcpConfigProbeResult = {
|
|
169
|
+
status: "verified";
|
|
170
|
+
} | {
|
|
171
|
+
status: "rejected";
|
|
172
|
+
option: "model" | "effort";
|
|
173
|
+
value: string;
|
|
174
|
+
error: string;
|
|
175
|
+
} | {
|
|
176
|
+
status: "unavailable";
|
|
177
|
+
reason: "spawn" | "handshake" | "timeout";
|
|
178
|
+
error?: string;
|
|
179
|
+
};
|
|
180
|
+
export interface AcpProviderProfile {
|
|
181
|
+
/** Provider key: "kimi" | "opencode" | "copilot" | "gemini" | "hermes". */
|
|
182
|
+
readonly name: string;
|
|
183
|
+
/** Default binary; per-call overridable via `AcpOptions.binaryPath`. */
|
|
184
|
+
readonly binary: string;
|
|
185
|
+
/**
|
|
186
|
+
* Static base argv (the historical ACP_TOOLS entry). Per-call rewrites
|
|
187
|
+
* (permission mode, model) happen in `argv()` — never by mutating this.
|
|
188
|
+
*/
|
|
189
|
+
readonly baseArgs: readonly string[];
|
|
190
|
+
/**
|
|
191
|
+
* Effective spawn argv for the resolved context. Absorbs the base args
|
|
192
|
+
* plus plan-mode rewrites (gemini's `-y` → `--approval-mode plan` swap)
|
|
193
|
+
* and spawn-time model flags (gemini `--model`). May warn (copilot
|
|
194
|
+
* plan/model gaps) or throw (hermes plan refusal).
|
|
195
|
+
*/
|
|
196
|
+
argv(ctx: AcpProfileContext): string[];
|
|
197
|
+
/**
|
|
198
|
+
* Which mode the YOLO argv pre-flight asserts against the produced argv:
|
|
199
|
+
* the effective permission mode (default), or always "yolo" for
|
|
200
|
+
* providers that deliberately keep their yolo flag in plan mode
|
|
201
|
+
* (copilot).
|
|
202
|
+
*/
|
|
203
|
+
readonly yoloAssertion?: "effective" | "always-yolo";
|
|
204
|
+
/**
|
|
205
|
+
* Pre-spawn filesystem/env preparation: opencode installs its runtime
|
|
206
|
+
* permission config, hermes sets HERMES_YOLO_MODE. Runs before the
|
|
207
|
+
* cwd-existence pre-check (historical ordering).
|
|
208
|
+
*/
|
|
209
|
+
preSpawn?(ctx: AcpSpawnContext): void;
|
|
210
|
+
/**
|
|
211
|
+
* ACP `session/set_mode` vocabulary for providers whose permission
|
|
212
|
+
* posture is an ACP mode ladder (hermes). `known` is what the provider
|
|
213
|
+
* implements, `selectable` the subset lmctl is willing to drive a member
|
|
214
|
+
* into. Absent ⇒ the provider declares no vocabulary and any acpMode
|
|
215
|
+
* request is rejected fail-closed before spawn.
|
|
216
|
+
*/
|
|
217
|
+
readonly modeVocabulary?: {
|
|
218
|
+
known: readonly AcpMode[];
|
|
219
|
+
selectable: readonly AcpMode[];
|
|
220
|
+
};
|
|
221
|
+
/**
|
|
222
|
+
* Ordered post-open configuration steps. Array order IS the wire order
|
|
223
|
+
* (kimi: permission mode before model; hermes: set_mode before
|
|
224
|
+
* set_model; opencode: model before effort). Per-step failure policy is
|
|
225
|
+
* the step's `onFailure`.
|
|
226
|
+
*/
|
|
227
|
+
readonly sessionSetup?: readonly AcpSessionStep[];
|
|
228
|
+
/**
|
|
229
|
+
* Result-text isolation applied INSIDE the stateful output processor
|
|
230
|
+
* (parsers/acp.ts) — gemini's latest-agent-message replay isolation.
|
|
231
|
+
*/
|
|
232
|
+
readonly isolateResult?: (text: string) => string;
|
|
233
|
+
/**
|
|
234
|
+
* Bundle-open session-identity strategy (see AcpSessionIdentity).
|
|
235
|
+
* Absent ⇒ the driver requires an exact sessionId echo in the load
|
|
236
|
+
* response and fails closed without one.
|
|
237
|
+
*/
|
|
238
|
+
readonly sessionIdentity?: AcpSessionIdentity;
|
|
239
|
+
/**
|
|
240
|
+
* Structured telemetry emitted during exit finalization, after the
|
|
241
|
+
* output processor has produced the final result (kimi runtime-dead).
|
|
242
|
+
*/
|
|
243
|
+
onExitFinalization?(ctx: AcpFinalizeContext): void;
|
|
244
|
+
/**
|
|
245
|
+
* Filters provider-known-benign stderr blocks out of the no-result
|
|
246
|
+
* error report (kimi's notifications/initialized -32601 block). This is
|
|
247
|
+
* error-path stderr hygiene, not a result transform.
|
|
248
|
+
*/
|
|
249
|
+
filterExpectedStderr?(stderr: string): string;
|
|
250
|
+
/**
|
|
251
|
+
* Separate probe entry point (opencode only): verifies the provider's
|
|
252
|
+
* live per-session model/effort contract WITHOUT prompting. It
|
|
253
|
+
* intentionally differs from the chat spawn — no runtime
|
|
254
|
+
* permission-config install, and a rejected model/effort option is
|
|
255
|
+
* reported as probe evidence rather than absorbed as a warning.
|
|
256
|
+
*/
|
|
257
|
+
readonly probe?: (opts: OpenCodeAcpConfigProbeOptions) => Promise<OpenCodeAcpConfigProbeResult>;
|
|
258
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ACP JSON-RPC 2.0 channel over a spawned child's stdin/stdout.
|
|
3
|
+
*
|
|
4
|
+
* Pure protocol plumbing shared by the chat engine (transports/acp.ts)
|
|
5
|
+
* and profile-owned entry points that run their own spawn/RPC lifecycle
|
|
6
|
+
* (the opencode config probe in transports/acp-profiles/opencode.ts).
|
|
7
|
+
*/
|
|
8
|
+
import type { ChildProcess } from "node:child_process";
|
|
9
|
+
export interface AcpRpcChannel {
|
|
10
|
+
sendRpc(method: string, params?: Record<string, unknown>): Promise<Record<string, unknown>>;
|
|
11
|
+
sendNotification(method: string, params?: Record<string, unknown>): void;
|
|
12
|
+
close(error?: Error): void;
|
|
13
|
+
}
|
|
14
|
+
export declare function createAcpRpcChannel(child: ChildProcess, opts: {
|
|
15
|
+
provider: string;
|
|
16
|
+
onEvent?: (event: Record<string, unknown>) => void;
|
|
17
|
+
onNotification?: (event: Record<string, unknown>) => void;
|
|
18
|
+
}): AcpRpcChannel;
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic ACP (Agent Client Protocol) transport — the protocol engine.
|
|
3
|
+
*
|
|
4
|
+
* Works with any CLI tool that supports ACP (Gemini, Copilot, OpenCode, Kimi,
|
|
5
|
+
* Hermes). Spawns the tool and communicates via ACP JSON-RPC 2.0 over
|
|
6
|
+
* stdin/stdout.
|
|
7
|
+
*
|
|
8
|
+
* Protocol:
|
|
9
|
+
* initialize → session/new (or session/load) → session/prompt
|
|
10
|
+
* ← session/update (agent_message_chunk, streaming)
|
|
11
|
+
* ← result with stopReason
|
|
12
|
+
*
|
|
13
|
+
* Everything provider-specific (argv shapes, plan-mode rewrites, config
|
|
14
|
+
* installs, post-open mode/model/effort configuration, result isolation,
|
|
15
|
+
* exit telemetry, the opencode config probe) lives behind
|
|
16
|
+
* `AcpProviderProfile` in `./acp-profiles/`; this file is the generic
|
|
17
|
+
* driver: RPC channel lifecycle, abort/hold-open scaffolding, the
|
|
18
|
+
* initialize → load/new → prompt skeleton, and the resume contract
|
|
19
|
+
* (`requireResume` — deliberately NOT a profile capability).
|
|
20
|
+
*
|
|
21
|
+
* `sendAcpPrompt` is internally phased (spawn → open → configure → prompt
|
|
22
|
+
* → finalize) so a future `openAcpSession(profile, opts)` bundle seam can
|
|
23
|
+
* hoist the spawn+open phases out of the per-turn prompt phase without
|
|
24
|
+
* restructuring (batch redesign, item 2/3).
|
|
25
|
+
*/
|
|
26
|
+
import { type HoldOpenOptions } from "./hold-open.js";
|
|
27
|
+
import { type PermissionMode } from "../yolo-contract.js";
|
|
28
|
+
import { type AcpResult } from "../parsers/acp.js";
|
|
29
|
+
import { ACP_TOOLS_FOR_TEST } from "./acp-profiles/index.js";
|
|
30
|
+
import type { AcpMode } from "./acp-profiles/types.js";
|
|
31
|
+
import { detectKimiRuntimeDead } from "./acp-profiles/kimi.js";
|
|
32
|
+
import { probeOpenCodeAcpConfig } from "./acp-profiles/opencode.js";
|
|
33
|
+
export { acpSessionIdFromResponse, acpStopReasonFromResponse, createAcpOutputProcessor, isolateGeminiLatestAgentMessage, supportsAcp, } from "../parsers/acp.js";
|
|
34
|
+
export type { AcpOutputProcessor, AcpOutputProcessorResult, AcpResult, } from "../parsers/acp.js";
|
|
35
|
+
export { ACP_TOOLS_FOR_TEST, detectKimiRuntimeDead, probeOpenCodeAcpConfig };
|
|
36
|
+
export type { AcpMode } from "./acp-profiles/types.js";
|
|
37
|
+
export type { OpenCodeAcpConfigProbeOptions, OpenCodeAcpConfigProbeResult, } from "./acp-profiles/types.js";
|
|
38
|
+
export interface AcpOptions {
|
|
39
|
+
/** Existing session ID to load. If omitted, creates a new session. */
|
|
40
|
+
sessionId?: string;
|
|
41
|
+
cwd: string;
|
|
42
|
+
env?: NodeJS.ProcessEnv;
|
|
43
|
+
/** Tool name — determines the provider profile (binary, argv, hooks) */
|
|
44
|
+
tool: string;
|
|
45
|
+
/**
|
|
46
|
+
* Override the spawned binary path. When set, this exact path is
|
|
47
|
+
* spawned (with the profile's standard ACP args) instead of the
|
|
48
|
+
* profile's default binary. Used for troubleshooting custom
|
|
49
|
+
* builds. (Task 63 fix 3.)
|
|
50
|
+
*/
|
|
51
|
+
binaryPath?: string;
|
|
52
|
+
/** If true, failed session/load is an error. If false, falls back to session/new. */
|
|
53
|
+
requireResume?: boolean;
|
|
54
|
+
/**
|
|
55
|
+
* Per-session model override. Honored for Gemini via a
|
|
56
|
+
* spawn-time `--model` flag and for OpenCode/Kimi via ACP
|
|
57
|
+
* `session/set_config_option`. Format: `providerID/modelID` (e.g.
|
|
58
|
+
* `anthropic/claude-3-5-sonnet-20241022`). Copilot does not expose
|
|
59
|
+
* a per-session ACP model selection surface.
|
|
60
|
+
*/
|
|
61
|
+
model?: string;
|
|
62
|
+
/**
|
|
63
|
+
* Per-session effort / variant override. OpenCode uses ACP configId
|
|
64
|
+
* `"effort"`. Kimi 0.28.x exposes only an on/off thinking toggle,
|
|
65
|
+
* so its model-defined effort cannot be overridden here.
|
|
66
|
+
*/
|
|
67
|
+
effort?: string;
|
|
68
|
+
/**
|
|
69
|
+
* Teamfile alias of the spawned member, retained for diagnostics and
|
|
70
|
+
* provider-specific policy.
|
|
71
|
+
*/
|
|
72
|
+
alias?: string;
|
|
73
|
+
/**
|
|
74
|
+
* Permission mode for this turn (default "yolo"). How "plan" is
|
|
75
|
+
* realized is profile policy: gemini's argv swaps `-y` for
|
|
76
|
+
* `--approval-mode plan`, kimi sets its protocol-level mode to "plan",
|
|
77
|
+
* copilot keeps `--yolo` and warns (no plan flag exists), opencode is
|
|
78
|
+
* config-file based, and hermes refuses plan mode outright.
|
|
79
|
+
*
|
|
80
|
+
* Caveat (gemini): plan mode in gemini-cli filters `write_file` by
|
|
81
|
+
* workspace path only; `run_shell_command` can still escape. A
|
|
82
|
+
* follow-up will tighten the sandbox for plan mode.
|
|
83
|
+
*/
|
|
84
|
+
permissionMode?: PermissionMode;
|
|
85
|
+
/** Called for each text delta */
|
|
86
|
+
onDelta?: (text: string) => void;
|
|
87
|
+
/** Called for each raw JSON-RPC event */
|
|
88
|
+
onEvent?: (event: Record<string, unknown>) => void;
|
|
89
|
+
/** Abort signal — kills the provider process when aborted */
|
|
90
|
+
signal?: AbortSignal;
|
|
91
|
+
/**
|
|
92
|
+
* Defer closing the child's stdin past the turn's result while the session
|
|
93
|
+
* still has outstanding `run_in_background` dispatches (task #68, extended to
|
|
94
|
+
* ACP). Absent ⇒ the exact pre-existing synchronous close. See
|
|
95
|
+
* {@link HoldOpenOptions}.
|
|
96
|
+
*/
|
|
97
|
+
holdOpen?: HoldOpenOptions;
|
|
98
|
+
/**
|
|
99
|
+
* Explicit ACP `session/set_mode` mode id to apply to the session on
|
|
100
|
+
* every spawn, after `session/new`/`session/load` completes.
|
|
101
|
+
*
|
|
102
|
+
* Needed by providers whose ACP mode ladder is NOT covered by an argv
|
|
103
|
+
* permission flag and NOT expressible through lmctl's own
|
|
104
|
+
* `permissionMode` vocabulary. Hermes is the only such provider today:
|
|
105
|
+
* its ladder (`default`/`accept_edits`/`dont_ask`) governs the file-EDIT
|
|
106
|
+
* gate, which `HERMES_YOLO_MODE` does not approve, so the mode must be
|
|
107
|
+
* set per process or every edit stalls on a
|
|
108
|
+
* `session/request_permission` round-trip this client cannot answer.
|
|
109
|
+
*
|
|
110
|
+
* Validated at the call site against the profile's mode vocabulary —
|
|
111
|
+
* an unconfigured value throws fail-closed (`assertAcpModeSupported`)
|
|
112
|
+
* rather than being forwarded as an unknown mode id.
|
|
113
|
+
*/
|
|
114
|
+
acpMode?: AcpMode;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Fail-closed guard for `AcpOptions.acpMode`. Throws for a provider whose
|
|
118
|
+
* profile declares no mode vocabulary (nothing to validate against ⇒
|
|
119
|
+
* never guess) and for a mode outside the vocabulary's selectable subset.
|
|
120
|
+
*/
|
|
121
|
+
export declare function assertAcpModeSupported(tool: string, mode: AcpMode): void;
|
|
122
|
+
export declare function filterExpectedAcpStderr(tool: string, stderr: string): string;
|
|
123
|
+
/**
|
|
124
|
+
* Send a prompt via ACP to any supported tool.
|
|
125
|
+
*/
|
|
126
|
+
export declare function sendAcpPrompt(prompt: string, opts: AcpOptions): Promise<AcpResult>;
|
|
127
|
+
/**
|
|
128
|
+
* Per-turn options for an open ACP bundle session. The spawn-time profile
|
|
129
|
+
* context (permissionMode, model, effort, acpMode) was fixed at open; a
|
|
130
|
+
* turn only carries its streaming callbacks and an abort signal.
|
|
131
|
+
*/
|
|
132
|
+
export interface AcpSessionTurnOptions {
|
|
133
|
+
/** Called for each text delta belonging to THIS turn. */
|
|
134
|
+
onDelta?: (text: string) => void;
|
|
135
|
+
/** Called for each raw JSON-RPC event during THIS turn. */
|
|
136
|
+
onEvent?: (event: Record<string, unknown>) => void;
|
|
137
|
+
/**
|
|
138
|
+
* Abort signal for THIS turn. Abortion rejects the turn with
|
|
139
|
+
* `Error("Aborted")`, sends a best-effort `session/cancel`
|
|
140
|
+
* notification, and leaves the session open and usable — the child is
|
|
141
|
+
* NOT killed (contrast with `sendAcpPrompt`, where abort terminates the
|
|
142
|
+
* one-shot process). Chunks a provider still emits for the cancelled
|
|
143
|
+
* turn are dropped by the output processor.
|
|
144
|
+
*/
|
|
145
|
+
signal?: AbortSignal;
|
|
146
|
+
}
|
|
147
|
+
export interface AcpSessionTurnResult {
|
|
148
|
+
result: string;
|
|
149
|
+
sessionId: string;
|
|
150
|
+
stopReason: string;
|
|
151
|
+
durationMs: number;
|
|
152
|
+
/**
|
|
153
|
+
* The open-time profile configuration warnings (task #232 model/effort
|
|
154
|
+
* drops) are reported on the FIRST turn's result — in bundle mode the
|
|
155
|
+
* profile's sessionSetup runs once at open, not per spawn.
|
|
156
|
+
*/
|
|
157
|
+
warnings?: string[];
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* An open ACP bundle session: one provider process serving many prompts.
|
|
161
|
+
*
|
|
162
|
+
* Lifecycle contract (agreed in the batch-redesign v2 review before
|
|
163
|
+
* implementation; pinned by tests/providers/acp-bundle.test.ts):
|
|
164
|
+
*
|
|
165
|
+
* - Per-turn completion boundary: a turn ends at its `session/prompt`
|
|
166
|
+
* result. The stateful output processor is retained across turns and
|
|
167
|
+
* reset per turn (`beginTurn`), so chunks arriving after the boundary
|
|
168
|
+
* (provider replay trailing a load, late deltas) never leak into the
|
|
169
|
+
* next turn's result.
|
|
170
|
+
* - Cancellation: aborting a turn rejects that turn, sends
|
|
171
|
+
* `session/cancel`, and keeps the session usable.
|
|
172
|
+
* - Hold-open policy: the process is NOT closed between turns — the
|
|
173
|
+
* persistence IS the hold (the per-turn stdin close of
|
|
174
|
+
* `sendAcpPrompt` is suppressed). `close()` runs the caller's
|
|
175
|
+
* `holdOpen` outstanding-work check ONCE before ending stdin, so the
|
|
176
|
+
* final turn's background dispatches are not orphaned.
|
|
177
|
+
* - Unexpected exit: a mid-bundle process exit rejects the in-flight
|
|
178
|
+
* turn and every later turn with an error naming the number of
|
|
179
|
+
* completed prompts.
|
|
180
|
+
* - Idempotent, bounded close: `close()` may be called repeatedly and
|
|
181
|
+
* during an in-flight turn; a child that ignores stdin EOF is
|
|
182
|
+
* terminated after a bounded wait — close never hangs.
|
|
183
|
+
* - Identity: open requires `session/load` to return the EXACT
|
|
184
|
+
* requested session id (no fallback to the requested id, no
|
|
185
|
+
* session/new fallback) before prompt 1 is sent.
|
|
186
|
+
*/
|
|
187
|
+
export interface AcpSession {
|
|
188
|
+
readonly tool: string;
|
|
189
|
+
/** The verified session id (exact match to the requested id). */
|
|
190
|
+
readonly sessionId: string;
|
|
191
|
+
/** Successfully completed turns — the count bundle errors report. */
|
|
192
|
+
readonly completedTurns: number;
|
|
193
|
+
sendTurn(prompt: string, opts?: AcpSessionTurnOptions): Promise<AcpSessionTurnResult>;
|
|
194
|
+
close(): Promise<void>;
|
|
195
|
+
}
|
|
196
|
+
export interface AcpSessionOptions extends AcpOptions {
|
|
197
|
+
/**
|
|
198
|
+
* Shutdown cap: how long close() waits for the child to exit after
|
|
199
|
+
* stdin EOF before escalating to the terminate ladder (which is itself
|
|
200
|
+
* bounded by the same cap per step). Default 2000ms. Exists so tests
|
|
201
|
+
* and operator-driven shutdowns can tighten the bound.
|
|
202
|
+
*/
|
|
203
|
+
closeTimeoutMs?: number;
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Open a persistent ACP session for `opts.tool`: spawn → initialize →
|
|
207
|
+
* session/load (strict identity, fail-closed) → profile sessionSetup —
|
|
208
|
+
* everything `sendAcpPrompt` does per turn EXCEPT the prompt phase, which
|
|
209
|
+
* becomes `session.sendTurn`.
|
|
210
|
+
*
|
|
211
|
+
* `requireResume` is implied: a bundle without its exact teamfile session
|
|
212
|
+
* is always an error. `opts.signal` is intentionally not consulted here —
|
|
213
|
+
* bundle cancellation is per turn (`AcpSessionTurnOptions.signal`).
|
|
214
|
+
*/
|
|
215
|
+
export declare function openAcpSession(opts: AcpSessionOptions): Promise<AcpSession>;
|