lark-remote 0.5.12 → 0.5.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -2
- package/README.en.md +1 -14
- package/README.md +1 -14
- package/dist/bridge/approval-coordinator.d.ts +1 -0
- package/dist/bridge/approval-coordinator.js +12 -24
- package/dist/bridge/index.d.ts +7 -0
- package/dist/bridge/index.js +77 -227
- package/dist/bridge/run-approval.d.ts +15 -0
- package/dist/bridge/run-approval.js +41 -0
- package/dist/bridge/run-usage.d.ts +46 -0
- package/dist/bridge/run-usage.js +96 -0
- package/dist/bridge/session-writeback.d.ts +18 -0
- package/dist/bridge/session-writeback.js +36 -0
- package/dist/card/bash-card-session.js +2 -5
- package/dist/card/card-session.d.ts +2 -0
- package/dist/card/card-session.js +20 -22
- package/dist/card/card-shared.d.ts +4 -4
- package/dist/card/card-shared.js +3 -9
- package/dist/card/collapsible.js +3 -2
- package/dist/card/final-answer-overflow.d.ts +7 -0
- package/dist/card/final-answer-overflow.js +46 -0
- package/dist/card/run-card-session.js +2 -7
- package/dist/card/run-renderer.d.ts +14 -0
- package/dist/card/run-renderer.js +119 -1
- package/dist/card/run-state.d.ts +12 -1
- package/dist/card/run-state.js +73 -34
- package/dist/config/codex-config.js +4 -4
- package/dist/config/index.d.ts +4 -2
- package/dist/config/index.js +6 -4
- package/dist/inbound/turn-assembler.js +6 -0
- package/dist/index.js +15 -409
- package/dist/message-handlers.d.ts +26 -0
- package/dist/message-handlers.js +275 -0
- package/dist/router/card-action-payload.d.ts +1 -1
- package/dist/router/card-action-payload.js +1 -1
- package/dist/router/commands/ls.d.ts +9 -0
- package/dist/router/commands/ls.js +243 -0
- package/dist/router/commands/order.d.ts +12 -0
- package/dist/router/commands/order.js +178 -0
- package/dist/router/commands/resume.d.ts +47 -0
- package/dist/router/commands/resume.js +246 -0
- package/dist/router/commands/ws.d.ts +15 -0
- package/dist/router/commands/ws.js +189 -0
- package/dist/router/config/claude.js +3 -3
- package/dist/router/config/codex.js +2 -6
- package/dist/router/config/common/turn-idle.d.ts +2 -0
- package/dist/router/config/common/turn-idle.js +9 -0
- package/dist/router/config/kimi.js +2 -6
- package/dist/router/config/opencode.js +2 -6
- package/dist/router/index.d.ts +14 -22
- package/dist/router/index.js +70 -1152
- package/dist/router/paging.d.ts +13 -0
- package/dist/router/paging.js +20 -0
- package/dist/router/path-validation.d.ts +27 -0
- package/dist/router/path-validation.js +45 -0
- package/dist/runner/claude/runner.d.ts +3 -4
- package/dist/runner/claude/runner.js +4 -5
- package/dist/runner/claude/session.js +1 -1
- package/dist/runner/codex/app-server/translator.d.ts +2 -0
- package/dist/runner/codex/app-server/translator.js +65 -9
- package/dist/runner/common/acp/base-translator.d.ts +3 -8
- package/dist/runner/common/acp/base-translator.js +16 -7
- package/dist/runner/common/spawning-runner.js +10 -4
- package/dist/runner/common/turn-started.d.ts +2 -0
- package/dist/runner/common/turn-started.js +10 -0
- package/dist/runner/dsh/auth.js +2 -3
- package/dist/runner/dsh/client.d.ts +1 -6
- package/dist/runner/dsh/client.js +11 -165
- package/dist/runner/dsh/daemon.d.ts +0 -2
- package/dist/runner/dsh/daemon.js +1 -6
- package/dist/runner/dsh/errors.d.ts +5 -0
- package/dist/runner/dsh/errors.js +11 -0
- package/dist/runner/dsh/exit-cancel.d.ts +0 -2
- package/dist/runner/dsh/exit-cancel.js +1 -1
- package/dist/runner/dsh/home.d.ts +2 -0
- package/dist/runner/dsh/home.js +8 -0
- package/dist/runner/dsh/inflight.d.ts +0 -2
- package/dist/runner/dsh/inflight.js +1 -1
- package/dist/runner/dsh/mux-stream.d.ts +26 -0
- package/dist/runner/dsh/mux-stream.js +157 -0
- package/dist/runner/dsh/translator.d.ts +0 -10
- package/dist/runner/dsh/translator.js +1 -1
- package/dist/runner/index.d.ts +1 -1
- package/dist/runner/kimi/acp/translator.js +1 -12
- package/dist/runner/opencode/acp/translator.js +1 -12
- package/dist/runner/pi/rpc/translator.d.ts +2 -8
- package/dist/runner/pi/rpc/translator.js +2 -7
- package/dist/runner/registry.d.ts +0 -19
- package/dist/runner/registry.js +0 -32
- package/dist/runner/types.d.ts +17 -1
- package/dist/session/claude/session-index.js +4 -2
- package/dist/session/kimi/sessions.js +2 -2
- package/dist/session/opencode/sessions.js +2 -1
- package/dist/session/pi/sessions.js +3 -2
- package/package.json +1 -1
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export declare function pageInfo(totalCount: number, offset: number, pageSize: number): {
|
|
2
|
+
totalPages: number;
|
|
3
|
+
safeOffset: number;
|
|
4
|
+
currentPage: number;
|
|
5
|
+
};
|
|
6
|
+
export declare function pageSlice<T>(items: readonly T[], offset: number, pageSize: number): {
|
|
7
|
+
totalCount: number;
|
|
8
|
+
totalPages: number;
|
|
9
|
+
safeOffset: number;
|
|
10
|
+
currentPage: number;
|
|
11
|
+
pageItems: T[];
|
|
12
|
+
hasPagination: boolean;
|
|
13
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { clampInt } from '../common/clamp.js';
|
|
2
|
+
export function pageInfo(totalCount, offset, pageSize) {
|
|
3
|
+
const totalPages = Math.max(1, Math.ceil(totalCount / pageSize));
|
|
4
|
+
const maxOffset = Math.max(0, (totalPages - 1) * pageSize);
|
|
5
|
+
const safeOffset = clampInt(offset, 0, maxOffset);
|
|
6
|
+
const currentPage = Math.floor(safeOffset / pageSize) + 1;
|
|
7
|
+
return { totalPages, safeOffset, currentPage };
|
|
8
|
+
}
|
|
9
|
+
export function pageSlice(items, offset, pageSize) {
|
|
10
|
+
const { totalPages, safeOffset, currentPage } = pageInfo(items.length, offset, pageSize);
|
|
11
|
+
return {
|
|
12
|
+
totalCount: items.length,
|
|
13
|
+
totalPages,
|
|
14
|
+
safeOffset,
|
|
15
|
+
currentPage,
|
|
16
|
+
pageItems: items.slice(safeOffset, safeOffset + pageSize),
|
|
17
|
+
hasPagination: items.length > pageSize,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=paging.js.map
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
export type PathKind = 'directory' | 'file' | 'missing' | 'unreadable';
|
|
3
|
+
export type StatResolution = {
|
|
4
|
+
ok: true;
|
|
5
|
+
stat: fs.Stats;
|
|
6
|
+
} | {
|
|
7
|
+
ok: false;
|
|
8
|
+
reason: 'missing' | 'unreadable';
|
|
9
|
+
};
|
|
10
|
+
export type DirectoryResolution = {
|
|
11
|
+
ok: true;
|
|
12
|
+
canonical: string;
|
|
13
|
+
} | {
|
|
14
|
+
ok: false;
|
|
15
|
+
reason: 'missing' | 'not_directory' | 'unreadable';
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Inspect a path without a preceding existsSync call.
|
|
19
|
+
*
|
|
20
|
+
* stat already distinguishes missing paths from files/directories and closes
|
|
21
|
+
* the exists→stat race. Permission and other IO failures stay distinguishable
|
|
22
|
+
* from a normal missing path so callers can keep their user-facing messages.
|
|
23
|
+
*/
|
|
24
|
+
export declare function statPath(target: string): StatResolution;
|
|
25
|
+
export declare function inspectPath(target: string): PathKind;
|
|
26
|
+
export declare function resolveDirectory(target: string): DirectoryResolution;
|
|
27
|
+
export declare function isDirectorySafe(target: string): boolean;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
/**
|
|
3
|
+
* Inspect a path without a preceding existsSync call.
|
|
4
|
+
*
|
|
5
|
+
* stat already distinguishes missing paths from files/directories and closes
|
|
6
|
+
* the exists→stat race. Permission and other IO failures stay distinguishable
|
|
7
|
+
* from a normal missing path so callers can keep their user-facing messages.
|
|
8
|
+
*/
|
|
9
|
+
export function statPath(target) {
|
|
10
|
+
try {
|
|
11
|
+
return { ok: true, stat: fs.statSync(target) };
|
|
12
|
+
}
|
|
13
|
+
catch (err) {
|
|
14
|
+
const code = err.code;
|
|
15
|
+
return {
|
|
16
|
+
ok: false,
|
|
17
|
+
reason: code === 'ENOENT' || code === 'ENOTDIR' ? 'missing' : 'unreadable',
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export function inspectPath(target) {
|
|
22
|
+
const resolution = statPath(target);
|
|
23
|
+
if (!resolution.ok)
|
|
24
|
+
return resolution.reason;
|
|
25
|
+
return resolution.stat.isDirectory() ? 'directory' : 'file';
|
|
26
|
+
}
|
|
27
|
+
export function resolveDirectory(target) {
|
|
28
|
+
const kind = inspectPath(target);
|
|
29
|
+
if (kind === 'missing')
|
|
30
|
+
return { ok: false, reason: 'missing' };
|
|
31
|
+
if (kind === 'unreadable')
|
|
32
|
+
return { ok: false, reason: 'unreadable' };
|
|
33
|
+
if (kind === 'file')
|
|
34
|
+
return { ok: false, reason: 'not_directory' };
|
|
35
|
+
try {
|
|
36
|
+
return { ok: true, canonical: fs.realpathSync(target) };
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return { ok: false, reason: 'unreadable' };
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
export function isDirectorySafe(target) {
|
|
43
|
+
return inspectPath(target) === 'directory';
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=path-validation.js.map
|
|
@@ -3,10 +3,9 @@ import { type AgentEvent, type SpawnOptions, type AgentSessionReader, type Agent
|
|
|
3
3
|
* Claude Agent Runner(长驻交互模式)。
|
|
4
4
|
*
|
|
5
5
|
* 权限模式(permissionMode)在 spawn 时经 --permission-mode 生效:
|
|
6
|
-
* - 默认
|
|
7
|
-
*
|
|
8
|
-
* -
|
|
9
|
-
* 上抛 → bridge ApprovalCoordinator → run 卡审批区。
|
|
6
|
+
* - 默认 auto:低风险操作自动放行,高风险操作的 control_request 上抛 →
|
|
7
|
+
* bridge ApprovalCoordinator → run 卡审批区;
|
|
8
|
+
* - bypassPermissions:claude 内部全部放行,无 control_request(无审批卡)。
|
|
10
9
|
*/
|
|
11
10
|
export declare class ClaudeRunner implements AgentRunner {
|
|
12
11
|
readonly kind: "claude";
|
|
@@ -24,10 +24,9 @@ const COMPACT_COMMAND = '/compact';
|
|
|
24
24
|
* Claude Agent Runner(长驻交互模式)。
|
|
25
25
|
*
|
|
26
26
|
* 权限模式(permissionMode)在 spawn 时经 --permission-mode 生效:
|
|
27
|
-
* - 默认
|
|
28
|
-
*
|
|
29
|
-
* -
|
|
30
|
-
* 上抛 → bridge ApprovalCoordinator → run 卡审批区。
|
|
27
|
+
* - 默认 auto:低风险操作自动放行,高风险操作的 control_request 上抛 →
|
|
28
|
+
* bridge ApprovalCoordinator → run 卡审批区;
|
|
29
|
+
* - bypassPermissions:claude 内部全部放行,无 control_request(无审批卡)。
|
|
31
30
|
*/
|
|
32
31
|
export class ClaudeRunner {
|
|
33
32
|
kind = 'claude';
|
|
@@ -40,7 +39,7 @@ export class ClaudeRunner {
|
|
|
40
39
|
constructor(opts) {
|
|
41
40
|
this.defaultModel = opts.model ?? 'claude-opus-4-8';
|
|
42
41
|
this.defaultEffort = opts.effort ?? 'medium';
|
|
43
|
-
this.permissionMode = opts.permissionMode ?? '
|
|
42
|
+
this.permissionMode = opts.permissionMode ?? 'auto';
|
|
44
43
|
this.sessionReader = opts.sessionReader ?? new ClaudeSessionReader();
|
|
45
44
|
this.session = new ClaudeSession({
|
|
46
45
|
pidDir: opts.pidDir,
|
|
@@ -100,7 +100,7 @@ export class ClaudeSession extends SpawningRunner {
|
|
|
100
100
|
agent: 'claude',
|
|
101
101
|
});
|
|
102
102
|
this.binary = 'claude';
|
|
103
|
-
this.permissionMode = opts.permissionMode ?? '
|
|
103
|
+
this.permissionMode = opts.permissionMode ?? 'auto';
|
|
104
104
|
this.settings = opts.settings;
|
|
105
105
|
this.defaultEffort = opts.effort;
|
|
106
106
|
this.idleTtlMs = opts.idleTtlMs ?? DEFAULT_IDLE_TTL_MS;
|
|
@@ -17,6 +17,8 @@ export declare class CodexAppServerTranslator {
|
|
|
17
17
|
private turnStatus;
|
|
18
18
|
/** Full agentMessage text per item id (item-scoped snapshot). */
|
|
19
19
|
private textByItem;
|
|
20
|
+
/** agentMessage item ids whose protocol phase is `final_answer`. */
|
|
21
|
+
private finalTextItems;
|
|
20
22
|
/**
|
|
21
23
|
* Reasoning content per item id: paragraphs keyed by `contentIndex`
|
|
22
24
|
* (textDelta) plus the summary stream (summaryTextDelta). Each reasoning
|
|
@@ -18,6 +18,8 @@ export class CodexAppServerTranslator {
|
|
|
18
18
|
turnStatus = 'pending';
|
|
19
19
|
/** Full agentMessage text per item id (item-scoped snapshot). */
|
|
20
20
|
textByItem = new Map();
|
|
21
|
+
/** agentMessage item ids whose protocol phase is `final_answer`. */
|
|
22
|
+
finalTextItems = new Set();
|
|
21
23
|
/**
|
|
22
24
|
* Reasoning content per item id: paragraphs keyed by `contentIndex`
|
|
23
25
|
* (textDelta) plus the summary stream (summaryTextDelta). Each reasoning
|
|
@@ -141,15 +143,24 @@ export class CodexAppServerTranslator {
|
|
|
141
143
|
const text = item.text ?? '';
|
|
142
144
|
if (text && this.textByItem.get(item.id) !== text) {
|
|
143
145
|
this.textByItem.set(item.id, text);
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
146
|
+
const final = item.phase === 'final_answer';
|
|
147
|
+
events.push(final
|
|
148
|
+
? {
|
|
149
|
+
type: 'final_answer',
|
|
150
|
+
text,
|
|
151
|
+
itemId: item.id,
|
|
152
|
+
complete: true,
|
|
153
|
+
timestamp: this.now(),
|
|
154
|
+
}
|
|
155
|
+
: {
|
|
156
|
+
type: 'turn_diff',
|
|
157
|
+
text,
|
|
158
|
+
itemId: item.id,
|
|
159
|
+
complete: true,
|
|
160
|
+
threadId: params?.threadId ?? this.threadId,
|
|
161
|
+
turnId: params?.turn?.id ?? this.turnId,
|
|
162
|
+
timestamp: this.now(),
|
|
163
|
+
});
|
|
153
164
|
}
|
|
154
165
|
}
|
|
155
166
|
else if (item.type === 'plan') {
|
|
@@ -228,6 +239,7 @@ export class CodexAppServerTranslator {
|
|
|
228
239
|
// 的 turnId 闸门挡掉。
|
|
229
240
|
this.usage = { inputTokens: 0, outputTokens: 0 };
|
|
230
241
|
this.textByItem.clear();
|
|
242
|
+
this.finalTextItems.clear();
|
|
231
243
|
this.reasoningByItem.clear();
|
|
232
244
|
this.toolOutputByItem.clear();
|
|
233
245
|
this.toolItemMeta.clear();
|
|
@@ -249,6 +261,17 @@ export class CodexAppServerTranslator {
|
|
|
249
261
|
const prev = this.textByItem.get(params.itemId) ?? '';
|
|
250
262
|
const next = prev + params.delta;
|
|
251
263
|
this.textByItem.set(params.itemId, next);
|
|
264
|
+
if (this.finalTextItems.has(params.itemId)) {
|
|
265
|
+
return [
|
|
266
|
+
{
|
|
267
|
+
type: 'final_answer',
|
|
268
|
+
text: next,
|
|
269
|
+
itemId: params.itemId,
|
|
270
|
+
complete: false,
|
|
271
|
+
timestamp: this.now(),
|
|
272
|
+
},
|
|
273
|
+
];
|
|
274
|
+
}
|
|
252
275
|
return [
|
|
253
276
|
{
|
|
254
277
|
type: 'turn_diff',
|
|
@@ -358,6 +381,19 @@ export class CodexAppServerTranslator {
|
|
|
358
381
|
if (text || accumulated) {
|
|
359
382
|
const finalText = text || accumulated;
|
|
360
383
|
this.textByItem.set(item.id, finalText);
|
|
384
|
+
if (item.phase === 'final_answer' ||
|
|
385
|
+
this.finalTextItems.has(item.id)) {
|
|
386
|
+
this.finalTextItems.delete(item.id);
|
|
387
|
+
return [
|
|
388
|
+
{
|
|
389
|
+
type: 'final_answer',
|
|
390
|
+
text: finalText,
|
|
391
|
+
itemId: item.id,
|
|
392
|
+
complete: true,
|
|
393
|
+
timestamp: this.now(),
|
|
394
|
+
},
|
|
395
|
+
];
|
|
396
|
+
}
|
|
361
397
|
return [
|
|
362
398
|
{
|
|
363
399
|
type: 'turn_diff',
|
|
@@ -635,6 +671,26 @@ export class CodexAppServerTranslator {
|
|
|
635
671
|
}
|
|
636
672
|
handleItemStarted(params) {
|
|
637
673
|
const item = params.item;
|
|
674
|
+
if (item.type === 'agentMessage') {
|
|
675
|
+
const phase = item.phase;
|
|
676
|
+
if (phase === 'final_answer') {
|
|
677
|
+
this.finalTextItems.add(item.id);
|
|
678
|
+
const text = item.text ?? '';
|
|
679
|
+
if (text) {
|
|
680
|
+
this.textByItem.set(item.id, text);
|
|
681
|
+
return [
|
|
682
|
+
{
|
|
683
|
+
type: 'final_answer',
|
|
684
|
+
text,
|
|
685
|
+
itemId: item.id,
|
|
686
|
+
complete: false,
|
|
687
|
+
timestamp: this.now(),
|
|
688
|
+
},
|
|
689
|
+
];
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
return [];
|
|
693
|
+
}
|
|
638
694
|
if (item.type === 'fileChange' && Array.isArray(item.changes)) {
|
|
639
695
|
this.fileChangeItems.set(item.id, item.changes);
|
|
640
696
|
const pending = this.pendingFileApprovalByItemId.get(item.id);
|
|
@@ -19,16 +19,10 @@
|
|
|
19
19
|
* buildToolUseUpdate — tool_use patch from tool_call_update
|
|
20
20
|
* onUsageSample — extra usage bookkeeping (opencode: cost)
|
|
21
21
|
*/
|
|
22
|
-
import type { AgentEvent, ApprovalRequestedEvent, ResultEvent } from '../../types.js';
|
|
22
|
+
import type { AgentEvent, ApprovalRequestedEvent, ApprovalView, ResultEvent, TurnStartedEvent } from '../../types.js';
|
|
23
23
|
import { type AgentMessageChunkEvent, type AgentThoughtChunkEvent, type ToolCallEvent, type ToolCallUpdateEvent, type UsageUpdateEvent, type RequestPermissionParams, type AcpPlanEvent } from './protocol-types.js';
|
|
24
24
|
/** Self-produced turn_started event (no wire equivalent — produced on prompt start). */
|
|
25
|
-
export
|
|
26
|
-
type: 'turn_started';
|
|
27
|
-
threadId: string;
|
|
28
|
-
turnId: string;
|
|
29
|
-
operationKind: 'turn' | 'compaction';
|
|
30
|
-
timestamp?: string;
|
|
31
|
-
}
|
|
25
|
+
export type AcpTurnStartedEvent = TurnStartedEvent;
|
|
32
26
|
/**
|
|
33
27
|
* Live usage snapshot from usage_update. The ACP wire carries context
|
|
34
28
|
* occupancy (`used`) and the model context window (`size`) — there is NO
|
|
@@ -96,6 +90,7 @@ export declare abstract class BaseAcpTranslator {
|
|
|
96
90
|
* is sent (no wire notification for this — self-produced).
|
|
97
91
|
*/
|
|
98
92
|
produceTurnStarted(sessionId: string, turnId: string): AcpTurnStartedEvent;
|
|
93
|
+
protected approvalRequestedEvents(requestId: number | string, sessionId: string, view: ApprovalView): ApprovalRequestedEvent[];
|
|
99
94
|
/**
|
|
100
95
|
* Translate a prompt response into a result event.
|
|
101
96
|
* stopReason: end_turn → success; cancelled → interrupted (independent
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { makeTurnStartedEvent } from '../turn-started.js';
|
|
1
2
|
import { NotificationMethod, ServerRequestMethod, SessionEventType, } from './protocol-types.js';
|
|
2
3
|
/**
|
|
3
4
|
* ACP tool kind → 常用工具名映射(信息保真,数据驱动无 agent 分支)。
|
|
@@ -145,13 +146,21 @@ export class BaseAcpTranslator {
|
|
|
145
146
|
*/
|
|
146
147
|
produceTurnStarted(sessionId, turnId) {
|
|
147
148
|
this.currentTurnId = turnId;
|
|
148
|
-
return
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
149
|
+
return makeTurnStartedEvent(sessionId, turnId, this.operationKind);
|
|
150
|
+
}
|
|
151
|
+
approvalRequestedEvents(requestId, sessionId, view) {
|
|
152
|
+
return [
|
|
153
|
+
{
|
|
154
|
+
type: 'approval_requested',
|
|
155
|
+
requestId,
|
|
156
|
+
kind: 'command',
|
|
157
|
+
threadId: sessionId,
|
|
158
|
+
turnId: '',
|
|
159
|
+
itemId: '',
|
|
160
|
+
view,
|
|
161
|
+
timestamp: new Date().toISOString(),
|
|
162
|
+
},
|
|
163
|
+
];
|
|
155
164
|
}
|
|
156
165
|
/**
|
|
157
166
|
* Translate a prompt response into a result event.
|
|
@@ -374,13 +374,19 @@ export class SpawningRunner {
|
|
|
374
374
|
try {
|
|
375
375
|
process.kill(-pid, 'SIGTERM');
|
|
376
376
|
}
|
|
377
|
-
catch {
|
|
378
|
-
|
|
377
|
+
catch (err) {
|
|
378
|
+
const code = err.code;
|
|
379
|
+
if (code === 'ESRCH') {
|
|
380
|
+
getLogger().debug(`[${this.logTag}] orphan process group ${-pid} already exited`);
|
|
381
|
+
}
|
|
382
|
+
else {
|
|
383
|
+
getLogger().warn(`[${this.logTag}] killOrphan failed to signal process group ${-pid}: ${err?.message ?? String(err)}`);
|
|
384
|
+
}
|
|
379
385
|
}
|
|
380
386
|
bestEffortUnlink(this.pidFilePath);
|
|
381
387
|
}
|
|
382
|
-
catch {
|
|
383
|
-
|
|
388
|
+
catch (err) {
|
|
389
|
+
getLogger().warn(`[${this.logTag}] killOrphan failed for ${this.pidFilePath}: ${err?.message ?? String(err)}`);
|
|
384
390
|
}
|
|
385
391
|
}
|
|
386
392
|
/**
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export function makeTurnStartedEvent(sessionId, turnId, operationKind) {
|
|
2
|
+
return {
|
|
3
|
+
type: 'turn_started',
|
|
4
|
+
threadId: sessionId,
|
|
5
|
+
turnId,
|
|
6
|
+
operationKind: operationKind === 'compact' ? 'compaction' : 'turn',
|
|
7
|
+
timestamp: new Date().toISOString(),
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=turn-started.js.map
|
package/dist/runner/dsh/auth.js
CHANGED
|
@@ -10,10 +10,10 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import { createHash, createHmac } from 'node:crypto';
|
|
12
12
|
import fs from 'node:fs';
|
|
13
|
-
import os from 'node:os';
|
|
14
13
|
import path from 'node:path';
|
|
15
14
|
import YAML from 'yaml';
|
|
16
15
|
import { isWin32 } from '../../platform/select.js';
|
|
16
|
+
import { resolveDshHome } from './home.js';
|
|
17
17
|
/** Credential record that owns the browser-cookie signing secret. */
|
|
18
18
|
export const DSH_BROWSER_CREDENTIAL_KEY = 'client-connection/browser-session';
|
|
19
19
|
/** Short lifetime keeps every request safely inside DSH's configurable max age. */
|
|
@@ -29,8 +29,7 @@ function isRecord(value) {
|
|
|
29
29
|
* file matches the `DSH_HOME` lark-remote passes to a process it starts.
|
|
30
30
|
*/
|
|
31
31
|
export function resolveDshCredentialsFile(env = process.env, configuredHome) {
|
|
32
|
-
|
|
33
|
-
return path.join(home && home.length > 0 ? home : path.join(os.homedir(), '.dsh'), '.credentials.yaml');
|
|
32
|
+
return path.join(resolveDshHome(configuredHome, env), '.credentials.yaml');
|
|
34
33
|
}
|
|
35
34
|
/** Whether a filesystem error means the credentials file is absent. */
|
|
36
35
|
export function isDshCredentialsNotFound(error) {
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
*/
|
|
22
22
|
import type { DshApprovalStreamItem, DshCommandResult, DshModelCatalogValue, DshModelSelection, DshOpenTurnState, DshPermissionCatalogValue, DshJobView, DshPresetListValue, DshProjectionItem, DshProjectionSnapshot, DshQuestion, DshRemoteEventOutcome, DshSessionPageValue, DshStreamItem, DshTokenUsage } from './types.js';
|
|
23
23
|
import type { GoalView } from '../types.js';
|
|
24
|
+
export { DshError } from './errors.js';
|
|
24
25
|
interface WebSocketMessageEvent {
|
|
25
26
|
data: unknown;
|
|
26
27
|
}
|
|
@@ -40,11 +41,6 @@ export interface WebSocketLike {
|
|
|
40
41
|
close(): void;
|
|
41
42
|
}
|
|
42
43
|
export type WebSocketFactory = (url: string, options?: WebSocketOptions) => WebSocketLike;
|
|
43
|
-
export declare class DshError extends Error {
|
|
44
|
-
readonly code: string;
|
|
45
|
-
readonly details: Record<string, unknown>;
|
|
46
|
-
constructor(code: string, message: string, details?: Record<string, unknown>);
|
|
47
|
-
}
|
|
48
44
|
export interface DshClientOptions {
|
|
49
45
|
/** Configured DSH home; selects `.credentials.yaml` when no explicit file is given. */
|
|
50
46
|
dshHome?: string;
|
|
@@ -372,4 +368,3 @@ export declare function dshQuestionsOf(request: Record<string, unknown>): DshQue
|
|
|
372
368
|
* silently on the server side.
|
|
373
369
|
*/
|
|
374
370
|
export declare function isTimedQuestionRequest(request: Record<string, unknown>): boolean;
|
|
375
|
-
export {};
|
|
@@ -25,6 +25,9 @@ import { getLogger } from '../../logger/index.js';
|
|
|
25
25
|
import { DSH_COOKIE_LIFETIME_MS, isDshCredentialsNotFound, mintDshBrowserCookie, readDshBrowserSecret, resolveDshCredentialsFile, } from './auth.js';
|
|
26
26
|
import { DEFAULT_REQUEST_TIMEOUT_MS } from '../common/defaults.js';
|
|
27
27
|
import { goalFromProjection } from './goal-view.js';
|
|
28
|
+
import { DshError } from './errors.js';
|
|
29
|
+
import { openMuxStream } from './mux-stream.js';
|
|
30
|
+
export { DshError } from './errors.js';
|
|
28
31
|
function defaultWebSocketFactory(url, options) {
|
|
29
32
|
const socket = new WebSocket(url, {
|
|
30
33
|
...(options?.headers ? { headers: options.headers } : {}),
|
|
@@ -35,16 +38,6 @@ function defaultWebSocketFactory(url, options) {
|
|
|
35
38
|
});
|
|
36
39
|
return socket;
|
|
37
40
|
}
|
|
38
|
-
export class DshError extends Error {
|
|
39
|
-
code;
|
|
40
|
-
details;
|
|
41
|
-
constructor(code, message, details = {}) {
|
|
42
|
-
super(message);
|
|
43
|
-
this.name = 'DshError';
|
|
44
|
-
this.code = code;
|
|
45
|
-
this.details = details;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
41
|
function isDshAuthError(error) {
|
|
49
42
|
return (error instanceof DshError &&
|
|
50
43
|
error.code === 'http' &&
|
|
@@ -602,162 +595,15 @@ export class DshClient {
|
|
|
602
595
|
* close, error, or `signal` abort.
|
|
603
596
|
*/
|
|
604
597
|
async *muxStream(endpoint, payload, signal) {
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
const settle = (frame) => {
|
|
614
|
-
if (waiter) {
|
|
615
|
-
const resolve = waiter;
|
|
616
|
-
waiter = null;
|
|
617
|
-
resolve(frame);
|
|
618
|
-
return;
|
|
619
|
-
}
|
|
620
|
-
if (frame)
|
|
621
|
-
queue.push(frame);
|
|
622
|
-
else
|
|
623
|
-
ended = true;
|
|
624
|
-
};
|
|
625
|
-
// 建连/首帧超时:服务器接受 TCP 但不完成 WS 握手时 onopen/onerror/onclose
|
|
626
|
-
// 都不触发,generator 会永远停在 await next()——unary 挂起有 60s 预算与
|
|
627
|
-
// 回归测试(test_anchor_dsh_stop_with_hung_unary_releases_run),mux 的
|
|
628
|
-
// 同型问题此前只能靠 idle 看门狗兜底。超时按传输错误走既有重连路径。
|
|
629
|
-
const connectTimer = setTimeout(() => {
|
|
630
|
-
if (opened || ended)
|
|
631
|
-
return;
|
|
632
|
-
failure = new DshError('transport', 'DSH remote mux WebSocket connect timeout');
|
|
633
|
-
ended = true;
|
|
634
|
-
settle(undefined);
|
|
635
|
-
try {
|
|
636
|
-
socket.close();
|
|
637
|
-
}
|
|
638
|
-
catch {
|
|
639
|
-
/* ignore close error */
|
|
640
|
-
}
|
|
641
|
-
}, this.requestTimeoutMs);
|
|
642
|
-
connectTimer.unref?.();
|
|
643
|
-
socket.onopen = () => {
|
|
644
|
-
opened = true;
|
|
645
|
-
clearTimeout(connectTimer);
|
|
646
|
-
if (ended)
|
|
647
|
-
return;
|
|
648
|
-
try {
|
|
649
|
-
socket.send(JSON.stringify({
|
|
650
|
-
type: 'open',
|
|
651
|
-
streamId,
|
|
652
|
-
endpoint,
|
|
653
|
-
payload,
|
|
654
|
-
}));
|
|
655
|
-
}
|
|
656
|
-
catch (err) {
|
|
657
|
-
failure = new DshError('transport', `DSH remote mux send error: ${err.message}`);
|
|
658
|
-
ended = true;
|
|
659
|
-
settle(undefined);
|
|
660
|
-
}
|
|
661
|
-
};
|
|
662
|
-
socket.onmessage = (event) => {
|
|
663
|
-
if (ended)
|
|
664
|
-
return;
|
|
665
|
-
const text = typeof event.data === 'string'
|
|
666
|
-
? event.data
|
|
667
|
-
: Buffer.isBuffer(event.data)
|
|
668
|
-
? event.data.toString('utf8')
|
|
669
|
-
: String(event.data);
|
|
670
|
-
let message;
|
|
671
|
-
try {
|
|
672
|
-
message = JSON.parse(text);
|
|
673
|
-
}
|
|
674
|
-
catch {
|
|
675
|
-
getLogger().warn('[dsh] dropped malformed remote mux frame');
|
|
676
|
-
return;
|
|
677
|
-
}
|
|
678
|
-
if (message.streamId !== streamId)
|
|
679
|
-
return;
|
|
680
|
-
if (message.type === 'item' && message.value) {
|
|
681
|
-
settle(message.value);
|
|
682
|
-
return;
|
|
683
|
-
}
|
|
684
|
-
if (message.type === 'end') {
|
|
685
|
-
ended = true;
|
|
686
|
-
settle(undefined);
|
|
687
|
-
return;
|
|
688
|
-
}
|
|
689
|
-
if (message.type === 'error') {
|
|
690
|
-
failure = new DshError(message.error?.code ?? 'stream', message.error?.message ?? 'DSH remote stream failed', message.error?.details ?? {});
|
|
691
|
-
ended = true;
|
|
692
|
-
settle(undefined);
|
|
693
|
-
}
|
|
694
|
-
};
|
|
695
|
-
socket.onerror = () => {
|
|
696
|
-
if (ended)
|
|
697
|
-
return;
|
|
698
|
-
failure = new DshError('transport', 'DSH remote mux WebSocket error');
|
|
699
|
-
ended = true;
|
|
700
|
-
settle(undefined);
|
|
701
|
-
};
|
|
702
|
-
socket.onunexpectedresponse = (event) => {
|
|
703
|
-
if (ended)
|
|
704
|
-
return;
|
|
705
|
-
const status = event.statusCode;
|
|
706
|
-
failure = new DshError('http', `DSH remote mux WebSocket HTTP ${status === undefined ? 'error' : status}`, status === undefined ? {} : { status });
|
|
707
|
-
ended = true;
|
|
708
|
-
settle(undefined);
|
|
709
|
-
};
|
|
710
|
-
socket.onclose = () => {
|
|
711
|
-
if (ended)
|
|
712
|
-
return;
|
|
713
|
-
ended = true;
|
|
714
|
-
settle(undefined);
|
|
715
|
-
};
|
|
716
|
-
const onAbort = () => {
|
|
717
|
-
if (ended)
|
|
718
|
-
return;
|
|
719
|
-
ended = true;
|
|
720
|
-
settle(undefined);
|
|
721
|
-
try {
|
|
722
|
-
socket.close();
|
|
723
|
-
}
|
|
724
|
-
catch {
|
|
725
|
-
/* ignore close error */
|
|
726
|
-
}
|
|
727
|
-
};
|
|
728
|
-
signal.addEventListener('abort', onAbort, { once: true });
|
|
729
|
-
const next = () => new Promise((resolve) => {
|
|
730
|
-
if (queue.length > 0) {
|
|
731
|
-
resolve(queue.shift());
|
|
732
|
-
return;
|
|
733
|
-
}
|
|
734
|
-
if (ended) {
|
|
735
|
-
resolve(undefined);
|
|
736
|
-
return;
|
|
737
|
-
}
|
|
738
|
-
waiter = resolve;
|
|
598
|
+
yield* openMuxStream({
|
|
599
|
+
baseUrl: this.baseUrl,
|
|
600
|
+
endpoint,
|
|
601
|
+
payload,
|
|
602
|
+
signal,
|
|
603
|
+
requestTimeoutMs: this.requestTimeoutMs,
|
|
604
|
+
authCookie: () => this.authCookie(),
|
|
605
|
+
webSocketFactory: this.webSocketImpl,
|
|
739
606
|
});
|
|
740
|
-
try {
|
|
741
|
-
while (!signal.aborted) {
|
|
742
|
-
const frame = await next();
|
|
743
|
-
if (frame === undefined) {
|
|
744
|
-
if (failure && !signal.aborted)
|
|
745
|
-
throw failure;
|
|
746
|
-
return;
|
|
747
|
-
}
|
|
748
|
-
yield frame;
|
|
749
|
-
}
|
|
750
|
-
}
|
|
751
|
-
finally {
|
|
752
|
-
clearTimeout(connectTimer);
|
|
753
|
-
signal.removeEventListener('abort', onAbort);
|
|
754
|
-
try {
|
|
755
|
-
socket.close();
|
|
756
|
-
}
|
|
757
|
-
catch {
|
|
758
|
-
/* ignore close error */
|
|
759
|
-
}
|
|
760
|
-
}
|
|
761
607
|
}
|
|
762
608
|
/** One logical `session/follow` stream. */
|
|
763
609
|
async *followStream(sessionId, signal) {
|
|
@@ -28,8 +28,6 @@ interface SpawnedDaemon {
|
|
|
28
28
|
failure(): Error | undefined;
|
|
29
29
|
exited(): boolean;
|
|
30
30
|
}
|
|
31
|
-
/** Resolve the same Harness home DSH resolves: configured, env, then ~/.dsh. */
|
|
32
|
-
export declare function resolveDshHome(configured?: string, env?: NodeJS.ProcessEnv): string;
|
|
33
31
|
/** True only for the loopback HTTP address this process may start and probe. */
|
|
34
32
|
export declare function isLoopbackHttpUrl(value: string): boolean;
|
|
35
33
|
/**
|
|
@@ -12,10 +12,10 @@
|
|
|
12
12
|
*/
|
|
13
13
|
import fs from 'node:fs';
|
|
14
14
|
import fsp from 'node:fs/promises';
|
|
15
|
-
import os from 'node:os';
|
|
16
15
|
import path from 'node:path';
|
|
17
16
|
import { DEFAULT_DSH_HOST } from '../../config/index.js';
|
|
18
17
|
import { mergeProcessEnv, spawnProcess } from '../../platform/spawn.js';
|
|
18
|
+
import { resolveDshHome } from './home.js';
|
|
19
19
|
const DEFAULT_START_TIMEOUT_MS = 30_000;
|
|
20
20
|
const READY_POLL_MS = 100;
|
|
21
21
|
/** User-facing daemon failure; the bridge renders `message` in the chat card. */
|
|
@@ -30,11 +30,6 @@ export class DshDaemonError extends Error {
|
|
|
30
30
|
function stripTrailingSlash(value) {
|
|
31
31
|
return value.replace(/\/+$/, '');
|
|
32
32
|
}
|
|
33
|
-
/** Resolve the same Harness home DSH resolves: configured, env, then ~/.dsh. */
|
|
34
|
-
export function resolveDshHome(configured, env = process.env) {
|
|
35
|
-
const value = configured?.trim() || env.DSH_HOME?.trim();
|
|
36
|
-
return path.resolve(value && value.length > 0 ? value : path.join(os.homedir(), '.dsh'));
|
|
37
|
-
}
|
|
38
33
|
/** True only for the loopback HTTP address this process may start and probe. */
|
|
39
34
|
export function isLoopbackHttpUrl(value) {
|
|
40
35
|
try {
|