lark-remote 0.5.3 → 0.5.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/README.en.md +1 -1
  3. package/README.md +1 -1
  4. package/dist/bridge/approval-coordinator.d.ts +18 -1
  5. package/dist/bridge/approval-coordinator.js +57 -10
  6. package/dist/bridge/index.js +90 -17
  7. package/dist/card/approval-render.js +18 -0
  8. package/dist/card/run-state.js +10 -5
  9. package/dist/config/index.d.ts +16 -0
  10. package/dist/config/index.js +46 -6
  11. package/dist/config/opencode-config.d.ts +3 -3
  12. package/dist/config/opencode-config.js +38 -22
  13. package/dist/inbound/authorize.d.ts +34 -0
  14. package/dist/inbound/authorize.js +31 -0
  15. package/dist/index.js +116 -63
  16. package/dist/instance-lock.d.ts +9 -0
  17. package/dist/instance-lock.js +113 -42
  18. package/dist/router/config/dsh.d.ts +51 -21
  19. package/dist/router/config/dsh.js +255 -66
  20. package/dist/router/config/opencode.js +16 -6
  21. package/dist/router/config/types.d.ts +6 -3
  22. package/dist/router/index.d.ts +1 -0
  23. package/dist/router/index.js +13 -5
  24. package/dist/runner/codex/app-server/translator.js +21 -7
  25. package/dist/runner/common/acp/base-acp-runner.d.ts +12 -0
  26. package/dist/runner/common/acp/base-acp-runner.js +83 -8
  27. package/dist/runner/common/acp/protocol-types.d.ts +10 -2
  28. package/dist/runner/common/acp/protocol-types.js +3 -1
  29. package/dist/runner/common/connection-based-runner.d.ts +15 -0
  30. package/dist/runner/common/connection-based-runner.js +40 -5
  31. package/dist/runner/common/jsonrpc/connection-manager.d.ts +15 -0
  32. package/dist/runner/common/jsonrpc/connection-manager.js +83 -50
  33. package/dist/runner/dsh/approval.d.ts +53 -0
  34. package/dist/runner/dsh/approval.js +161 -0
  35. package/dist/runner/dsh/auth.d.ts +36 -0
  36. package/dist/runner/dsh/auth.js +118 -0
  37. package/dist/runner/dsh/client.d.ts +161 -42
  38. package/dist/runner/dsh/client.js +551 -244
  39. package/dist/runner/dsh/daemon.d.ts +85 -0
  40. package/dist/runner/dsh/daemon.js +272 -0
  41. package/dist/runner/dsh/merge.d.ts +9 -0
  42. package/dist/runner/dsh/merge.js +51 -0
  43. package/dist/runner/dsh/runner.d.ts +94 -11
  44. package/dist/runner/dsh/runner.js +553 -67
  45. package/dist/runner/dsh/translator.d.ts +43 -3
  46. package/dist/runner/dsh/translator.js +115 -38
  47. package/dist/runner/dsh/types.d.ts +189 -27
  48. package/dist/runner/dsh/types.js +11 -5
  49. package/dist/runner/kimi/acp/runner.js +15 -3
  50. package/dist/runner/opencode/acp/error-monitor.d.ts +2 -2
  51. package/dist/runner/opencode/acp/error-monitor.js +2 -2
  52. package/dist/runner/opencode/acp/runner.d.ts +9 -0
  53. package/dist/runner/opencode/acp/runner.js +40 -1
  54. package/dist/runner/opencode/acp/translator.d.ts +4 -2
  55. package/dist/runner/opencode/acp/translator.js +12 -3
  56. package/dist/runner/pi/rpc/translator.d.ts +2 -0
  57. package/dist/runner/pi/rpc/translator.js +27 -1
  58. package/dist/runner/types.d.ts +34 -0
  59. package/dist/runner/types.js +24 -0
  60. package/dist/session/codex/rollout-reader.js +44 -7
  61. package/dist/session/dsh/sessions.d.ts +45 -6
  62. package/dist/session/dsh/sessions.js +171 -27
  63. package/dist/session/opencode/sessions.d.ts +18 -7
  64. package/dist/session/opencode/sessions.js +294 -15
  65. package/dist/session/pi/sessions.js +6 -2
  66. package/package.json +3 -1
@@ -12,17 +12,57 @@
12
12
  * completed → success; aborted/interrupted → interrupted;
13
13
  * blocked/error/max-tokens → error (errorMessage carries the reason)
14
14
  *
15
+ * Token accounting (ccusage 口径, AGENTS.md §9.21): DSH emits ONE usage per model
16
+ * call (per step) — a tool-looping turn has several `assistant/message` events.
17
+ * The result event therefore carries **the sum of the turn's samplings**, not the
18
+ * last one (codex `token_count` has the same shape). `lastUsage` is kept only as
19
+ * the message/stream watermark and as a fallback when no durable message usage
20
+ * was persisted (e.g. an interrupted attempt).
21
+ *
15
22
  * DSH TokenUsage: {inputTokens, outputTokens, cacheReadTokens?, cacheWriteTokens?}
16
23
  * → ResultEvent.usage: input_tokens/output_tokens/cache_read_tokens/
17
24
  * cache_creation_tokens (cacheWrite = cache creation) + total_tokens sum.
18
25
  */
19
26
  import type { AgentEvent, ResultEvent } from '../types.js';
20
- import type { DshSessionEvent, DshTokenUsage } from './types.js';
27
+ import type { DshAssistantStreamFrame, DshSessionEvent, DshTokenUsage } from './types.js';
28
+ /** 本 turn 各采样用量之和(input 是非缓存输入,与 ccusage/ResultEvent 口径一致)。 */
29
+ interface UsageTotals {
30
+ input: number;
31
+ output: number;
32
+ cacheRead: number;
33
+ cacheWrite: number;
34
+ }
21
35
  export declare class DshTranslator {
22
- private liveUsage?;
23
- private hasUsage;
36
+ /** 本 turn 累计用量:DSH 每次模型调用(step)一条 usage,工具往返 = 多条。 */
37
+ private turnUsage;
38
+ /** 末次采样的 usage:上下文水位,或只有 live chunk 时的兜底值。 */
39
+ private lastUsage?;
40
+ /** 是否收到过 durable assistant/message.usage(唯一累加来源)。 */
41
+ private hasMessageUsage;
42
+ /** 是否只收到过 live usage chunk(无 durable message usage 时的兜底)。 */
43
+ private hasStreamUsage;
44
+ /** 当前 attempt 的 id(start 帧携带,chunk 帧也带;断开重连重放时不变)。 */
45
+ private attemptId;
46
+ /** Translate one live assistant-stream chunk into AgentEvents. */
47
+ streamFrameToAgentEvents(frame: DshAssistantStreamFrame, _sessionId: string): AgentEvent[];
48
+ /**
49
+ * Record durable per-step usage — the accumulation source. assistant/message
50
+ * carries exactly one usage per model call and is seq-deduped by the client,
51
+ * so summing it gives the turn total without double counting.
52
+ */
53
+ private recordMessageUsage;
54
+ /**
55
+ * Record a live usage chunk. Never summed: the same step also arrives as a
56
+ * durable assistant/message.usage, so summing both would double count. It only
57
+ * refreshes the watermark and covers a turn whose messages never persisted.
58
+ */
59
+ private recordStreamUsage;
60
+ private chunkToAgentEvents;
24
61
  /** Translate one session event into AgentEvents (may be empty). */
25
62
  eventToAgentEvents(ev: DshSessionEvent, sessionId: string): AgentEvent[];
26
63
  }
27
64
  /** Map DSH TokenUsage → ResultEvent.usage (cacheWrite = cache creation). */
28
65
  export declare function mapUsage(u: DshTokenUsage): NonNullable<ResultEvent['usage']>;
66
+ /** Map a turn's summed usage → ResultEvent.usage (cacheWrite = cache creation). */
67
+ export declare function mapAggregatedUsage(t: UsageTotals): NonNullable<ResultEvent['usage']>;
68
+ export {};
@@ -12,13 +12,96 @@
12
12
  * completed → success; aborted/interrupted → interrupted;
13
13
  * blocked/error/max-tokens → error (errorMessage carries the reason)
14
14
  *
15
+ * Token accounting (ccusage 口径, AGENTS.md §9.21): DSH emits ONE usage per model
16
+ * call (per step) — a tool-looping turn has several `assistant/message` events.
17
+ * The result event therefore carries **the sum of the turn's samplings**, not the
18
+ * last one (codex `token_count` has the same shape). `lastUsage` is kept only as
19
+ * the message/stream watermark and as a fallback when no durable message usage
20
+ * was persisted (e.g. an interrupted attempt).
21
+ *
15
22
  * DSH TokenUsage: {inputTokens, outputTokens, cacheReadTokens?, cacheWriteTokens?}
16
23
  * → ResultEvent.usage: input_tokens/output_tokens/cache_read_tokens/
17
24
  * cache_creation_tokens (cacheWrite = cache creation) + total_tokens sum.
18
25
  */
19
26
  export class DshTranslator {
20
- liveUsage;
21
- hasUsage = false;
27
+ /** 本 turn 累计用量:DSH 每次模型调用(step)一条 usage,工具往返 = 多条。 */
28
+ turnUsage = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
29
+ /** 末次采样的 usage:上下文水位,或只有 live chunk 时的兜底值。 */
30
+ lastUsage;
31
+ /** 是否收到过 durable assistant/message.usage(唯一累加来源)。 */
32
+ hasMessageUsage = false;
33
+ /** 是否只收到过 live usage chunk(无 durable message usage 时的兜底)。 */
34
+ hasStreamUsage = false;
35
+ /** 当前 attempt 的 id(start 帧携带,chunk 帧也带;断开重连重放时不变)。 */
36
+ attemptId = '0';
37
+ /** Translate one live assistant-stream chunk into AgentEvents. */
38
+ streamFrameToAgentEvents(frame, _sessionId) {
39
+ if (frame.type === 'start') {
40
+ if (frame.attemptId)
41
+ this.attemptId = frame.attemptId;
42
+ return [];
43
+ }
44
+ if (frame.type !== 'chunk' || !frame.chunk)
45
+ return [];
46
+ return this.chunkToAgentEvents(frame.chunk, frame.time ?? Date.now(), frame.attemptId ?? this.attemptId);
47
+ }
48
+ /**
49
+ * Record durable per-step usage — the accumulation source. assistant/message
50
+ * carries exactly one usage per model call and is seq-deduped by the client,
51
+ * so summing it gives the turn total without double counting.
52
+ */
53
+ recordMessageUsage(usage) {
54
+ this.turnUsage.input += usage.inputTokens ?? 0;
55
+ this.turnUsage.output += usage.outputTokens ?? 0;
56
+ this.turnUsage.cacheRead += usage.cacheReadTokens ?? 0;
57
+ this.turnUsage.cacheWrite += usage.cacheWriteTokens ?? 0;
58
+ this.lastUsage = usage;
59
+ this.hasMessageUsage = true;
60
+ }
61
+ /**
62
+ * Record a live usage chunk. Never summed: the same step also arrives as a
63
+ * durable assistant/message.usage, so summing both would double count. It only
64
+ * refreshes the watermark and covers a turn whose messages never persisted.
65
+ */
66
+ recordStreamUsage(usage) {
67
+ this.lastUsage = usage;
68
+ this.hasStreamUsage = true;
69
+ }
70
+ chunkToAgentEvents(chunk, time, attemptId = '0') {
71
+ if (chunk.type === 'usage' && chunk.usage && typeof chunk.usage === 'object') {
72
+ this.recordStreamUsage(chunk.usage);
73
+ return [];
74
+ }
75
+ const ts = new Date(time).toISOString();
76
+ if (chunk.type === 'text-delta' && typeof chunk.text === 'string' && chunk.text) {
77
+ return [
78
+ {
79
+ type: 'assistant',
80
+ message: { content: [{ type: 'text', text: chunk.text }] },
81
+ timestamp: ts,
82
+ },
83
+ ];
84
+ }
85
+ if (chunk.type === 'reasoning-delta' && typeof chunk.text === 'string' && chunk.text) {
86
+ return [
87
+ {
88
+ type: 'assistant',
89
+ message: {
90
+ content: [
91
+ {
92
+ type: 'thinking',
93
+ thinking: chunk.text,
94
+ // 同一 attempt 的同一 reasoning block:token delta 共享 itemId。
95
+ itemId: `r:${attemptId}:${chunk.index}`,
96
+ },
97
+ ],
98
+ },
99
+ timestamp: ts,
100
+ },
101
+ ];
102
+ }
103
+ return [];
104
+ }
22
105
  /** Translate one session event into AgentEvents (may be empty). */
23
106
  eventToAgentEvents(ev, sessionId) {
24
107
  switch (ev.type) {
@@ -26,40 +109,21 @@ export class DshTranslator {
26
109
  const chunk = ev.data.chunk;
27
110
  if (!chunk)
28
111
  return [];
29
- const ts = new Date(ev.time).toISOString();
30
- if (chunk.type === 'text-delta' && chunk.text) {
31
- return [
32
- {
33
- type: 'assistant',
34
- message: { content: [{ type: 'text', text: chunk.text }] },
35
- timestamp: ts,
36
- },
37
- ];
38
- }
39
- if (chunk.type === 'reasoning-delta' && chunk.text) {
40
- return [
41
- {
42
- type: 'assistant',
43
- message: { content: [{ type: 'thinking', thinking: chunk.text }] },
44
- timestamp: ts,
45
- },
46
- ];
47
- }
48
- return [];
112
+ return this.chunkToAgentEvents(chunk, ev.time);
49
113
  }
50
114
  case 'assistant/message': {
51
115
  const raw = ev.data.usage;
52
116
  if (raw && typeof raw === 'object') {
53
- this.liveUsage = raw;
54
- this.hasUsage = true;
117
+ this.recordMessageUsage(raw);
55
118
  }
56
119
  return [];
57
120
  }
58
121
  case 'turn/end': {
59
- const reason = ev.data.reason?.kind;
122
+ const turnReason = ev.data.reason;
123
+ const reason = turnReason?.kind;
60
124
  const subtype = reason === 'completed'
61
125
  ? 'success'
62
- : reason === 'aborted' || reason === 'interrupted'
126
+ : reason === 'aborted' || reason === 'interrupted' || reason === 'forked'
63
127
  ? 'interrupted'
64
128
  : 'error';
65
129
  const result = {
@@ -68,11 +132,19 @@ export class DshTranslator {
68
132
  session_id: sessionId,
69
133
  timestamp: new Date(ev.time).toISOString(),
70
134
  };
71
- if (this.hasUsage && this.liveUsage) {
72
- result.usage = mapUsage(this.liveUsage);
135
+ if (this.hasMessageUsage) {
136
+ // 本 turn 各采样之和(多步工具往返 = 多条 assistant/message)。
137
+ result.usage = mapAggregatedUsage(this.turnUsage);
138
+ }
139
+ else if (this.hasStreamUsage && this.lastUsage) {
140
+ // 只有 live usage chunk(消息未落盘,如启动期中断):退回末次采样。
141
+ result.usage = mapUsage(this.lastUsage);
73
142
  }
74
143
  if (subtype === 'error') {
75
- result.errorMessage = `turn ended with reason: ${reason}`;
144
+ const detail = turnReason?.error?.message;
145
+ result.errorMessage = detail
146
+ ? `turn ended with reason: ${reason}: ${detail}`
147
+ : `turn ended with reason: ${reason}`;
76
148
  }
77
149
  return [result];
78
150
  }
@@ -83,16 +155,21 @@ export class DshTranslator {
83
155
  }
84
156
  /** Map DSH TokenUsage → ResultEvent.usage (cacheWrite = cache creation). */
85
157
  export function mapUsage(u) {
86
- const input = u.inputTokens ?? 0;
87
- const output = u.outputTokens ?? 0;
88
- const cacheRead = u.cacheReadTokens ?? 0;
89
- const cacheWrite = u.cacheWriteTokens ?? 0;
158
+ return mapAggregatedUsage({
159
+ input: u.inputTokens ?? 0,
160
+ output: u.outputTokens ?? 0,
161
+ cacheRead: u.cacheReadTokens ?? 0,
162
+ cacheWrite: u.cacheWriteTokens ?? 0,
163
+ });
164
+ }
165
+ /** Map a turn's summed usage → ResultEvent.usage (cacheWrite = cache creation). */
166
+ export function mapAggregatedUsage(t) {
90
167
  return {
91
- input_tokens: input,
92
- output_tokens: output,
93
- cache_read_tokens: cacheRead,
94
- cache_creation_tokens: cacheWrite,
95
- total_tokens: input + output + cacheRead + cacheWrite,
168
+ input_tokens: t.input,
169
+ output_tokens: t.output,
170
+ cache_read_tokens: t.cacheRead,
171
+ cache_creation_tokens: t.cacheWrite,
172
+ total_tokens: t.input + t.output + t.cacheRead + t.cacheWrite,
96
173
  };
97
174
  }
98
175
  //# sourceMappingURL=translator.js.map
@@ -1,14 +1,20 @@
1
1
  /**
2
2
  * DSH wire type shapes (DeepSeek Harness HTTP proxy contract).
3
3
  *
4
- * Source of truth: packages/host/apiproxy/src/api/{rpc,sessions,events}.ts and
5
- * packages/core/session/src/types.ts in the deepseek-harness repo. Only the
6
- * fields DshClient/DshRunner/DshSessionReader consume are declared here.
4
+ * Source of truth: @deepseek-ai/dsh-client-connection (unary envelope),
5
+ * @deepseek-ai/dsh-api-gateway (Remote stream mux) and
6
+ * @deepseek-ai/dsh-api-session-controller (session Remote methods) in DSH
7
+ * 0.2.x. Only the fields DshClient/DshRunner/DshSessionReader consume are
8
+ * declared here.
7
9
  *
8
10
  * Unary: POST /api/<method>, body `{type:'client-request',rpcId,method,payload}`,
9
11
  * response `{type:'server-response',rpcId,result:{ok:true,value}|{ok:false,error}}`.
10
- * Stream: SSE GET /api/events.mux, frames `data: {type:'server-request',rpcId,
11
- * method:<frame.type>, payload:<MuxFrame>}\n\n`.
12
+ * Auth: a signed `dsh-auth-*` cookie is required on every `/api/*` request and
13
+ * the `/api/remote.mux` WebSocket. The v1 cookie is minted from DSH's durable
14
+ * browser-session secret, or bootstrapped with `GET /?token=<launch token>`.
15
+ * Stream: WebSocket `/api/remote.mux` logical streams
16
+ * `{type:'open',streamId,endpoint:'session/follow',payload}` →
17
+ * `{type:'item',streamId,value}` / `{type:'end',streamId}`.
12
18
  */
13
19
  /** One SessionEvent entry (core SessionEvent<T> — seq/time/data discriminated on type). */
14
20
  export interface DshSessionEvent {
@@ -16,6 +22,9 @@ export interface DshSessionEvent {
16
22
  seq: number;
17
23
  time: number;
18
24
  data: Record<string, unknown>;
25
+ ignorable?: true;
26
+ sourceEventSeqs?: unknown;
27
+ surfaceOp?: unknown;
19
28
  }
20
29
  /** TokenUsage from assistant/message.usage (dsh-llm TokenUsage). */
21
30
  export interface DshTokenUsage {
@@ -25,31 +34,43 @@ export interface DshTokenUsage {
25
34
  cacheWriteTokens?: number;
26
35
  reasoningTokens?: number;
27
36
  }
28
- /** SessionSummary row from session.list items[]. */
37
+ /** Projection block returned by session/list / session/projections / follow. */
38
+ export interface DshProjections {
39
+ kind?: 'cached' | 'sequenced';
40
+ asOfSeq: number;
41
+ values: {
42
+ agentPreset?: string | null;
43
+ title?: string | null;
44
+ [key: string]: unknown;
45
+ };
46
+ }
47
+ /** SessionSummary row from session/list items[]. */
29
48
  export interface DshSessionSummary {
30
49
  sessionId: string;
31
50
  updatedAt: number;
32
51
  running: boolean;
33
52
  blank: boolean;
34
53
  cwd?: string;
35
- /** 创建时固定的 preset(/resume preset 一致性校验用)。 */
54
+ /** Legacy top-level preset (pre-0.2 DSH). */
36
55
  agentPreset?: string;
56
+ /** 0.2.x exposes preset/title through projections. */
57
+ projections?: DshProjections;
37
58
  }
38
- /** session.list → value. */
59
+ /** session/list → value. */
39
60
  export interface DshSessionListValue {
40
61
  items: DshSessionSummary[];
41
62
  }
42
- /** session.history → value.events[].event. */
43
- export interface DshHistoryEntry {
63
+ /** session/page → value.records[]. */
64
+ export interface DshPageRecord {
44
65
  event: DshSessionEvent;
45
66
  view?: unknown;
46
67
  }
47
- /** session.history → value. */
48
- export interface DshSessionHistoryValue {
49
- events: DshHistoryEntry[];
68
+ /** session/page → value. */
69
+ export interface DshSessionPageValue {
70
+ records: DshPageRecord[];
50
71
  hasMore: boolean;
51
72
  }
52
- /** One model entry inside a provider group (llm.models / session.models groups[].models[]). */
73
+ /** One model entry inside a provider group (session/modelCatalog / session/selectModel groups[].models[]). */
53
74
  export interface DshCatalogModel {
54
75
  id: string;
55
76
  name: string;
@@ -62,14 +83,16 @@ export interface DshCatalogModel {
62
83
  defaultEffort?: string;
63
84
  };
64
85
  }
65
- /** One provider group of the model catalog (llm.models groups[]). */
86
+ /** One provider group of the model catalog (session/modelCatalog groups[]). */
66
87
  export interface DshModelGroup {
67
88
  id: string;
68
89
  name: string;
69
90
  models: DshCatalogModel[];
70
91
  }
71
- /** llm.models → value. */
92
+ /** session/modelCatalog → value. */
72
93
  export interface DshModelCatalogValue {
94
+ default?: DshModelSelection;
95
+ routableProviders?: string[];
73
96
  groups: DshModelGroup[];
74
97
  failures?: Array<{
75
98
  id: string;
@@ -77,13 +100,13 @@ export interface DshModelCatalogValue {
77
100
  message: string;
78
101
  }>;
79
102
  }
80
- /** Current model selection (session.models current / session.selectModel selected). */
103
+ /** Current model selection (session/selectModel current / session/selectModel selected). */
81
104
  export interface DshModelSelection {
82
105
  provider: string;
83
106
  model: string;
84
107
  reasoningEffort?: string;
85
108
  }
86
- /** One preset row of agentPreset.list. */
109
+ /** One preset row of agentPresets/list. */
87
110
  export interface DshPresetEntry {
88
111
  id: string;
89
112
  trust: 'system' | 'user';
@@ -92,28 +115,167 @@ export interface DshPresetEntry {
92
115
  description?: string;
93
116
  broken?: string;
94
117
  }
95
- /** agentPreset.list → value. */
118
+ /** agentPresets/list → value. */
96
119
  export interface DshPresetListValue {
97
120
  presets: DshPresetEntry[];
98
- authorable: boolean;
99
- hasDocument: boolean;
121
+ authorable?: boolean;
122
+ hasDocument?: boolean;
123
+ }
124
+ /** One permission preset from `permissionPresets/catalog`. */
125
+ export interface DshPermissionPresetOption {
126
+ value: string;
127
+ name: string;
128
+ description?: string;
129
+ }
130
+ /** `permissionPresets/catalog` → value. */
131
+ export interface DshPermissionCatalogValue {
132
+ options: DshPermissionPresetOption[];
133
+ /** Presets that may be selected as the default for future sessions. */
134
+ defaultOptions: DshPermissionPresetOption[];
135
+ defaultPreset: string;
136
+ }
137
+ /** One durable event record inside a follow snapshot / event frame. */
138
+ export interface DshFollowRecord {
139
+ type: 'event';
140
+ event: DshSessionEvent;
141
+ }
142
+ /** Opening frame of `session/follow`. */
143
+ export interface DshFollowSnapshot {
144
+ type: 'snapshot';
145
+ header?: Record<string, unknown>;
146
+ cursor: number;
147
+ records: DshFollowRecord[];
148
+ hasMore?: boolean;
149
+ projections?: DshProjections;
150
+ }
151
+ /** Assistant stream chunk emitted while the model is still generating. */
152
+ export type DshStreamChunk = {
153
+ type: 'text-delta';
154
+ index: number;
155
+ text: string;
156
+ } | {
157
+ type: 'reasoning-delta';
158
+ index: number;
159
+ text: string;
160
+ } | {
161
+ type: 'usage';
162
+ usage: DshTokenUsage;
163
+ } | {
164
+ type: string;
165
+ [key: string]: unknown;
166
+ };
167
+ /** One `assistant-stream` item of the follow stream. */
168
+ export interface DshAssistantStreamFrame {
169
+ type: 'start' | 'chunk' | 'end';
170
+ attemptId?: string;
171
+ revision?: number;
172
+ index?: number;
173
+ time?: number;
174
+ chunk?: DshStreamChunk;
175
+ outcome?: Record<string, unknown>;
176
+ }
177
+ /** First item of the Gateway-owned `$events` stream. */
178
+ export interface DshRemoteEventReadyFrame {
179
+ type: 'ready';
180
+ clientId: string;
181
+ host: {
182
+ home: string;
183
+ };
184
+ }
185
+ /** One pending Agent-scoped approval waterfall delivered by `$events`. */
186
+ export interface DshRemoteEventWaterfallFrame {
187
+ type: 'waterfall';
188
+ event: string;
189
+ eventId: string;
190
+ agentId: string;
191
+ request: Record<string, unknown>;
192
+ }
193
+ /** Cancellation of a previously delivered waterfall. */
194
+ export interface DshRemoteEventCancelFrame {
195
+ type: 'cancel';
196
+ eventId: string;
197
+ }
198
+ /** Ordinary forwarded notification (not used by the DSH approval path). */
199
+ export interface DshRemoteEventEmitFrame {
200
+ type: 'emit';
201
+ event: string;
202
+ args: unknown[];
100
203
  }
101
- /** ServerRequest envelope (frame.method === frame.payload.type). */
102
- export interface DshServerRequest {
103
- type: 'server-request';
104
- rpcId: string;
105
- method: string;
106
- payload: unknown;
204
+ /** One item of the Gateway-owned `$events` logical stream. */
205
+ export type DshRemoteEventFrame = DshRemoteEventReadyFrame | DshRemoteEventWaterfallFrame | DshRemoteEventCancelFrame | DshRemoteEventEmitFrame;
206
+ /** Answer accepted by `$events/result` for one approval waterfall. */
207
+ export type DshRemoteEventOutcome = {
208
+ kind: 'next';
209
+ } | {
210
+ kind: 'result';
211
+ value?: unknown;
212
+ } | {
213
+ kind: 'rejected';
214
+ error: {
215
+ name: string;
216
+ message: string;
217
+ code?: string;
218
+ details?: unknown;
219
+ };
220
+ };
221
+ /** Approval-related items yielded while consuming `$events`. */
222
+ export type DshApprovalStreamItem = {
223
+ kind: 'ready';
224
+ clientId: string;
225
+ } | {
226
+ kind: 'request';
227
+ eventId: string;
228
+ clientId: string;
229
+ toolName: string;
230
+ callId?: string;
231
+ reason?: string;
232
+ } | {
233
+ kind: 'cancelled';
234
+ eventId: string;
235
+ } | {
236
+ kind: 'unavailable';
237
+ message: string;
238
+ };
239
+ /** One value yielded by a `session/follow` logical stream. */
240
+ export type DshFollowFrame = DshFollowSnapshot | {
241
+ type: 'event';
242
+ event: DshSessionEvent;
243
+ } | {
244
+ type: 'assistant-stream';
245
+ frame: DshAssistantStreamFrame;
246
+ };
247
+ /**
248
+ * Normalized `commands/execute` result.
249
+ *
250
+ * DSH's wire value is `CommandExecution { commandId, result: CommandResult }`.
251
+ * The client flattens that envelope so every caller (compact, permission
252
+ * alignment) sees one stable shape. The RPC settles only after the command
253
+ * handler finishes — for `/compact` that means after the compaction completes.
254
+ * `kind:'error'` is an expected outcome (idle-only violation, no compactable
255
+ * history, summary failure), not a transport failure, so callers must surface
256
+ * `text` instead of throwing. `undefined` means the line was not admitted as a
257
+ * known command at all (admission miss logs nothing and runs nothing).
258
+ */
259
+ export interface DshCommandResult {
260
+ commandId?: string;
261
+ kind?: 'success' | 'error';
262
+ text?: string;
263
+ sourceEventSeq?: number;
107
264
  }
108
265
  /** Unified per-session subscription item: a session event or an approval notice. */
109
266
  export type DshStreamItem = {
110
267
  kind: 'event';
111
268
  sessionId: string;
112
269
  event: DshSessionEvent;
270
+ } | {
271
+ kind: 'assistant-stream';
272
+ sessionId: string;
273
+ frame: DshAssistantStreamFrame;
113
274
  } | {
114
275
  kind: 'approval';
115
276
  sessionId: string;
116
277
  approvalId: string;
117
278
  toolName: string;
279
+ callId?: string;
118
280
  reason?: string;
119
281
  };
@@ -1,14 +1,20 @@
1
1
  /**
2
2
  * DSH wire type shapes (DeepSeek Harness HTTP proxy contract).
3
3
  *
4
- * Source of truth: packages/host/apiproxy/src/api/{rpc,sessions,events}.ts and
5
- * packages/core/session/src/types.ts in the deepseek-harness repo. Only the
6
- * fields DshClient/DshRunner/DshSessionReader consume are declared here.
4
+ * Source of truth: @deepseek-ai/dsh-client-connection (unary envelope),
5
+ * @deepseek-ai/dsh-api-gateway (Remote stream mux) and
6
+ * @deepseek-ai/dsh-api-session-controller (session Remote methods) in DSH
7
+ * 0.2.x. Only the fields DshClient/DshRunner/DshSessionReader consume are
8
+ * declared here.
7
9
  *
8
10
  * Unary: POST /api/<method>, body `{type:'client-request',rpcId,method,payload}`,
9
11
  * response `{type:'server-response',rpcId,result:{ok:true,value}|{ok:false,error}}`.
10
- * Stream: SSE GET /api/events.mux, frames `data: {type:'server-request',rpcId,
11
- * method:<frame.type>, payload:<MuxFrame>}\n\n`.
12
+ * Auth: a signed `dsh-auth-*` cookie is required on every `/api/*` request and
13
+ * the `/api/remote.mux` WebSocket. The v1 cookie is minted from DSH's durable
14
+ * browser-session secret, or bootstrapped with `GET /?token=<launch token>`.
15
+ * Stream: WebSocket `/api/remote.mux` logical streams
16
+ * `{type:'open',streamId,endpoint:'session/follow',payload}` →
17
+ * `{type:'item',streamId,value}` / `{type:'end',streamId}`.
12
18
  */
13
19
  export {};
14
20
  //# sourceMappingURL=types.js.map
@@ -294,6 +294,11 @@ export class KimiAcpRunner extends BaseAcpRunner {
294
294
  return this.compactBaselineRecords.length;
295
295
  }
296
296
  onCompactPromptFired(promptPromise, translator, sessionId, opts, baselineTotalCount) {
297
+ // 本轮归属锚点(代际 + client),所有 settle 回调落地前复核;
298
+ // 旧轮迟到的 prompt 结算不得写进新轮的共享队列。
299
+ const generation = this.turnGeneration;
300
+ const client = this.currentClient;
301
+ const isCurrent = () => this.isCurrentTurn(generation, client);
297
302
  const compactionReader = this.sessionReader.readCompactionRecords;
298
303
  // Derived from the pre-fire snapshot captured by compactBaseline() —
299
304
  // re-reading here would race the prompt fire and misclassify new records.
@@ -302,6 +307,8 @@ export class KimiAcpRunner extends BaseAcpRunner {
302
307
  // prompt 结算只供两样东西:成功 result 的翻译素材(stopReason/usage)与 §5.4
303
308
  // 探针。终态判定不再挂在它后面。
304
309
  const promptSettled = promptPromise.then((result) => {
310
+ if (!isCurrent())
311
+ return undefined;
305
312
  this.promptSettled = true;
306
313
  if (compactionReader) {
307
314
  // §5.4 可选防线(日志探针):prompt settle 后既无新 begin 也无新
@@ -314,6 +321,8 @@ export class KimiAcpRunner extends BaseAcpRunner {
314
321
  }
315
322
  return result;
316
323
  }, (err) => {
324
+ if (!isCurrent())
325
+ return undefined;
317
326
  promptRejected = true;
318
327
  this.handlePromptRejected(err, translator, sessionId, 'compact');
319
328
  return undefined;
@@ -321,8 +330,9 @@ export class KimiAcpRunner extends BaseAcpRunner {
321
330
  if (!compactionReader) {
322
331
  // 读不到 compaction 记录(reader 未实现):沿用「prompt 结算即成功」的口径。
323
332
  void promptSettled.then((result) => {
324
- if (result)
325
- this.pushEvents([translator.handlePromptResponse(sessionId, result)]);
333
+ if (!result || !isCurrent())
334
+ return;
335
+ this.pushEvents([translator.handlePromptResponse(sessionId, result)]);
326
336
  });
327
337
  return;
328
338
  }
@@ -331,6 +341,8 @@ export class KimiAcpRunner extends BaseAcpRunner {
331
341
  // 用户拖到通用 turnIdleTimeout(默认 30min),拿到的还是一个语义泛化的 turn
332
342
  // 超时,而不是「压缩状态未知 / 可重试」。
333
343
  void this.waitForCompactionTerminal(compactionReader, opts, baselineTerminalCount).then(async (outcome) => {
344
+ if (!isCurrent())
345
+ return;
334
346
  // outcome === 'stopped':不推 result,由 consumeTurn 的 interrupted 兜底。
335
347
  if (outcome === 'stopped' || this.forceFinish || this.stopRequested)
336
348
  return;
@@ -351,7 +363,7 @@ export class KimiAcpRunner extends BaseAcpRunner {
351
363
  const result = await promptSettled;
352
364
  if (!result || promptRejected)
353
365
  return; // handlePromptRejected 已产 error result
354
- if (this.forceFinish || this.stopRequested)
366
+ if (!isCurrent() || this.forceFinish || this.stopRequested)
355
367
  return;
356
368
  this.pushEvents([translator.handlePromptResponse(sessionId, result)]);
357
369
  });
@@ -2,7 +2,7 @@
2
2
  * OpencodeLogErrorMonitor: watches opencode's own log file for LLM stream
3
3
  * errors of the active ACP session and reports them immediately.
4
4
  *
5
- * Why this exists: opencode ACP (v1.18.x) does NOT forward retry/error state
5
+ * Why this exists: opencode ACP (v1.18.x and v2.0.x) does NOT forward retry/error state
6
6
  * to the client over the ACP protocol. Its session/update notifications are
7
7
  * limited to content deltas (agent_message_chunk / agent_thought_chunk /
8
8
  * tool_call / tool_call_update), usage and available-commands updates — there
@@ -34,7 +34,7 @@ export interface OpencodeLogErrorMonitorOptions {
34
34
  * Resolve opencode's log file path from the XDG data dir
35
35
  * (`$XDG_DATA_HOME/opencode/log/opencode.log`, default
36
36
  * `~/.local/share/opencode/log/opencode.log`). Matches opencode's own
37
- * data-dir layout (open source v1.18.x: `opencode/src/cli/global.ts`).
37
+ * data-dir layout (verified against v1.18.x and v2.0.32 `debug paths`).
38
38
  */
39
39
  export declare function resolveOpencodeLogPath(): string;
40
40
  export declare class OpencodeLogErrorMonitor {