dsh-loop-engine 0.1.5-rc1 → 0.1.5-rc2
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 +88 -5
- package/README.zh.md +74 -0
- package/lib/client.js +158 -0
- package/lib/index.js +1020 -1486
- package/lib/invariant.js +6 -3
- package/lib/types/client/turn-status.d.ts +43 -0
- package/lib/types/driver-core/agents-md-skill-provider.d.ts +72 -0
- package/lib/types/driver-core/hosted-loop-factory.d.ts +119 -0
- package/lib/types/driver-core/ownership.d.ts +6 -6
- package/lib/types/driver-core/permission-knobs.d.ts +1 -1
- package/lib/types/driver-core/prompt.d.ts +1 -1
- package/lib/types/driver-core/skill-inject.d.ts +2 -2
- package/lib/types/engine-claude/agent.d.ts +22 -0
- package/lib/types/engine-claude/loop.d.ts +7 -56
- package/lib/types/engine-claude/mapping.d.ts +28 -3
- package/lib/types/engine-codex/agent.d.ts +46 -3
- package/lib/types/engine-codex/appserver/client.d.ts +16 -0
- package/lib/types/engine-codex/loop.d.ts +7 -56
- package/lib/types/engine-codex/permission.d.ts +100 -6
- package/lib/types/engine-codex/skills.d.ts +7 -8
- package/lib/types/engine-kimi/acp/client.d.ts +33 -3
- package/lib/types/engine-kimi/acp/mapping.d.ts +36 -7
- package/lib/types/engine-kimi/acp/types.d.ts +41 -2
- package/lib/types/engine-kimi/agent.d.ts +65 -8
- package/lib/types/engine-kimi/loop.d.ts +7 -56
- package/lib/types/engine-kimi/skills.d.ts +9 -14
- package/lib/types/engine-pi/agent.d.ts +23 -4
- package/lib/types/engine-pi/loop.d.ts +7 -56
- package/lib/types/engine-pi/permission.d.ts +16 -12
- package/lib/types/engine-pi/skills.d.ts +6 -14
- package/lib/types/patch-manager.d.ts +7 -0
- package/lib/types/provider-route.d.ts +16 -7
- package/lib/types/settings.d.ts +4 -1
- package/lib/types/skills.d.ts +6 -14
- package/package.json +1 -1
|
@@ -9,11 +9,13 @@
|
|
|
9
9
|
*
|
|
10
10
|
* @module dsh-loop-engine/engine-codex
|
|
11
11
|
*/
|
|
12
|
-
import { Service } from '@deepseek-ai/cordis';
|
|
13
12
|
import type { Context } from '@deepseek-ai/cordis';
|
|
14
13
|
import z from '@deepseek-ai/schemastery';
|
|
15
|
-
import type {
|
|
14
|
+
import type { AgentOptions } from '@deepseek-ai/dsh-agent';
|
|
15
|
+
import type { Session, SessionId } from '@deepseek-ai/dsh-session';
|
|
16
|
+
import { CodexAgent } from './agent.ts';
|
|
16
17
|
import type { CodexApprovalPolicy, CodexSandboxMode, ResolvedConfig } from './types.ts';
|
|
18
|
+
import { HostedLoopFactory } from '../driver-core/hosted-loop-factory.ts';
|
|
17
19
|
/** Codex CLI sandbox modes a deployment may pin. */
|
|
18
20
|
export declare const CODEX_SANDBOX_MODES: readonly CodexSandboxMode[];
|
|
19
21
|
/** Codex CLI approval policies a deployment may pin. */
|
|
@@ -53,62 +55,11 @@ declare module '@deepseek-ai/cordis' {
|
|
|
53
55
|
* transaction: prepare, run setup, then publish through both registries,
|
|
54
56
|
* announce, and emit `agent/session-start`.
|
|
55
57
|
*/
|
|
56
|
-
export declare class CodexLoop extends
|
|
58
|
+
export declare class CodexLoop extends HostedLoopFactory<ResolvedConfig, CodexAgent> {
|
|
57
59
|
/** Services the loop resolves through its own fiber; blessed identically to the package-level entry inject. */
|
|
58
60
|
static inject: string[];
|
|
59
|
-
/** Validated configuration owned by the loop plugin. */
|
|
60
|
-
readonly config: ResolvedConfig;
|
|
61
|
-
private readonly ownership;
|
|
62
|
-
/** Plain holder prevents Cordis from re-tracing the factory's dependency context through a caller shadow. */
|
|
63
|
-
private readonly runtime;
|
|
64
61
|
constructor(ctx: Context, config: Config);
|
|
65
|
-
/**
|
|
66
|
-
|
|
67
|
-
* agent. The teardown is registered with the factory and the owner fiber
|
|
68
|
-
* BEFORE publication, so a mid-setup unload rolls everything back; `signal`
|
|
69
|
-
* fuses caller cancellation with lifecycle teardown for setup awaits.
|
|
70
|
-
*/
|
|
71
|
-
private prepare;
|
|
72
|
-
/** Prepare one Agent around an acquired Session, run setup, and publish it. */
|
|
73
|
-
private setupAndPublish;
|
|
74
|
-
/**
|
|
75
|
-
* Create an agent and session under one caller-supplied identity, owned by
|
|
76
|
-
* the accessing fiber. When a persistence backend is mounted, the session's
|
|
77
|
-
* durable identity is stored before publication.
|
|
78
|
-
* @param ownerCtx - caller context that structurally owns the lifecycle.
|
|
79
|
-
* @param options - identities, optional live parent, session seed/metadata, loop options, setup, and cancellation.
|
|
80
|
-
* @returns the published handle.
|
|
81
|
-
*/
|
|
82
|
-
createAgent(ownerCtx: Context, options: CreateAgentOptions): Promise<AgentHandle>;
|
|
83
|
-
/**
|
|
84
|
-
* Take a fresh session's write ownership when persistence is mounted.
|
|
85
|
-
* Nothing is appended here: the constructor seed (which never re-emits
|
|
86
|
-
* through `session/event`) is stored by {@link appendUnstoredSuffix} at the
|
|
87
|
-
* publication commit point, so a failed or cancelled setup closes an
|
|
88
|
-
* unmaterialized handle and leaves no stored residue — the same id can be
|
|
89
|
-
* created again.
|
|
90
|
-
* @param session - the unpublished session to store.
|
|
91
|
-
* @param signal - optional cancellation forwarded to the backend create.
|
|
92
|
-
* @returns the owned handle and stored cursor, or `undefined` without a backend.
|
|
93
|
-
*/
|
|
94
|
-
private createStoredSession;
|
|
95
|
-
/**
|
|
96
|
-
* Durably store the session events appended since the last stored cursor.
|
|
97
|
-
* Pre-publication appends (constructor seed markers, setup-window events)
|
|
98
|
-
* never re-emit through `session/event`, so publication must flush them
|
|
99
|
-
* through the handle before live events start routing into it.
|
|
100
|
-
* @param stored - the session's owned handle and stored cursor, if any.
|
|
101
|
-
* @param session - the unpublished session whose suffix is stored.
|
|
102
|
-
*/
|
|
103
|
-
private appendUnstoredSuffix;
|
|
104
|
-
/**
|
|
105
|
-
* Resume an owned agent from the configured persistence service.
|
|
106
|
-
* @param ownerCtx - caller context that owns load, setup, and the live lifecycle.
|
|
107
|
-
* @param options - persisted identity, optional live parent, loop options, setup, and cancellation.
|
|
108
|
-
* @returns the published handle.
|
|
109
|
-
*/
|
|
110
|
-
resume(ownerCtx: Context, options: ResumeAgentOptions): Promise<AgentHandle>;
|
|
111
|
-
/** Resume through an explicit persistence service. */
|
|
112
|
-
private resumeWith;
|
|
62
|
+
/** Construct the Codex driver for one prepared session. */
|
|
63
|
+
protected buildAgent(loopCtx: Context, id: SessionId, options: AgentOptions, session: Session): CodexAgent;
|
|
113
64
|
}
|
|
114
65
|
//# sourceMappingURL=loop.d.ts.map
|
|
@@ -1,14 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Mapping from the dsh session's durable permission knobs to one Codex query's
|
|
3
|
-
* declarative permission stance
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* `
|
|
3
|
+
* declarative permission stance, plus the replies to every server-initiated
|
|
4
|
+
* interaction Codex can raise. Codex surfaces interactive approval as
|
|
5
|
+
* server-initiated JSON-RPC requests answered through the dsh approval seam
|
|
6
|
+
* (see `approvalReason` / `resolveApprovalRequest` below), while the thread
|
|
7
|
+
* still starts with the `sandboxMode` + `approvalPolicy` pair chosen at
|
|
8
|
+
* creation. The fold maps the session's `sandbox/mode` and `approval/policy`
|
|
9
|
+
* events directly, mirroring the web surface's presets:
|
|
7
10
|
* - full access → `danger-full-access` + `never` (no native checks at all),
|
|
8
|
-
* - an `ask` policy → `workspace-write` + `on-request` (
|
|
9
|
-
*
|
|
11
|
+
* - an `ask` policy → `workspace-write` + `on-request` (approvals are then
|
|
12
|
+
* routed to the dsh approval seam, failing closed when it is absent),
|
|
10
13
|
* - anything else fails closed → `read-only` + `never`.
|
|
11
14
|
*
|
|
15
|
+
* The other two interactions (`item/tool/requestUserInput` and
|
|
16
|
+
* `mcpServer/elicitation/request`) have no approval to grant: one asks the human
|
|
17
|
+
* a question, the other asks for MCP input. Both are mapped here so the driver
|
|
18
|
+
* answers them in-vocabulary instead of failing them with a protocol error that
|
|
19
|
+
* the model reads as a broken tool.
|
|
20
|
+
*
|
|
12
21
|
* @module dsh-loop-engine/engine-codex/permission
|
|
13
22
|
*/
|
|
14
23
|
import type { PermissionEvent } from '../driver-core/permission-knobs.ts';
|
|
@@ -20,6 +29,69 @@ export interface CodexPermission {
|
|
|
20
29
|
}
|
|
21
30
|
/** Conservative unattended default: read-only sandbox, never ask. */
|
|
22
31
|
export declare const DEFAULT_CODEX_PERMISSION: CodexPermission;
|
|
32
|
+
/**
|
|
33
|
+
* One question to put to the human, in the shape the dsh user-questions seam
|
|
34
|
+
* accepts. Declared inline (a structural subset) to avoid a peer dep on
|
|
35
|
+
* `@deepseek-ai/dsh-user-questions`, matching the approval seam's treatment.
|
|
36
|
+
*/
|
|
37
|
+
export interface UserQuestionItem {
|
|
38
|
+
/** Stable question id, echoed back by the answer and by our reply. */
|
|
39
|
+
readonly id: string;
|
|
40
|
+
/** The question to display. */
|
|
41
|
+
readonly question: string;
|
|
42
|
+
/** Optional short heading/group label. */
|
|
43
|
+
readonly header?: string;
|
|
44
|
+
/** Optional choices a UI renders as a menu. */
|
|
45
|
+
readonly options?: readonly {
|
|
46
|
+
readonly label: string;
|
|
47
|
+
readonly description?: string;
|
|
48
|
+
}[];
|
|
49
|
+
}
|
|
50
|
+
/** The human's answer, in the shape the dsh user-questions seam returns. */
|
|
51
|
+
export interface UserQuestionAnswer {
|
|
52
|
+
/** One entry per answered question; unanswered questions are simply absent. */
|
|
53
|
+
readonly answers: readonly {
|
|
54
|
+
readonly id: string;
|
|
55
|
+
readonly selected: readonly string[];
|
|
56
|
+
/** Optional free-text answer, carried alongside `selected`. */
|
|
57
|
+
readonly custom?: string;
|
|
58
|
+
}[];
|
|
59
|
+
}
|
|
60
|
+
/** The `item/tool/requestUserInput` result Codex expects. */
|
|
61
|
+
export interface CodexUserInputResponse {
|
|
62
|
+
/** Answer lists keyed by question id; an empty map means "no answers given". */
|
|
63
|
+
readonly answers: Record<string, {
|
|
64
|
+
readonly answers: string[];
|
|
65
|
+
}>;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Read the answerable questions of one `item/tool/requestUserInput` request. The
|
|
69
|
+
* wire is untrusted, so an entry with no usable id or wording is dropped: the
|
|
70
|
+
* reply is keyed by question id, and a synthesized key would answer a question
|
|
71
|
+
* Codex never asked.
|
|
72
|
+
* @param params - the request params.
|
|
73
|
+
* @returns the questions to put to the human, possibly none.
|
|
74
|
+
*/
|
|
75
|
+
export declare function userInputQuestions(params: unknown): UserQuestionItem[];
|
|
76
|
+
/**
|
|
77
|
+
* Project one human answer onto Codex's response shape. A free-text answer rides
|
|
78
|
+
* along as a further entry of the question's answer list, and no answer at all
|
|
79
|
+
* (no seam, or the human dismissed it) becomes an empty map — the honest "asked,
|
|
80
|
+
* answered nothing", never an invented answer.
|
|
81
|
+
* @param answer - the seam's answer, or undefined when none was obtained.
|
|
82
|
+
* @returns the response payload.
|
|
83
|
+
*/
|
|
84
|
+
export declare function userInputResponse(answer: UserQuestionAnswer | undefined): CodexUserInputResponse;
|
|
85
|
+
/**
|
|
86
|
+
* The reply to one `mcpServer/elicitation/request`. An unattended driver cannot
|
|
87
|
+
* render an elicitation form, so it declines — the same stance the Claude Code
|
|
88
|
+
* driver takes for the equivalent callback, and the one answer that never
|
|
89
|
+
* fabricates input the user did not give.
|
|
90
|
+
* @returns the response payload.
|
|
91
|
+
*/
|
|
92
|
+
export declare function elicitationResponse(): {
|
|
93
|
+
action: 'decline';
|
|
94
|
+
};
|
|
23
95
|
/**
|
|
24
96
|
* Resolve the session's effective Codex permission stance. Full access wins
|
|
25
97
|
* outright; otherwise an `ask` policy maps to the CLI's on-request approval
|
|
@@ -29,4 +101,26 @@ export declare const DEFAULT_CODEX_PERMISSION: CodexPermission;
|
|
|
29
101
|
* @returns the stance one query should run under.
|
|
30
102
|
*/
|
|
31
103
|
export declare function resolveSessionPermission(events: readonly PermissionEvent[]): CodexPermission;
|
|
104
|
+
/** The closed outcome of one dsh approval request (mirrors the dsh-user-approval seam). */
|
|
105
|
+
export type ApprovalOutcome = 'allowed-once' | 'rejected' | 'cancelled' | 'unavailable';
|
|
106
|
+
/** Short tool identity for the dsh approval request carrying one native Codex approval. */
|
|
107
|
+
export declare function approvalToolName(method: string): string;
|
|
108
|
+
/** Human-readable reason for one native Codex approval request. */
|
|
109
|
+
export declare function approvalReason(method: string, params: unknown): string;
|
|
110
|
+
/** The native decision a command/file-change approval resolves to. */
|
|
111
|
+
export declare function approvalDecision(outcome: ApprovalOutcome): 'accept' | 'decline';
|
|
112
|
+
/** The native permissions-request response for one approval outcome. */
|
|
113
|
+
export declare function permissionsGrant(outcome: ApprovalOutcome, requested: unknown): {
|
|
114
|
+
permissions: unknown;
|
|
115
|
+
scope: 'turn';
|
|
116
|
+
};
|
|
117
|
+
/** Build the JSON-RPC reply payload for one inbound Codex approval request. */
|
|
118
|
+
export declare function resolveApprovalRequest(method: string, params: unknown, outcome: ApprovalOutcome): {
|
|
119
|
+
result: unknown;
|
|
120
|
+
} | {
|
|
121
|
+
error: {
|
|
122
|
+
code: number;
|
|
123
|
+
message: string;
|
|
124
|
+
};
|
|
125
|
+
};
|
|
32
126
|
//# sourceMappingURL=permission.d.ts.map
|
|
@@ -9,21 +9,20 @@
|
|
|
9
9
|
* contents, so the dsh skill-injection seam (`/name` gestures) can carry it
|
|
10
10
|
* into the prompt.
|
|
11
11
|
*
|
|
12
|
+
* The discovery algorithm itself lives in {@link AgentsMdSkillProvider}; this
|
|
13
|
+
* module supplies only Codex's locations and ranks — and, having no skills
|
|
14
|
+
* catalog, no `skills` entry.
|
|
15
|
+
*
|
|
12
16
|
* @module dsh-loop-engine/engine-codex/skills
|
|
13
17
|
*/
|
|
14
|
-
import
|
|
18
|
+
import { AgentsMdSkillProvider } from '../driver-core/agents-md-skill-provider.ts';
|
|
19
|
+
import type { SkillProviderControl } from '../skills.ts';
|
|
15
20
|
/**
|
|
16
21
|
* Skill provider that discovers `AGENTS.md` from every directory between the
|
|
17
22
|
* project cwd and the git root, plus the user home `~/.codex/AGENTS.md`.
|
|
18
23
|
*/
|
|
19
|
-
export declare class CodexSkillProvider
|
|
20
|
-
private readonly control;
|
|
21
|
-
readonly name = "codex";
|
|
24
|
+
export declare class CodexSkillProvider extends AgentsMdSkillProvider {
|
|
22
25
|
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
26
|
}
|
|
28
27
|
export default CodexSkillProvider;
|
|
29
28
|
//# sourceMappingURL=skills.d.ts.map
|
|
@@ -13,9 +13,35 @@
|
|
|
13
13
|
* @module dsh-loop-engine/engine-kimi/acp/client
|
|
14
14
|
*/
|
|
15
15
|
import type { KimiProcess, KimiSpawnCapability, KimiSpawnSpec } from '../process.ts';
|
|
16
|
-
import { type AcpFrame, type AcpUpdate } from './types.ts';
|
|
16
|
+
import { type AcpFrame, type AcpPermissionOption, type AcpPermissionResponse, type AcpUpdate } from './types.ts';
|
|
17
17
|
/** How the client answers one `session/request_permission`. */
|
|
18
18
|
export type AcpPermissionHandler = (request: AcpFrame) => boolean | Promise<boolean>;
|
|
19
|
+
/**
|
|
20
|
+
* The answerable options of one `session/request_permission` frame. The wire is
|
|
21
|
+
* untrusted, so entries without a usable `optionId` are dropped rather than
|
|
22
|
+
* echoed back.
|
|
23
|
+
* @param frame - the reverse-RPC request frame.
|
|
24
|
+
* @returns the options the client may answer with.
|
|
25
|
+
*/
|
|
26
|
+
export declare function permissionOptionsOf(frame: AcpFrame): readonly AcpPermissionOption[];
|
|
27
|
+
/**
|
|
28
|
+
* Encode one approval decision as the ACP `RequestPermissionResponse` the agent
|
|
29
|
+
* correlates against the options it advertised. The agent reads a terminal
|
|
30
|
+
* *option*, never a boolean: an outcome it cannot resolve (or a response that
|
|
31
|
+
* fails to parse at all) is reported to the model as a user rejection, so a
|
|
32
|
+
* decision that no single option expresses becomes `cancelled` — the honest
|
|
33
|
+
* "this client has no answer". Kimi re-uses this RPC for its question and
|
|
34
|
+
* plan-review bridges, which offer one `allow_once` option per choice; picking
|
|
35
|
+
* one of those would answer a question no human was asked, so any ambiguous
|
|
36
|
+
* set (zero or several `allow_once`/`reject_once` candidates) cancels instead.
|
|
37
|
+
* `allow_once` is preferred over `allow_always` because the decision is re-read
|
|
38
|
+
* from the session knobs on every request: letting the agent cache a session
|
|
39
|
+
* grant would outlive a mid-session switch back to `ask`.
|
|
40
|
+
* @param approved - whether the driver approves the request.
|
|
41
|
+
* @param options - the options the agent advertised for this request.
|
|
42
|
+
* @returns the result payload to send back.
|
|
43
|
+
*/
|
|
44
|
+
export declare function permissionResponse(approved: boolean, options: readonly AcpPermissionOption[]): AcpPermissionResponse;
|
|
19
45
|
/** Callback receiving every non-response event line. */
|
|
20
46
|
export type AcpUpdateHandler = (update: AcpUpdate) => void;
|
|
21
47
|
/**
|
|
@@ -61,8 +87,12 @@ export declare class AcpClient {
|
|
|
61
87
|
prompt(sessionId: string, text: string): Promise<unknown>;
|
|
62
88
|
/** Cancel the active turn in a session (fire-and-forget). */
|
|
63
89
|
cancel(sessionId: string): void;
|
|
64
|
-
/**
|
|
65
|
-
|
|
90
|
+
/**
|
|
91
|
+
* Answer a pending `session/request_permission`.
|
|
92
|
+
* @param id - the reverse-RPC request id.
|
|
93
|
+
* @param response - the ACP outcome to answer with.
|
|
94
|
+
*/
|
|
95
|
+
respondPermission(id: number, response: AcpPermissionResponse): void;
|
|
66
96
|
/** Consume every buffered update as an async generator. */
|
|
67
97
|
updates(): AsyncGenerator<AcpUpdate, void, void>;
|
|
68
98
|
/** Seal the client and request child termination. */
|
|
@@ -3,11 +3,15 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Kimi streams incremental assistant text (`agent_message_chunk`), incremental
|
|
5
5
|
* thinking (`agent_thought_chunk`), a tool-call announcement (`tool_call`) and its
|
|
6
|
-
* progress/result
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
6
|
+
* progress/result updates (`tool_call_update`). Assistant text and thinking are
|
|
7
|
+
* Deltas; a tool update's content is a whole Snapshot that replaces the previous
|
|
8
|
+
* one. A tool call's identity arrives on the announcement but its input
|
|
9
|
+
* (`rawInput`) does not — only a later update carries it. This module is pure: it
|
|
10
|
+
* classifies an update, extracts chunk deltas, and projects the tool-call
|
|
11
|
+
* identity/input/content/result so the agent can fold them into the durable log.
|
|
12
|
+
* Content blocks use the observed kimi
|
|
13
|
+
* `{ type: 'content', content: { type: 'text', text } }` nesting; unknown block
|
|
14
|
+
* types are ignored.
|
|
11
15
|
*
|
|
12
16
|
* @module dsh-loop-engine/engine-kimi/acp/mapping
|
|
13
17
|
*/
|
|
@@ -33,12 +37,37 @@ export declare function chunkDelta(update: AcpUpdate): string;
|
|
|
33
37
|
export declare function toolCallIdOf(update: AcpUpdate): string;
|
|
34
38
|
/** The tool display name (`title`). */
|
|
35
39
|
export declare function toolCallName(update: AcpUpdate): string;
|
|
40
|
+
/**
|
|
41
|
+
* The tool call's real input as the JSON `arguments` string the durable
|
|
42
|
+
* `tool/call` carries, when the frame supplies it.
|
|
43
|
+
*
|
|
44
|
+
* The `tool_call` announcement never carries `rawInput`; a later
|
|
45
|
+
* `tool_call_update` does (measured against kimi 0.28.x: the execution-start
|
|
46
|
+
* frame, `status: 'in_progress'`). A call's arguments are therefore unknowable
|
|
47
|
+
* at announce time, and the driver logs the call only once an update supplies
|
|
48
|
+
* them — or when the call settles, whichever comes first.
|
|
49
|
+
* @param update - one tool-call announcement or update.
|
|
50
|
+
* @returns the arguments string, or `undefined` when the frame omits the field.
|
|
51
|
+
*/
|
|
52
|
+
export declare function toolRawInput(update: AcpToolCallExt | AcpToolCallStreamExt): string | undefined;
|
|
36
53
|
/** Whether a tool stream status is settled (no longer streaming). */
|
|
37
54
|
export declare function isToolSettledStatus(status: string): boolean;
|
|
38
55
|
/** Whether a tool stream status denotes a failure. */
|
|
39
56
|
export declare function isToolErrorStatus(status: string): boolean;
|
|
40
|
-
/**
|
|
41
|
-
|
|
57
|
+
/**
|
|
58
|
+
* The tool call's content as ONE update carries it: the joined text of its
|
|
59
|
+
* observed `{ type: 'content', content: { type: 'text', text } }` blocks.
|
|
60
|
+
*
|
|
61
|
+
* Kimi re-sends the call's whole content on every `tool_call_update` rather than
|
|
62
|
+
* streaming deltas (measured against 0.28.1: the string grows from the tool
|
|
63
|
+
* input's rendering to the final output), so this is a *snapshot* — callers
|
|
64
|
+
* replace with it, never append. `undefined` means the update carried no
|
|
65
|
+
* content field at all (nothing to replace), which is distinct from a present
|
|
66
|
+
* but empty one.
|
|
67
|
+
* @param update - one tool-call announcement or update.
|
|
68
|
+
* @returns the content snapshot, or `undefined` when the field is absent.
|
|
69
|
+
*/
|
|
70
|
+
export declare function toolContentText(update: AcpToolCallExt | AcpToolCallStreamExt): string | undefined;
|
|
42
71
|
/** Project a completed tool call to a durable tool/result message. */
|
|
43
72
|
export declare function toolResult(callId: string, text: string, isError: boolean): ToolResultMessage;
|
|
44
73
|
//# sourceMappingURL=mapping.d.ts.map
|
|
@@ -33,7 +33,14 @@ export interface AcpUpdate {
|
|
|
33
33
|
readonly sessionUpdate: string;
|
|
34
34
|
readonly [key: string]: unknown;
|
|
35
35
|
}
|
|
36
|
-
/**
|
|
36
|
+
/**
|
|
37
|
+
* A tool-call announcement (`sessionUpdate: 'tool_call'`).
|
|
38
|
+
*
|
|
39
|
+
* Measured against kimi 0.28.x: an announcement carries the call's identity
|
|
40
|
+
* (`toolCallId`/`title`/`kind`) and an empty content card, but **never** its
|
|
41
|
+
* input — `rawInput` only ever arrives on a later `tool_call_update` (the
|
|
42
|
+
* execution-start frame).
|
|
43
|
+
*/
|
|
37
44
|
export interface AcpToolCallExt extends AcpUpdate {
|
|
38
45
|
readonly sessionUpdate: 'tool_call';
|
|
39
46
|
readonly toolCallId: string;
|
|
@@ -41,6 +48,8 @@ export interface AcpToolCallExt extends AcpUpdate {
|
|
|
41
48
|
readonly kind: string;
|
|
42
49
|
readonly status: string;
|
|
43
50
|
readonly content?: readonly AcpToolContentBlock[];
|
|
51
|
+
/** The call's real input; absent on the announcement (see above). */
|
|
52
|
+
readonly rawInput?: unknown;
|
|
44
53
|
}
|
|
45
54
|
/** A tool-call progress/result stream (`sessionUpdate: 'tool_call_update'`). */
|
|
46
55
|
export interface AcpToolCallStreamExt extends AcpUpdate {
|
|
@@ -48,11 +57,41 @@ export interface AcpToolCallStreamExt extends AcpUpdate {
|
|
|
48
57
|
readonly toolCallId: string;
|
|
49
58
|
readonly status: string;
|
|
50
59
|
readonly content?: readonly AcpToolContentBlock[];
|
|
60
|
+
/** The call's real input, first present on the execution-start frame. */
|
|
61
|
+
readonly rawInput?: unknown;
|
|
62
|
+
/** The tool's raw structured output, present once the call settles. */
|
|
63
|
+
readonly rawOutput?: unknown;
|
|
64
|
+
}
|
|
65
|
+
/** One option the agent offers for a `session/request_permission`. */
|
|
66
|
+
export interface AcpPermissionOption {
|
|
67
|
+
/** Opaque id the client sends back in `outcome.optionId` (the only field the agent reads). */
|
|
68
|
+
readonly optionId: string;
|
|
69
|
+
/** Human-readable label for the option. */
|
|
70
|
+
readonly name?: string;
|
|
71
|
+
/** Option class: `allow_once` / `allow_always` / `reject_once` / `reject_always`. */
|
|
72
|
+
readonly kind?: string;
|
|
51
73
|
}
|
|
52
74
|
/** The `session/request_permission` reverse-RPC params. */
|
|
53
75
|
export interface AcpPermissionRequest {
|
|
54
76
|
readonly sessionId?: string;
|
|
55
|
-
|
|
77
|
+
/** Every option the agent will accept an answer for; empty means no answerable choice. */
|
|
78
|
+
readonly options?: readonly AcpPermissionOption[];
|
|
79
|
+
/** The tool call the prompt belongs to (presentation/correlation only). */
|
|
80
|
+
readonly toolCall?: unknown;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Terminal outcome of a permission request: the option the client picked, or
|
|
84
|
+
* `cancelled` when it had no answer to give.
|
|
85
|
+
*/
|
|
86
|
+
export type AcpPermissionOutcome = {
|
|
87
|
+
readonly outcome: 'selected';
|
|
88
|
+
readonly optionId: string;
|
|
89
|
+
} | {
|
|
90
|
+
readonly outcome: 'cancelled';
|
|
91
|
+
};
|
|
92
|
+
/** The `session/request_permission` result the agent expects (ACP `RequestPermissionResponse`). */
|
|
93
|
+
export interface AcpPermissionResponse {
|
|
94
|
+
readonly outcome: AcpPermissionOutcome;
|
|
56
95
|
}
|
|
57
96
|
/** Result of a completed `session/prompt` request (opaque; the turn ended). */
|
|
58
97
|
export interface AcpPromptResult {
|
|
@@ -111,19 +111,76 @@ export declare class KimiAgent implements Agent {
|
|
|
111
111
|
private step;
|
|
112
112
|
/** Per-step accumulation state for streamed assistant blocks and tool calls. */
|
|
113
113
|
private blocks;
|
|
114
|
-
|
|
115
|
-
|
|
114
|
+
/**
|
|
115
|
+
* Announced calls still waiting for the update that carries their input,
|
|
116
|
+
* keyed by call id, holding the latest content snapshot seen meanwhile (a
|
|
117
|
+
* frame can report output before it reports input). `toolContent` holds the
|
|
118
|
+
* calls already logged, so a call is in exactly one of the two.
|
|
119
|
+
*/
|
|
120
|
+
private pendingCalls;
|
|
121
|
+
/** Whether the current step published any assistant message at all. */
|
|
122
|
+
private producedOutput;
|
|
123
|
+
/**
|
|
124
|
+
* Tool results logged into the currently open step. A result means the
|
|
125
|
+
* segment that requested the call is finished, so the next assistant content
|
|
126
|
+
* opens the next step (see {@link beginSegment}).
|
|
127
|
+
*/
|
|
128
|
+
private stepSettledTools;
|
|
129
|
+
/**
|
|
130
|
+
* Tool calls the current segment has already received input for, awaiting the
|
|
131
|
+
* segment's single assistant message. A model turn that announces several
|
|
132
|
+
* calls before any result must land them all in ONE message — the chat node
|
|
133
|
+
* keys by `${turn}:${step}` and replaces blocks on every message, so a second
|
|
134
|
+
* message would overwrite the first one's reasoning/text (and tool-call head).
|
|
135
|
+
* The list is flushed once, when the segment closes (its first settled result).
|
|
136
|
+
*/
|
|
137
|
+
private segmentCalls;
|
|
138
|
+
/** Latest content snapshot per logged tool call (see {@link applyUpdate}). */
|
|
139
|
+
private toolContent;
|
|
140
|
+
/** The live attempt framing the assistant message being assembled right now. */
|
|
141
|
+
private live;
|
|
116
142
|
private blockRef;
|
|
117
143
|
private ensureBlock;
|
|
118
|
-
/**
|
|
144
|
+
/**
|
|
145
|
+
* Rotate to the next step when the segment that ran a tool has finished, so
|
|
146
|
+
* each assistant segment lands in its own step.
|
|
147
|
+
*
|
|
148
|
+
* Called as new assistant content begins. A step holding a settled tool
|
|
149
|
+
* result means the previous segment is complete, and the content about to be
|
|
150
|
+
* applied belongs to the next one. Rotating here (rather than when a call is
|
|
151
|
+
* announced) keeps calls that were announced before any result — one model
|
|
152
|
+
* turn — in a single step.
|
|
153
|
+
*/
|
|
154
|
+
private beginSegment;
|
|
155
|
+
/** Apply one streamed update to the open step's blocks and stream. */
|
|
119
156
|
private applyUpdate;
|
|
157
|
+
/** The live attempt framing the current segment, opened on its first chunk. */
|
|
158
|
+
private currentStream;
|
|
159
|
+
/**
|
|
160
|
+
* Flush the open segment's single assistant message and every `tool/call` it
|
|
161
|
+
* accumulated, in the load-bearing order assistant/message → tool/call. One
|
|
162
|
+
* segment produces exactly ONE message even when the model announced several
|
|
163
|
+
* calls before any result, because the chat view keys an assistant node by
|
|
164
|
+
* `${turn}:${step}` and replaces its blocks on every message — a second
|
|
165
|
+
* message in the same step would overwrite the first one's reasoning/text and
|
|
166
|
+
* leave only the last bare tool-call head visible.
|
|
167
|
+
* @param phase - the open step the segment belongs to.
|
|
168
|
+
*/
|
|
169
|
+
private flushSegment;
|
|
120
170
|
/**
|
|
121
171
|
* Flush the accumulated assistant blocks into one durable assistant/message
|
|
122
|
-
* carrying the exact stream the attempt published live
|
|
123
|
-
*
|
|
124
|
-
*
|
|
125
|
-
*
|
|
126
|
-
*
|
|
172
|
+
* carrying the exact stream the attempt published live, optionally closing it
|
|
173
|
+
* with the tool-call block(s) that ended the segment.
|
|
174
|
+
*
|
|
175
|
+
* Every flush settles its own attempt (and so its own live `end` frame): a
|
|
176
|
+
* step emits one message per assistant segment, and the client pairs a durable
|
|
177
|
+
* message with the attempt whose `end` cites it, so two messages may not share
|
|
178
|
+
* one attempt.
|
|
179
|
+
* @param phase - the open step the message belongs to.
|
|
180
|
+
* @param toolCalls - the calls that closed this segment, rendered as trailing
|
|
181
|
+
* `tool-call` content blocks. A segment with no blocks of its own — a step
|
|
182
|
+
* whose only activity was a tool call — still emits this message, so its
|
|
183
|
+
* `tool/call` and `tool/result` events have a parent to pair with.
|
|
127
184
|
*/
|
|
128
185
|
private flushAssistant;
|
|
129
186
|
}
|
|
@@ -14,12 +14,14 @@
|
|
|
14
14
|
*
|
|
15
15
|
* @module dsh-loop-engine/engine-kimi
|
|
16
16
|
*/
|
|
17
|
-
import { Service } from '@deepseek-ai/cordis';
|
|
18
17
|
import type { Context } from '@deepseek-ai/cordis';
|
|
19
18
|
import z from '@deepseek-ai/schemastery';
|
|
20
|
-
import type {
|
|
19
|
+
import type { AgentOptions } from '@deepseek-ai/dsh-agent';
|
|
20
|
+
import type { Session, SessionId } from '@deepseek-ai/dsh-session';
|
|
21
|
+
import { KimiAgent } from './agent.ts';
|
|
21
22
|
import type { KimiSpawnCapability } from './process.ts';
|
|
22
23
|
import type { ResolvedConfig } from './types.ts';
|
|
24
|
+
import { HostedLoopFactory } from '../driver-core/hosted-loop-factory.ts';
|
|
23
25
|
/** Grace in milliseconds for Kimi process-tree termination. */
|
|
24
26
|
export declare const KIMI_DISPOSE_GRACE_MS = 3000;
|
|
25
27
|
/** Deployment-owned configuration for the Kimi loop plugin. */
|
|
@@ -45,64 +47,13 @@ declare module '@deepseek-ai/cordis' {
|
|
|
45
47
|
* transaction: prepare, run setup, then publish through both registries,
|
|
46
48
|
* announce, and emit `agent/session-start`.
|
|
47
49
|
*/
|
|
48
|
-
export declare class KimiLoop extends
|
|
50
|
+
export declare class KimiLoop extends HostedLoopFactory<ResolvedConfig, KimiAgent> {
|
|
49
51
|
/** Services the loop resolves through its own fiber; blessed identically to the package-level entry inject. */
|
|
50
52
|
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
53
|
/** One-shot spawn capability handed to every agent, sandboxed by the subprocess seam. */
|
|
57
54
|
readonly spawn: KimiSpawnCapability;
|
|
58
55
|
constructor(ctx: Context, config: Config);
|
|
59
|
-
/**
|
|
60
|
-
|
|
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;
|
|
56
|
+
/** Construct the Kimi ACP driver for one prepared session. */
|
|
57
|
+
protected buildAgent(loopCtx: Context, id: SessionId, options: AgentOptions, session: Session): KimiAgent;
|
|
107
58
|
}
|
|
108
59
|
//# sourceMappingURL=loop.d.ts.map
|
|
@@ -11,6 +11,9 @@
|
|
|
11
11
|
* is surfaced under its own name, so the dsh skill-injection seam (`/name`
|
|
12
12
|
* gestures) can carry them into the prompt.
|
|
13
13
|
*
|
|
14
|
+
* Unlike pi and Codex, Kimi has no user-level instruction file: its user
|
|
15
|
+
* install root holds only the skills catalog.
|
|
16
|
+
*
|
|
14
17
|
* The generic `~/.agents/skills/` and `.agents/skills/` roots are deliberately
|
|
15
18
|
* not scanned here: dsh's own `skill-filesystem` provider already exposes them
|
|
16
19
|
* through the same registry in the web profile. Kimi built-in Skills are
|
|
@@ -21,9 +24,13 @@
|
|
|
21
24
|
* `disableModelInvocation`/`type` fields are not translated, so a `type: flow`
|
|
22
25
|
* skill is surfaced as model-invocable.
|
|
23
26
|
*
|
|
27
|
+
* The discovery algorithm itself lives in {@link AgentsMdSkillProvider}; this
|
|
28
|
+
* module supplies only Kimi's locations and ranks.
|
|
29
|
+
*
|
|
24
30
|
* @module dsh-loop-engine/engine-kimi/skills
|
|
25
31
|
*/
|
|
26
|
-
import
|
|
32
|
+
import { AgentsMdSkillProvider } from '../driver-core/agents-md-skill-provider.ts';
|
|
33
|
+
import type { SkillProviderControl } from '../skills.ts';
|
|
27
34
|
/**
|
|
28
35
|
* Resolve the Kimi config directory, honoring the `KIMI_CODE_HOME` environment
|
|
29
36
|
* override and falling back to `~/.kimi-code`.
|
|
@@ -38,20 +45,8 @@ export declare function kimiAgentDir(): string;
|
|
|
38
45
|
* - project `.kimi-code/skills/` and user `~/.kimi-code/skills/` — each
|
|
39
46
|
* `SKILL.md` entry surfaced under its own name.
|
|
40
47
|
*/
|
|
41
|
-
export declare class KimiSkillProvider
|
|
42
|
-
private readonly control;
|
|
43
|
-
readonly name = "kimi";
|
|
48
|
+
export declare class KimiSkillProvider extends AgentsMdSkillProvider {
|
|
44
49
|
constructor(control: SkillProviderControl);
|
|
45
|
-
list(options: SkillLookupOptions): Promise<readonly SkillCandidate[]>;
|
|
46
|
-
get(candidate: SkillCandidate, _options: SkillLookupOptions): Promise<SkillDefinition | undefined>;
|
|
47
|
-
/** One merged `agents-md` candidate for a ranked file set. */
|
|
48
|
-
private agentsCandidate;
|
|
49
|
-
/** Collect every skill in one skills directory, both kimi layouts. */
|
|
50
|
-
private collectSkillsDir;
|
|
51
|
-
/** One parsed skill as a ranked candidate. */
|
|
52
|
-
private skillCandidate;
|
|
53
|
-
/** Parse one SKILL.md file, or `undefined` when it is unreadable or invalid. */
|
|
54
|
-
private tryParse;
|
|
55
50
|
}
|
|
56
51
|
export default KimiSkillProvider;
|
|
57
52
|
//# sourceMappingURL=skills.d.ts.map
|