dsh-loop-engine 0.1.5-rc1
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/README.md +125 -0
- package/README.zh.md +53 -0
- package/cordis.patch.yml +3 -0
- package/lib/client.js +675 -0
- package/lib/index.js +6821 -0
- package/lib/invariant.js +108 -0
- package/lib/types/client/LoopEngineBadge.d.ts +34 -0
- package/lib/types/client/LoopEngineComposerSelect.d.ts +40 -0
- package/lib/types/client/LoopEngineSection.d.ts +34 -0
- package/lib/types/client/index.d.ts +29 -0
- package/lib/types/client/locales.d.ts +46 -0
- package/lib/types/client/store.d.ts +58 -0
- package/lib/types/commands.d.ts +69 -0
- package/lib/types/driver-core/assistant-stream.d.ts +68 -0
- package/lib/types/driver-core/context-files.d.ts +62 -0
- package/lib/types/driver-core/inbox.d.ts +104 -0
- package/lib/types/driver-core/ownership.d.ts +40 -0
- package/lib/types/driver-core/permission-knobs.d.ts +26 -0
- package/lib/types/driver-core/prompt.d.ts +23 -0
- package/lib/types/driver-core/skill-inject.d.ts +59 -0
- package/lib/types/engine-claude/agent.d.ts +106 -0
- package/lib/types/engine-claude/loop.d.ts +111 -0
- package/lib/types/engine-claude/mapping.d.ts +83 -0
- package/lib/types/engine-claude/permission.d.ts +41 -0
- package/lib/types/engine-claude/process.d.ts +59 -0
- package/lib/types/engine-claude/sdk.d.ts +57 -0
- package/lib/types/engine-claude/types.d.ts +18 -0
- package/lib/types/engine-codex/agent.d.ts +113 -0
- package/lib/types/engine-codex/appserver/client.d.ts +49 -0
- package/lib/types/engine-codex/appserver/mapping.d.ts +67 -0
- package/lib/types/engine-codex/appserver/thread.d.ts +66 -0
- package/lib/types/engine-codex/appserver/types.d.ts +215 -0
- package/lib/types/engine-codex/loop.d.ts +114 -0
- package/lib/types/engine-codex/permission.d.ts +32 -0
- package/lib/types/engine-codex/skills.d.ts +29 -0
- package/lib/types/engine-codex/types.d.ts +19 -0
- package/lib/types/engine-kimi/acp/client.d.ts +76 -0
- package/lib/types/engine-kimi/acp/mapping.d.ts +44 -0
- package/lib/types/engine-kimi/acp/types.d.ts +95 -0
- package/lib/types/engine-kimi/agent.d.ts +130 -0
- package/lib/types/engine-kimi/commands.d.ts +40 -0
- package/lib/types/engine-kimi/loop.d.ts +108 -0
- package/lib/types/engine-kimi/mapping.d.ts +71 -0
- package/lib/types/engine-kimi/permission.d.ts +28 -0
- package/lib/types/engine-kimi/process.d.ts +61 -0
- package/lib/types/engine-kimi/skills.d.ts +57 -0
- package/lib/types/engine-kimi/types.d.ts +23 -0
- package/lib/types/engine-pi/agent.d.ts +150 -0
- package/lib/types/engine-pi/loop.d.ts +125 -0
- package/lib/types/engine-pi/permission.d.ts +43 -0
- package/lib/types/engine-pi/probe.d.ts +23 -0
- package/lib/types/engine-pi/rpc/client.d.ts +105 -0
- package/lib/types/engine-pi/rpc/mapping.d.ts +37 -0
- package/lib/types/engine-pi/rpc/types.d.ts +235 -0
- package/lib/types/engine-pi/skills.d.ts +55 -0
- package/lib/types/engine-pi/types.d.ts +27 -0
- package/lib/types/index.d.ts +114 -0
- package/lib/types/invariant.d.ts +23 -0
- package/lib/types/namespace.d.ts +9 -0
- package/lib/types/patch-manager.d.ts +59 -0
- package/lib/types/preset.d.ts +73 -0
- package/lib/types/provider-route.d.ts +49 -0
- package/lib/types/settings.d.ts +31 -0
- package/lib/types/skills.d.ts +93 -0
- package/package.json +104 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Codex skill provider: exposes the codex CLI's instruction files as DSH
|
|
3
|
+
* skills.
|
|
4
|
+
*
|
|
5
|
+
* Codex has no per-skill catalog like the agents-skill standard; its
|
|
6
|
+
* instructions are `AGENTS.md` files read from the session cwd up to the git
|
|
7
|
+
* root, plus the global `~/.codex/AGENTS.md`. Each file set is surfaced as one
|
|
8
|
+
* user-invocable `agents-md` skill whose body is the concatenated file
|
|
9
|
+
* contents, so the dsh skill-injection seam (`/name` gestures) can carry it
|
|
10
|
+
* into the prompt.
|
|
11
|
+
*
|
|
12
|
+
* @module dsh-loop-engine/engine-codex/skills
|
|
13
|
+
*/
|
|
14
|
+
import type { SkillCandidate, SkillDefinition, SkillLookupOptions, SkillProvider, SkillProviderControl } from '../skills.ts';
|
|
15
|
+
/**
|
|
16
|
+
* Skill provider that discovers `AGENTS.md` from every directory between the
|
|
17
|
+
* project cwd and the git root, plus the user home `~/.codex/AGENTS.md`.
|
|
18
|
+
*/
|
|
19
|
+
export declare class CodexSkillProvider implements SkillProvider {
|
|
20
|
+
private readonly control;
|
|
21
|
+
readonly name = "codex";
|
|
22
|
+
constructor(control: SkillProviderControl);
|
|
23
|
+
list(options: SkillLookupOptions): Promise<readonly SkillCandidate[]>;
|
|
24
|
+
get(candidate: SkillCandidate, _options: SkillLookupOptions): Promise<SkillDefinition | undefined>;
|
|
25
|
+
/** One merged `agents-md` candidate for a ranked file set. */
|
|
26
|
+
private agentsCandidate;
|
|
27
|
+
}
|
|
28
|
+
export default CodexSkillProvider;
|
|
29
|
+
//# sourceMappingURL=skills.d.ts.map
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public types of the Codex loop driver. Types only — no runtime code.
|
|
3
|
+
*
|
|
4
|
+
* @module dsh-loop-engine/engine-codex/types
|
|
5
|
+
*/
|
|
6
|
+
/** Codex CLI sandbox modes, as spoken by the app-server `sandbox` field. */
|
|
7
|
+
export type CodexSandboxMode = 'read-only' | 'workspace-write' | 'danger-full-access';
|
|
8
|
+
/** Codex CLI approval policies, as spoken by the app-server `approvalPolicy` field. */
|
|
9
|
+
export type CodexApprovalPolicy = 'never' | 'on-request' | 'on-failure' | 'untrusted';
|
|
10
|
+
/** Driver configuration after defaults and load-time validation. */
|
|
11
|
+
export interface ResolvedConfig {
|
|
12
|
+
/** Pinned sandbox mode; `undefined` follows the session's dsh permission knobs per query. */
|
|
13
|
+
readonly sandboxMode: CodexSandboxMode | undefined;
|
|
14
|
+
/** Pinned approval policy; `undefined` follows the session's dsh permission knobs per query. */
|
|
15
|
+
readonly approvalPolicy: CodexApprovalPolicy | undefined;
|
|
16
|
+
readonly env: Record<string, string>;
|
|
17
|
+
readonly model: string | undefined;
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSON-RPC client for the `kimi acp` subprocess (Agent Client Protocol over
|
|
3
|
+
* stdio).
|
|
4
|
+
*
|
|
5
|
+
* The driver hands the client a process handle carrying its stdin/stdout/stderr
|
|
6
|
+
* (projected from the dsh subprocess seam). The client frames JSON-RPC 2.0
|
|
7
|
+
* records on a bare `\n` (via a byte decoder, tolerating a trailing `\r`),
|
|
8
|
+
* correlates request responses by `id`, dispatches every `session/update`
|
|
9
|
+
* notification to a buffered event stream, and answers the reverse-RPC
|
|
10
|
+
* `session/request_permission` requests the agent publishes. The child is
|
|
11
|
+
* long-lived (one per factory) and stepped over via `newSession` + `prompt`.
|
|
12
|
+
*
|
|
13
|
+
* @module dsh-loop-engine/engine-kimi/acp/client
|
|
14
|
+
*/
|
|
15
|
+
import type { KimiProcess, KimiSpawnCapability, KimiSpawnSpec } from '../process.ts';
|
|
16
|
+
import { type AcpFrame, type AcpUpdate } from './types.ts';
|
|
17
|
+
/** How the client answers one `session/request_permission`. */
|
|
18
|
+
export type AcpPermissionHandler = (request: AcpFrame) => boolean | Promise<boolean>;
|
|
19
|
+
/** Callback receiving every non-response event line. */
|
|
20
|
+
export type AcpUpdateHandler = (update: AcpUpdate) => void;
|
|
21
|
+
/**
|
|
22
|
+
* ACP client over one `kimi acp` child. Created once per driver scope and reused
|
|
23
|
+
* across steps, matching the Pi RPC client's lifecycle.
|
|
24
|
+
*/
|
|
25
|
+
export declare class AcpClient {
|
|
26
|
+
private readonly process;
|
|
27
|
+
private readonly pending;
|
|
28
|
+
private readonly updateBuffer;
|
|
29
|
+
private updateWake;
|
|
30
|
+
private updateHandler;
|
|
31
|
+
private permissionHandler;
|
|
32
|
+
private sealed;
|
|
33
|
+
private nextId;
|
|
34
|
+
private readonly decoder;
|
|
35
|
+
private buffer;
|
|
36
|
+
/** Whether this client was sealed or its process exited. */
|
|
37
|
+
get closed(): boolean;
|
|
38
|
+
/** Mount a client over an already-spawned `kimi acp` process. */
|
|
39
|
+
constructor(process: KimiProcess);
|
|
40
|
+
/**
|
|
41
|
+
* Create a client, spawning the `kimi acp` child through the supplied
|
|
42
|
+
* capability (or the default node spawn when none is given).
|
|
43
|
+
* @param spec - the `kimi acp` argv/cwd/env the child should run with.
|
|
44
|
+
* @param spawn - optional process-spawn capability (the subprocess seam).
|
|
45
|
+
* @returns the connected client.
|
|
46
|
+
*/
|
|
47
|
+
static create(spec: KimiSpawnSpec, spawn?: KimiSpawnCapability): AcpClient;
|
|
48
|
+
/** Register the event dispatch handler. */
|
|
49
|
+
onUpdate(handler: AcpUpdateHandler): void;
|
|
50
|
+
/** Register the permission-approval handler (reverse-RPC answers). */
|
|
51
|
+
onPermission(handler: AcpPermissionHandler): void;
|
|
52
|
+
/** Send one request and await the correlated response. */
|
|
53
|
+
request(method: string, params: unknown): Promise<unknown>;
|
|
54
|
+
/** Send a notification (no correlated response awaited). */
|
|
55
|
+
notify(method: string, params: unknown): void;
|
|
56
|
+
/** Open the protocol handshake. */
|
|
57
|
+
initialize(): Promise<unknown>;
|
|
58
|
+
/** Start a fresh ACP session and resolve to its session id. */
|
|
59
|
+
newSession(cwd: string): Promise<string>;
|
|
60
|
+
/** Prompt the agent in a session and resolve when the turn completes. */
|
|
61
|
+
prompt(sessionId: string, text: string): Promise<unknown>;
|
|
62
|
+
/** Cancel the active turn in a session (fire-and-forget). */
|
|
63
|
+
cancel(sessionId: string): void;
|
|
64
|
+
/** Answer a pending `session/request_permission`. */
|
|
65
|
+
respondPermission(id: number, approved: boolean): void;
|
|
66
|
+
/** Consume every buffered update as an async generator. */
|
|
67
|
+
updates(): AsyncGenerator<AcpUpdate, void, void>;
|
|
68
|
+
/** Seal the client and request child termination. */
|
|
69
|
+
dispose(): void;
|
|
70
|
+
private feed;
|
|
71
|
+
/** Dispatch one parsed line: a response, an update notification, or a reverse-RPC request. */
|
|
72
|
+
private dispatch;
|
|
73
|
+
private settle;
|
|
74
|
+
private handlePermission;
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Maps `kimi acp` `session/update` events to dsh session-log projections.
|
|
3
|
+
*
|
|
4
|
+
* Kimi streams incremental assistant text (`agent_message_chunk`), incremental
|
|
5
|
+
* thinking (`agent_thought_chunk`), a tool-call announcement (`tool_call`) and its
|
|
6
|
+
* progress/result stream (`tool_call_update`). This module is pure: it classifies
|
|
7
|
+
* an update, extracts chunk deltas, and projects the tool-call identity/result so
|
|
8
|
+
* the agent can fold them into the durable log. Content blocks use the observed
|
|
9
|
+
* kimi `{ type: 'content', content: { type: 'text', text } }` nesting; unknown
|
|
10
|
+
* block types are ignored.
|
|
11
|
+
*
|
|
12
|
+
* @module dsh-loop-engine/engine-kimi/acp/mapping
|
|
13
|
+
*/
|
|
14
|
+
import type { ToolResultMessage } from '@deepseek-ai/dsh-llm';
|
|
15
|
+
import type { AcpContentBlock, AcpToolCallExt, AcpToolCallStreamExt, AcpUpdate } from './types.ts';
|
|
16
|
+
/** Whether the update is an incremental assistant text chunk. */
|
|
17
|
+
export declare function isTextChunk(update: AcpUpdate): update is AcpUpdate & {
|
|
18
|
+
readonly sessionUpdate: 'agent_message_chunk';
|
|
19
|
+
readonly content: AcpContentBlock;
|
|
20
|
+
};
|
|
21
|
+
/** Whether the update is an incremental thinking chunk. */
|
|
22
|
+
export declare function isThoughtChunk(update: AcpUpdate): update is AcpUpdate & {
|
|
23
|
+
readonly sessionUpdate: 'agent_thought_chunk';
|
|
24
|
+
readonly content: AcpContentBlock;
|
|
25
|
+
};
|
|
26
|
+
/** Whether the update announces a tool call. */
|
|
27
|
+
export declare function isToolCall(update: AcpUpdate): update is AcpToolCallExt;
|
|
28
|
+
/** Whether the update streams a tool call's progress/result. */
|
|
29
|
+
export declare function isToolCallUpdate(update: AcpUpdate): update is AcpToolCallStreamExt;
|
|
30
|
+
/** The delta text of a text/thinking chunk. */
|
|
31
|
+
export declare function chunkDelta(update: AcpUpdate): string;
|
|
32
|
+
/** The raw tool-call id (+ content index) as the wire carries it. */
|
|
33
|
+
export declare function toolCallIdOf(update: AcpUpdate): string;
|
|
34
|
+
/** The tool display name (`title`). */
|
|
35
|
+
export declare function toolCallName(update: AcpUpdate): string;
|
|
36
|
+
/** Whether a tool stream status is settled (no longer streaming). */
|
|
37
|
+
export declare function isToolSettledStatus(status: string): boolean;
|
|
38
|
+
/** Whether a tool stream status denotes a failure. */
|
|
39
|
+
export declare function isToolErrorStatus(status: string): boolean;
|
|
40
|
+
/** Join the observed `{ type: 'content', content: { type: 'text', text } }` blocks. */
|
|
41
|
+
export declare function toolContentText(update: AcpToolCallExt | AcpToolCallStreamExt): string;
|
|
42
|
+
/** Project a completed tool call to a durable tool/result message. */
|
|
43
|
+
export declare function toolResult(callId: string, text: string, isError: boolean): ToolResultMessage;
|
|
44
|
+
//# sourceMappingURL=mapping.d.ts.map
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent Client Protocol (ACP) wire types for the `kimi acp` driver.
|
|
3
|
+
*
|
|
4
|
+
* Kimi exposes the ACP adapter over stdio (JSON-RPC 2.0). The driver speaks a
|
|
5
|
+
* minimal, stable subset that a normal agent flow needs: initialize, session/new,
|
|
6
|
+
* session/prompt, session/cancel, plus the `session/update` notifications carrying
|
|
7
|
+
* incremental assistant text (`agent_message_chunk`), thinking
|
|
8
|
+
* (`agent_thought_chunk`), and tool calls (`tool_call` / `tool_call_update`).
|
|
9
|
+
* Tool-approval arrives as the reverse-RPC `session/request_permission`, which the
|
|
10
|
+
* client must answer. Shapes below are the live-record deltas observed against the
|
|
11
|
+
* 0.28.1 CLI; content blocks re-use a `{ type, text }` text shape (and tolerate
|
|
12
|
+
* other block types by ignoring them).
|
|
13
|
+
*
|
|
14
|
+
* @module dsh-loop-engine/engine-kimi/acp/types
|
|
15
|
+
*/
|
|
16
|
+
/** A text content block in an ACP update (the observed kimi shape). */
|
|
17
|
+
export interface AcpTextContent {
|
|
18
|
+
readonly type: 'text';
|
|
19
|
+
readonly text: string;
|
|
20
|
+
}
|
|
21
|
+
/** One tool-call content block: the observed kimi `{ type: 'content', content }` nesting. */
|
|
22
|
+
export interface AcpToolContentBlock {
|
|
23
|
+
readonly type: 'content';
|
|
24
|
+
readonly content: AcpTextContent;
|
|
25
|
+
}
|
|
26
|
+
/** A content block in a chunk/thought update; text is handled, others are ignored. */
|
|
27
|
+
export type AcpContentBlock = AcpTextContent | {
|
|
28
|
+
readonly type: 'image' | 'resource_link' | 'audio' | string;
|
|
29
|
+
readonly [key: string]: unknown;
|
|
30
|
+
};
|
|
31
|
+
/** One `session/update` notification (partial; the discriminator is `sessionUpdate`). */
|
|
32
|
+
export interface AcpUpdate {
|
|
33
|
+
readonly sessionUpdate: string;
|
|
34
|
+
readonly [key: string]: unknown;
|
|
35
|
+
}
|
|
36
|
+
/** A tool-call announcement (`sessionUpdate: 'tool_call'`). */
|
|
37
|
+
export interface AcpToolCallExt extends AcpUpdate {
|
|
38
|
+
readonly sessionUpdate: 'tool_call';
|
|
39
|
+
readonly toolCallId: string;
|
|
40
|
+
readonly title: string;
|
|
41
|
+
readonly kind: string;
|
|
42
|
+
readonly status: string;
|
|
43
|
+
readonly content?: readonly AcpToolContentBlock[];
|
|
44
|
+
}
|
|
45
|
+
/** A tool-call progress/result stream (`sessionUpdate: 'tool_call_update'`). */
|
|
46
|
+
export interface AcpToolCallStreamExt extends AcpUpdate {
|
|
47
|
+
readonly sessionUpdate: 'tool_call_update';
|
|
48
|
+
readonly toolCallId: string;
|
|
49
|
+
readonly status: string;
|
|
50
|
+
readonly content?: readonly AcpToolContentBlock[];
|
|
51
|
+
}
|
|
52
|
+
/** The `session/request_permission` reverse-RPC params. */
|
|
53
|
+
export interface AcpPermissionRequest {
|
|
54
|
+
readonly sessionId?: string;
|
|
55
|
+
readonly request?: unknown;
|
|
56
|
+
}
|
|
57
|
+
/** Result of a completed `session/prompt` request (opaque; the turn ended). */
|
|
58
|
+
export interface AcpPromptResult {
|
|
59
|
+
readonly [key: string]: unknown;
|
|
60
|
+
}
|
|
61
|
+
/** The id-scoped response to a request (result or error). */
|
|
62
|
+
export interface AcpResponse {
|
|
63
|
+
readonly id: number;
|
|
64
|
+
readonly result?: unknown;
|
|
65
|
+
readonly error?: {
|
|
66
|
+
readonly code: number;
|
|
67
|
+
readonly message: string;
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
/** A JSON-RPC request/notification frame on the wire. */
|
|
71
|
+
export interface AcpFrame {
|
|
72
|
+
readonly jsonrpc: '2.0';
|
|
73
|
+
readonly id?: number;
|
|
74
|
+
readonly method?: string;
|
|
75
|
+
readonly params?: unknown;
|
|
76
|
+
readonly result?: unknown;
|
|
77
|
+
readonly error?: {
|
|
78
|
+
readonly code: number;
|
|
79
|
+
readonly message: string;
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
/** Guards toolkit: is the frame a `session/update` notification carrying an update. */
|
|
83
|
+
export declare function isUpdateFrame(frame: AcpFrame): frame is AcpFrame & {
|
|
84
|
+
readonly method: 'session/update';
|
|
85
|
+
readonly params: {
|
|
86
|
+
readonly sessionId: string;
|
|
87
|
+
readonly update: AcpUpdate;
|
|
88
|
+
};
|
|
89
|
+
};
|
|
90
|
+
/** Guards toolkit: is the frame a reverse-RPC request needing an approval answer. */
|
|
91
|
+
export declare function isPermissionRequestFrame(frame: AcpFrame): frame is AcpFrame & {
|
|
92
|
+
readonly id: number;
|
|
93
|
+
readonly method: 'session/request_permission';
|
|
94
|
+
};
|
|
95
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kimi Code loop Agent: drives one session through turn and step boundaries over
|
|
3
|
+
* a persistent `kimi acp` child (Agent Client Protocol over stdio), speaking one
|
|
4
|
+
* stateless `session/new` + `session/prompt` per dsh step. The dsh session log is
|
|
5
|
+
* the sole source of model context and the prompt is a pure serialization of the
|
|
6
|
+
* durable history, so the transcript stays Model-visible ⟺ logged. Kimi owns its
|
|
7
|
+
* system prompt and tools natively; the ACP child is spawned through the dsh
|
|
8
|
+
* subprocess seam (the only available privilege boundary) and tool approvals are
|
|
9
|
+
* answered from the session's dsh approval knobs.
|
|
10
|
+
*
|
|
11
|
+
* @module dsh-loop-engine/engine-kimi/agent
|
|
12
|
+
*/
|
|
13
|
+
import type { Agent, AgentCancelCause, AgentOptions, AgentStatus, CancelOptions, InboxTarget } from '@deepseek-ai/dsh-agent';
|
|
14
|
+
import type { Scope } from '@deepseek-ai/dsh-scope';
|
|
15
|
+
import type { Session, SessionId, UserMessage } from '@deepseek-ai/dsh-session';
|
|
16
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
17
|
+
import type { ResolvedConfig } from './types.ts';
|
|
18
|
+
import { DriverInbox } from '../driver-core/inbox.ts';
|
|
19
|
+
import type { KimiSpawnCapability } from './process.ts';
|
|
20
|
+
/** Provider route label used for logged header snapshots and message provenance. */
|
|
21
|
+
export declare const PROVIDER = "kimi";
|
|
22
|
+
/** Drives one session through turn and step boundaries on Kimi Code. */
|
|
23
|
+
export declare class KimiAgent implements Agent {
|
|
24
|
+
private loopCtx;
|
|
25
|
+
readonly id: SessionId;
|
|
26
|
+
readonly options: AgentOptions;
|
|
27
|
+
readonly session: Session;
|
|
28
|
+
private readonly config;
|
|
29
|
+
private readonly spawn;
|
|
30
|
+
private readonly bin;
|
|
31
|
+
readonly inbox: DriverInbox;
|
|
32
|
+
private phase;
|
|
33
|
+
private activityDone;
|
|
34
|
+
/** The agent-scoped registration boundary; the lifecycle owner unwinds it after the driver exits. */
|
|
35
|
+
readonly scope: Scope;
|
|
36
|
+
readonly ctx: Context;
|
|
37
|
+
/** Fused dispatcher, built once in the constructor so hot-path dispatches never allocate. */
|
|
38
|
+
private readonly dispatch;
|
|
39
|
+
/** Whether this loop instance has appended its initial/resume request anchor. */
|
|
40
|
+
private requestHeaderLogged;
|
|
41
|
+
/** Agent-lifecycle-local counter naming each streamed attempt. */
|
|
42
|
+
private streamAttempts;
|
|
43
|
+
/** Lazily created ACP client, reused across steps and released on scope teardown. */
|
|
44
|
+
private acp;
|
|
45
|
+
/** The spawn spec the cached client was built from; a change forces a respawn. */
|
|
46
|
+
private lastSpec;
|
|
47
|
+
constructor(loopCtx: Context, id: SessionId, options: AgentOptions, session: Session, config: ResolvedConfig, spawn: KimiSpawnCapability, bin: string);
|
|
48
|
+
get status(): AgentStatus;
|
|
49
|
+
/** Commit a phase and publish its externally visible status transition. */
|
|
50
|
+
private setPhase;
|
|
51
|
+
send(message: UserMessage, target: InboxTarget, wakeup: boolean): void;
|
|
52
|
+
/**
|
|
53
|
+
* Queue a message for the next turn and wake the driver.
|
|
54
|
+
* @param input - the user message to deliver.
|
|
55
|
+
*/
|
|
56
|
+
followup(input: UserMessage): void;
|
|
57
|
+
/**
|
|
58
|
+
* Queue a message for the running step and wake the driver.
|
|
59
|
+
* @param input - the user message to deliver.
|
|
60
|
+
*/
|
|
61
|
+
steer(input: UserMessage): void;
|
|
62
|
+
/**
|
|
63
|
+
* Queue a message for the running step without waking the driver.
|
|
64
|
+
* @param input - the user message to deliver.
|
|
65
|
+
*/
|
|
66
|
+
inject(input: UserMessage): void;
|
|
67
|
+
cancel(cause: AgentCancelCause, options?: CancelOptions): void;
|
|
68
|
+
/**
|
|
69
|
+
* Run a maintenance job while the agent is idle.
|
|
70
|
+
* @param job - the maintenance operation, receiving the phase abort signal.
|
|
71
|
+
* @returns the maintenance result.
|
|
72
|
+
*/
|
|
73
|
+
runMaintenance<T>(job: (signal: AbortSignal) => Promise<T>): Promise<T>;
|
|
74
|
+
/**
|
|
75
|
+
* Start one driver, or latch its wake behind maintenance or an aborted
|
|
76
|
+
* activity. A wake sent while idle always opens its turn boundary, even
|
|
77
|
+
* when its message was cleared; only a latched replay is suppressed when
|
|
78
|
+
* the queue no longer holds the wake.
|
|
79
|
+
* @param wakeAfterAbort - the {@link send} classification, captured before
|
|
80
|
+
* the inbox insertion so a reentrant cancel cannot reclassify it.
|
|
81
|
+
*/
|
|
82
|
+
private wakeDriver;
|
|
83
|
+
whenIdle(): Promise<void>;
|
|
84
|
+
/** Report one failure at its live boundary, then preserve it for driver containment. */
|
|
85
|
+
private throwError;
|
|
86
|
+
private kick;
|
|
87
|
+
private preStep;
|
|
88
|
+
/**
|
|
89
|
+
* Scan the step's user messages for `/name` skill gestures, load each
|
|
90
|
+
* matching skill, and inject the rendered skill content into the message
|
|
91
|
+
* batch. This mirrors what dsh-tool-skill does for the in-process engine.
|
|
92
|
+
* @param messages - the current step's message batch.
|
|
93
|
+
* @param signal - cancellation signal (aborted loads are silently dropped).
|
|
94
|
+
* @returns the original batch when no skill was invoked, or an extended
|
|
95
|
+
* batch with injected skill-content messages appended.
|
|
96
|
+
*/
|
|
97
|
+
private injectSkills;
|
|
98
|
+
/** Open one turn before claiming its first proposed step. */
|
|
99
|
+
private turn;
|
|
100
|
+
/** Model label recorded in the request header for one lifecycle. */
|
|
101
|
+
private modelLabel;
|
|
102
|
+
/** Append the request header snapshot once per loop instance. */
|
|
103
|
+
private assertRequestHeader;
|
|
104
|
+
/** Whether two spawn specs describe the same `kimi acp` child. */
|
|
105
|
+
private specsEqual;
|
|
106
|
+
/** Return the cached ACP client, respawning when the spec or process changed. */
|
|
107
|
+
private acpClient;
|
|
108
|
+
/** Build the `kimi acp` argv/cwd/env for the persistent child. */
|
|
109
|
+
private spawnSpec;
|
|
110
|
+
/** Run one `kimi acp` step for the current session history and map the streamed updates. */
|
|
111
|
+
private step;
|
|
112
|
+
/** Per-step accumulation state for streamed assistant blocks and tool calls. */
|
|
113
|
+
private blocks;
|
|
114
|
+
private emittedToolCalls;
|
|
115
|
+
private toolText;
|
|
116
|
+
private blockRef;
|
|
117
|
+
private ensureBlock;
|
|
118
|
+
/** Apply one streamed update to the current step's blocks and stream. */
|
|
119
|
+
private applyUpdate;
|
|
120
|
+
/**
|
|
121
|
+
* Flush the accumulated assistant blocks into one durable assistant/message
|
|
122
|
+
* carrying the exact stream the attempt published live.
|
|
123
|
+
* @param turn - durable turn owning the message.
|
|
124
|
+
* @param step - durable step owning the message.
|
|
125
|
+
* @param currentStream - the step's live attempt accessor; a step whose
|
|
126
|
+
* blocks streamed always has one open, while a tool-only step has none.
|
|
127
|
+
*/
|
|
128
|
+
private flushAssistant;
|
|
129
|
+
}
|
|
130
|
+
//# sourceMappingURL=agent.d.ts.map
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kimi Code slash-command bridge.
|
|
3
|
+
*
|
|
4
|
+
* The dsh `commands` runtime executes a registered command locally — the line is
|
|
5
|
+
* consumed and never reaches the model — so a command whose real processing
|
|
6
|
+
* lives inside the Kimi engine must forward the raw line back to the agent, which
|
|
7
|
+
* Kimi then expands (as far as the ACP prompt surface supports). Registering the
|
|
8
|
+
* built-ins keeps them visible in the dsh web slash menu; unregistered `/lines`
|
|
9
|
+
* pass through as user text, but the menu would hide the engine's command
|
|
10
|
+
* surface.
|
|
11
|
+
*
|
|
12
|
+
* Kimi's slash commands are chiefly TUI controls (`/login`, `/provider`,
|
|
13
|
+
* `/settings`, `/sessions`, `/tasks`, …) that the ACP prompt surface does not
|
|
14
|
+
* expand the way an interactive TUI does; this bridge therefore registers the
|
|
15
|
+
* subset that are meaningful to forward to the engine (session/mode/status and
|
|
16
|
+
* the goal form). `skill:` commands are already carried by the dsh skill
|
|
17
|
+
* injection seam and Kimi's own shorthand, so they are not duplicated here.
|
|
18
|
+
*
|
|
19
|
+
* `model` is deliberately absent even though the CLI has it: the dsh web
|
|
20
|
+
* client owns a `/model` contribution, and a same-named host command makes
|
|
21
|
+
* `ui-commands` throw the whole command menu source away, leaving only the
|
|
22
|
+
* skill group. `/goal` stays: the managed block disables dsh's `command-goal`
|
|
23
|
+
* row for hosted engines, so the slot is free and the CLI's own goal mode
|
|
24
|
+
* takes it over.
|
|
25
|
+
*
|
|
26
|
+
* @module dsh-loop-engine/engine-kimi/commands
|
|
27
|
+
*/
|
|
28
|
+
import type { CommandDefinition, CommandInvocation, CommandResult } from '../commands.ts';
|
|
29
|
+
/**
|
|
30
|
+
* Build the forwarding handler for one Kimi slash command: it re-delivers the
|
|
31
|
+
* full `/<name> [args]` line to the receiving agent as a plain user message,
|
|
32
|
+
* where the engine expands it. `rawInput` already carries the separator
|
|
33
|
+
* whitespace and any arguments.
|
|
34
|
+
* @param name - the command name without the leading slash.
|
|
35
|
+
* @returns the command handler.
|
|
36
|
+
*/
|
|
37
|
+
export declare function forwardKimiCommand(name: string): (invocation: CommandInvocation) => CommandResult;
|
|
38
|
+
/** Kimi Code's built-in slash commands that make sense to forward to the engine. */
|
|
39
|
+
export declare const KIMI_COMMANDS: readonly CommandDefinition[];
|
|
40
|
+
//# sourceMappingURL=commands.d.ts.map
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kimi Code loop engine module: hosts the AgentFactory that drives every
|
|
3
|
+
* session through a persistent `kimi acp` child (Agent Client Protocol over
|
|
4
|
+
* stdio), speaking one stateless `session/new` + `session/prompt` per dsh step,
|
|
5
|
+
* with the durable session log as the sole source of model context.
|
|
6
|
+
* dsh-loop-engine constructs this factory when the Kimi engine is selected; this
|
|
7
|
+
* module is a library, not a Cordis plugin entry. Kimi has no host approval
|
|
8
|
+
* callback, so tool approvals surfaced by ACP (`session/request_permission`) are
|
|
9
|
+
* answered from the session's dsh approval knobs (an `ask` policy degrades to
|
|
10
|
+
* denial); the whole child is spawned through the dsh subprocess seam — the only
|
|
11
|
+
* available privilege boundary — and the sandbox stance follows the session's
|
|
12
|
+
* durable permission knobs as the subprocess provider resolves them (default
|
|
13
|
+
* read-only).
|
|
14
|
+
*
|
|
15
|
+
* @module dsh-loop-engine/engine-kimi
|
|
16
|
+
*/
|
|
17
|
+
import { Service } from '@deepseek-ai/cordis';
|
|
18
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
19
|
+
import z from '@deepseek-ai/schemastery';
|
|
20
|
+
import type { AgentFactory, AgentHandle, CreateAgentOptions, ResumeAgentOptions } from '@deepseek-ai/dsh-agent';
|
|
21
|
+
import type { KimiSpawnCapability } from './process.ts';
|
|
22
|
+
import type { ResolvedConfig } from './types.ts';
|
|
23
|
+
/** Grace in milliseconds for Kimi process-tree termination. */
|
|
24
|
+
export declare const KIMI_DISPOSE_GRACE_MS = 3000;
|
|
25
|
+
/** Deployment-owned configuration for the Kimi loop plugin. */
|
|
26
|
+
export interface Config {
|
|
27
|
+
/** Model alias for the `kimi` child (`-m`); Kimi native config owns the model when omitted. */
|
|
28
|
+
model?: string;
|
|
29
|
+
/** Explicit environment entries passed to the `kimi` child. */
|
|
30
|
+
env?: Record<string, string>;
|
|
31
|
+
/** Kimi CLI executable; `'kimi'` resolves through PATH when not pinned to an absolute path. */
|
|
32
|
+
bin?: string;
|
|
33
|
+
}
|
|
34
|
+
/** Schema of the Kimi loop plugin configuration. */
|
|
35
|
+
export declare const Config: z<Config>;
|
|
36
|
+
/** Host-face ctx key for the Kimi loop service. */
|
|
37
|
+
declare module '@deepseek-ai/cordis' {
|
|
38
|
+
interface Context {
|
|
39
|
+
agentLoopKimi: KimiLoop;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Concrete AgentFactory and driver service of the Kimi loop. Creation and
|
|
44
|
+
* resume follow the registry factory contract and the shared publication
|
|
45
|
+
* transaction: prepare, run setup, then publish through both registries,
|
|
46
|
+
* announce, and emit `agent/session-start`.
|
|
47
|
+
*/
|
|
48
|
+
export declare class KimiLoop extends Service implements AgentFactory {
|
|
49
|
+
/** Services the loop resolves through its own fiber; blessed identically to the package-level entry inject. */
|
|
50
|
+
static inject: string[];
|
|
51
|
+
/** Validated configuration owned by the loop plugin. */
|
|
52
|
+
readonly config: ResolvedConfig;
|
|
53
|
+
private readonly ownership;
|
|
54
|
+
/** Plain holder prevents Cordis from re-tracing the factory's dependency context through a caller shadow. */
|
|
55
|
+
private readonly runtime;
|
|
56
|
+
/** One-shot spawn capability handed to every agent, sandboxed by the subprocess seam. */
|
|
57
|
+
readonly spawn: KimiSpawnCapability;
|
|
58
|
+
constructor(ctx: Context, config: Config);
|
|
59
|
+
/**
|
|
60
|
+
* Construct the driver, scope, and one memoized reverse teardown for a new
|
|
61
|
+
* agent. The teardown is registered with the factory and the owner fiber
|
|
62
|
+
* BEFORE publication, so a mid-setup unload rolls everything back; `signal`
|
|
63
|
+
* fuses caller cancellation with lifecycle teardown for setup awaits.
|
|
64
|
+
*/
|
|
65
|
+
private prepare;
|
|
66
|
+
/** Prepare one Agent around an acquired Session, run setup, and publish it. */
|
|
67
|
+
private setupAndPublish;
|
|
68
|
+
/**
|
|
69
|
+
* Create an agent and session under one caller-supplied identity, owned by
|
|
70
|
+
* the accessing fiber. When a persistence backend is mounted, the session's
|
|
71
|
+
* durable identity is stored before publication.
|
|
72
|
+
* @param ownerCtx - caller context that structurally owns the lifecycle.
|
|
73
|
+
* @param options - identities, optional live parent, session seed/metadata, loop options, setup, and cancellation.
|
|
74
|
+
* @returns the published handle.
|
|
75
|
+
*/
|
|
76
|
+
createAgent(ownerCtx: Context, options: CreateAgentOptions): Promise<AgentHandle>;
|
|
77
|
+
/**
|
|
78
|
+
* Take a fresh session's write ownership when persistence is mounted.
|
|
79
|
+
* Nothing is appended here: the constructor seed (which never re-emits
|
|
80
|
+
* through `session/event`) is stored by {@link appendUnstoredSuffix} at the
|
|
81
|
+
* publication commit point, so a failed or cancelled setup closes an
|
|
82
|
+
* unmaterialized handle and leaves no stored residue — the same id can be
|
|
83
|
+
* created again.
|
|
84
|
+
* @param session - the unpublished session to store.
|
|
85
|
+
* @param signal - optional cancellation forwarded to the backend create.
|
|
86
|
+
* @returns the owned handle and stored cursor, or `undefined` without a backend.
|
|
87
|
+
*/
|
|
88
|
+
private createStoredSession;
|
|
89
|
+
/**
|
|
90
|
+
* Durably store the session events appended since the last stored cursor.
|
|
91
|
+
* Pre-publication appends (constructor seed markers, setup-window events)
|
|
92
|
+
* never re-emit through `session/event`, so publication must flush them
|
|
93
|
+
* through the handle before live events start routing into it.
|
|
94
|
+
* @param stored - the session's owned handle and stored cursor, if any.
|
|
95
|
+
* @param session - the unpublished session whose suffix is stored.
|
|
96
|
+
*/
|
|
97
|
+
private appendUnstoredSuffix;
|
|
98
|
+
/**
|
|
99
|
+
* Resume an owned agent from the configured persistence service.
|
|
100
|
+
* @param ownerCtx - caller context that owns load, setup, and the live lifecycle.
|
|
101
|
+
* @param options - persisted identity, optional live parent, loop options, setup, and cancellation.
|
|
102
|
+
* @returns the published handle.
|
|
103
|
+
*/
|
|
104
|
+
resume(ownerCtx: Context, options: ResumeAgentOptions): Promise<AgentHandle>;
|
|
105
|
+
/** Resume through an explicit persistence service. */
|
|
106
|
+
private resumeWith;
|
|
107
|
+
}
|
|
108
|
+
//# sourceMappingURL=loop.d.ts.map
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mapping from Kimi Code `--output-format stream-json` records to dsh
|
|
3
|
+
* session-log events. Kimi emits one JSON object per stdout line, discriminated
|
|
4
|
+
* by a `role` field (verified against the 0.28.1 CLI):
|
|
5
|
+
* - `{ "role": "assistant", "content": "<text>" }`
|
|
6
|
+
* - `{ "role": "assistant", "tool_calls": [{ "type": "function",
|
|
7
|
+
* "id": "call_…", "function": { "name": "Bash", "arguments": "<json>" } }] }`
|
|
8
|
+
* - `{ "role": "tool", "tool_call_id": "call_…", "content": "<text>" }`
|
|
9
|
+
* - `{ "role": "meta", "type": "session.resume_hint", … }` (discarded)
|
|
10
|
+
* Thinking and tool progress go to stderr, never the JSONL, so this module has no
|
|
11
|
+
* usage or reasoning projection. The module is pure: it parses a raw stdout
|
|
12
|
+
* buffer and returns plain record projections the agent folds into the log.
|
|
13
|
+
*
|
|
14
|
+
* @module dsh-loop-engine/engine-kimi/mapping
|
|
15
|
+
*/
|
|
16
|
+
import type { ToolResultMessage } from '@deepseek-ai/dsh-llm';
|
|
17
|
+
/** One normalized Kimi tool-call record. */
|
|
18
|
+
export interface KimiToolCall {
|
|
19
|
+
/** The tool invocation id (`call_…`), correlated with the later `tool` record. */
|
|
20
|
+
readonly id: string;
|
|
21
|
+
/** The tool name. */
|
|
22
|
+
readonly name: string;
|
|
23
|
+
/** The raw arguments JSON string, passed verbatim. */
|
|
24
|
+
readonly arguments: string;
|
|
25
|
+
}
|
|
26
|
+
/** One parsed Kimi stream-json record (partial, tolerant of unknown fields). */
|
|
27
|
+
export interface KimiRecord {
|
|
28
|
+
readonly role?: unknown;
|
|
29
|
+
readonly type?: unknown;
|
|
30
|
+
readonly content?: unknown;
|
|
31
|
+
readonly tool_calls?: unknown;
|
|
32
|
+
readonly tool_call_id?: unknown;
|
|
33
|
+
readonly is_error?: unknown;
|
|
34
|
+
readonly isError?: unknown;
|
|
35
|
+
readonly error?: unknown;
|
|
36
|
+
}
|
|
37
|
+
/** Record role discriminant used by the step loop. */
|
|
38
|
+
export type KimiRecordRole = 'assistant' | 'tool' | 'meta' | 'unknown';
|
|
39
|
+
/**
|
|
40
|
+
* Parse a `kimi --output-format stream-json` stdout buffer into records.
|
|
41
|
+
* Lines are bare-`\n` delimited; blank lines and non-JSON lines are dropped.
|
|
42
|
+
* @param stdout - the child's full stdout text.
|
|
43
|
+
* @returns the parsed records, in order.
|
|
44
|
+
*/
|
|
45
|
+
export declare function parseKimiRecords(stdout: string): KimiRecord[];
|
|
46
|
+
/** Classify one record's role. */
|
|
47
|
+
export declare function roleOf(record: KimiRecord): KimiRecordRole;
|
|
48
|
+
/**
|
|
49
|
+
* The joined text of one record's `content` field. Handles both the string
|
|
50
|
+
* form (`"content": "<text>"`) and an array form (`[{ "type": "text", "text": … }]`)
|
|
51
|
+
* defensively; any other shape renders empty.
|
|
52
|
+
* @param record - the parsed record.
|
|
53
|
+
* @returns the joined body text.
|
|
54
|
+
*/
|
|
55
|
+
export declare function recordContentText(record: KimiRecord): string;
|
|
56
|
+
/**
|
|
57
|
+
* Normalize an assistant record's `tool_calls` array. Each entry keeps its id,
|
|
58
|
+
* name, and a verbatim argument string (the wire already serializes arguments
|
|
59
|
+
* as JSON text; absent args render `{}`).
|
|
60
|
+
* @param record - the parsed assistant record.
|
|
61
|
+
* @returns the normalized tool calls, in order.
|
|
62
|
+
*/
|
|
63
|
+
export declare function assistantToolCalls(record: KimiRecord): readonly KimiToolCall[];
|
|
64
|
+
/**
|
|
65
|
+
* Surface a `tool` record as a durable tool-result message, or `undefined` when
|
|
66
|
+
* the record is not a correlatable tool result (missing `role: tool` or no id).
|
|
67
|
+
* @param record - the parsed record.
|
|
68
|
+
* @returns the dsh tool-result message, or `undefined`.
|
|
69
|
+
*/
|
|
70
|
+
export declare function toolResultMessage(record: KimiRecord): ToolResultMessage | undefined;
|
|
71
|
+
//# sourceMappingURL=mapping.d.ts.map
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool-approval mapping for the `kimi acp` driver.
|
|
3
|
+
*
|
|
4
|
+
* Kimi's ACP adapter surfaces tool/permission decisions as the reverse-RPC
|
|
5
|
+
* `session/request_permission`, which the client must answer. The dsh harness
|
|
6
|
+
* has no interactive approval callback in the unattended runtime, so the fold
|
|
7
|
+
* mirrors the codex/Pi bridges: an `ask` approval policy degrades to a denial
|
|
8
|
+
* (the only safe answer when no human is present), and anything else
|
|
9
|
+
* (`never`, or knob-less) auto-approves the tool. A full-access sandbox mode
|
|
10
|
+
* also auto-approves; a `workspace-write` stance is still auto-approved here
|
|
11
|
+
* because Kimi's own permission gating (`--permission`/session policy) is what
|
|
12
|
+
* bounds the tool — the ACP approval is the host's gate, and the dsh
|
|
13
|
+
* `approval/policy` knob is its signal.
|
|
14
|
+
*
|
|
15
|
+
* @module dsh-loop-engine/engine-kimi/permission
|
|
16
|
+
*/
|
|
17
|
+
import type { PermissionEvent } from '../driver-core/permission-knobs.ts';
|
|
18
|
+
/**
|
|
19
|
+
* Whether the driver should answer `session/request_permission` with approval for
|
|
20
|
+
* one query. An `ask` policy denies (fail-closed — no human is present in the
|
|
21
|
+
* unattended runtime); anything else (`never`, or knob-less) auto-approves. The
|
|
22
|
+
* sandbox stance is not consulted: Kimi's own tool policy bounds what a tool does,
|
|
23
|
+
* and the ACP approval is the host's gate, signalled by `approval/policy`.
|
|
24
|
+
* @param events - the durable session log.
|
|
25
|
+
* @returns whether tool requests are approved.
|
|
26
|
+
*/
|
|
27
|
+
export declare function resolveToolApproval(events: readonly PermissionEvent[]): boolean;
|
|
28
|
+
//# sourceMappingURL=permission.d.ts.map
|