lark-remote 0.5.17 → 0.5.19
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 +29 -0
- package/dist/bridge/index.d.ts +66 -6
- package/dist/bridge/index.js +225 -35
- package/dist/card/bash-card-session.js +2 -0
- package/dist/card/card-session.d.ts +17 -0
- package/dist/card/card-session.js +35 -0
- package/dist/card/final-answer-file.d.ts +9 -0
- package/dist/card/final-answer-file.js +42 -0
- package/dist/card/run-card-session.js +2 -0
- package/dist/card/run-renderer.d.ts +2 -2
- package/dist/card/run-renderer.js +56 -24
- package/dist/card/run-state.d.ts +34 -7
- package/dist/card/run-state.js +160 -7
- package/dist/dsh/types.d.ts +8 -0
- package/dist/message-handlers.js +3 -1
- package/dist/router/index.js +3 -2
- package/dist/runner/dsh/client.d.ts +12 -5
- package/dist/runner/dsh/client.js +21 -11
- package/dist/runner/dsh/runner.d.ts +8 -1
- package/dist/runner/dsh/runner.js +16 -0
- package/dist/runner/dsh/translator.d.ts +8 -0
- package/dist/runner/dsh/translator.js +36 -0
- package/dist/runner/index.d.ts +1 -1
- package/dist/runner/types.d.ts +34 -6
- package/dist/session/dsh/sessions.d.ts +7 -2
- package/dist/session/dsh/sessions.js +24 -3
- package/package.json +1 -1
- package/dist/card/final-answer-overflow.d.ts +0 -1
- package/dist/card/final-answer-overflow.js +0 -46
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
* back to the Web UI prompt.
|
|
19
19
|
*/
|
|
20
20
|
import type { AgentSessionReader } from '../../session-contract.js';
|
|
21
|
-
import type { AgentEvent, AgentStatusInfo, SpawnOptions, StopOutcome } from '../types.js';
|
|
21
|
+
import type { AgentEvent, AgentStatusInfo, BackgroundJobRoster, SpawnOptions, StopOutcome } from '../types.js';
|
|
22
22
|
import type { AgentRunner } from '../types.js';
|
|
23
23
|
import { DshClient, type WebSocketFactory } from './client.js';
|
|
24
24
|
import type { DshDaemon } from './daemon.js';
|
|
@@ -169,6 +169,13 @@ export declare class DshRunner implements AgentRunner {
|
|
|
169
169
|
* 任务列表不等于有任务在跑。
|
|
170
170
|
*/
|
|
171
171
|
private collectBackgroundJobs;
|
|
172
|
+
/**
|
|
173
|
+
* 订阅本 session 的后台任务名单(跨 turn 生命周期)。
|
|
174
|
+
*
|
|
175
|
+
* 卡片收尾后仍要靠它把「任务结束了」推给用户:run 的事件流在 result 就关了,
|
|
176
|
+
* 而后台任务可能再跑半小时。名单是整份替换,消费方自己做 diff。
|
|
177
|
+
*/
|
|
178
|
+
watchJobs(sessionId: string, signal: AbortSignal): AsyncGenerator<ReadonlyArray<BackgroundJobRoster>>;
|
|
172
179
|
/** Kill one background job of this runner's session (card button path). */
|
|
173
180
|
killJob(sessionId: string, jobId: string): Promise<boolean>;
|
|
174
181
|
/** Apply the configured permission preset once per session, before prompt. */
|
|
@@ -796,6 +796,22 @@ export class DshRunner {
|
|
|
796
796
|
},
|
|
797
797
|
];
|
|
798
798
|
}
|
|
799
|
+
/**
|
|
800
|
+
* 订阅本 session 的后台任务名单(跨 turn 生命周期)。
|
|
801
|
+
*
|
|
802
|
+
* 卡片收尾后仍要靠它把「任务结束了」推给用户:run 的事件流在 result 就关了,
|
|
803
|
+
* 而后台任务可能再跑半小时。名单是整份替换,消费方自己做 diff。
|
|
804
|
+
*/
|
|
805
|
+
async *watchJobs(sessionId, signal) {
|
|
806
|
+
for await (const rows of this.client.watchJobs(sessionId, signal)) {
|
|
807
|
+
yield rows.map((job) => ({
|
|
808
|
+
id: job.id,
|
|
809
|
+
label: job.label ?? job.id,
|
|
810
|
+
...(job.status === undefined ? {} : { status: job.status }),
|
|
811
|
+
...(job.progress === undefined ? {} : { progress: job.progress }),
|
|
812
|
+
}));
|
|
813
|
+
}
|
|
814
|
+
}
|
|
799
815
|
/** Kill one background job of this runner's session (card button path). */
|
|
800
816
|
async killJob(sessionId, jobId) {
|
|
801
817
|
return await this.client.jobKill(sessionId, jobId);
|
|
@@ -36,6 +36,14 @@ export declare class DshTranslator {
|
|
|
36
36
|
private hasStreamUsage;
|
|
37
37
|
/** 当前 attempt 的 id(start 帧携带,chunk 帧也带;断开重连重放时不变)。 */
|
|
38
38
|
private attemptId;
|
|
39
|
+
/**
|
|
40
|
+
* 本 turn 末条 durable assistant 消息的正文(**完整**、未截断)。
|
|
41
|
+
*
|
|
42
|
+
* live chunk 是逐 delta 拼接、会被状态层的 MAX_TEXT_CHARS 窗口截断;
|
|
43
|
+
* durable `assistant/message` 一次就到齐全文。收 turn 时把它挂在 result 上,
|
|
44
|
+
* 卡片才能渲染完整回答(2026-11-01 输出块被截断缺陷)。
|
|
45
|
+
*/
|
|
46
|
+
private lastMessageText?;
|
|
39
47
|
/** Translate one live assistant-stream chunk into AgentEvents. */
|
|
40
48
|
streamFrameToAgentEvents(frame: DshAssistantStreamFrame, _sessionId: string): AgentEvent[];
|
|
41
49
|
/**
|
|
@@ -34,6 +34,14 @@ export class DshTranslator {
|
|
|
34
34
|
hasStreamUsage = false;
|
|
35
35
|
/** 当前 attempt 的 id(start 帧携带,chunk 帧也带;断开重连重放时不变)。 */
|
|
36
36
|
attemptId = '0';
|
|
37
|
+
/**
|
|
38
|
+
* 本 turn 末条 durable assistant 消息的正文(**完整**、未截断)。
|
|
39
|
+
*
|
|
40
|
+
* live chunk 是逐 delta 拼接、会被状态层的 MAX_TEXT_CHARS 窗口截断;
|
|
41
|
+
* durable `assistant/message` 一次就到齐全文。收 turn 时把它挂在 result 上,
|
|
42
|
+
* 卡片才能渲染完整回答(2026-11-01 输出块被截断缺陷)。
|
|
43
|
+
*/
|
|
44
|
+
lastMessageText;
|
|
37
45
|
/** Translate one live assistant-stream chunk into AgentEvents. */
|
|
38
46
|
streamFrameToAgentEvents(frame, _sessionId) {
|
|
39
47
|
if (frame.type === 'start') {
|
|
@@ -116,6 +124,10 @@ export class DshTranslator {
|
|
|
116
124
|
if (raw && typeof raw === 'object') {
|
|
117
125
|
this.recordMessageUsage(raw);
|
|
118
126
|
}
|
|
127
|
+
// 每条 durable 消息都带完整正文;记最后一条非空的就是本轮回答。
|
|
128
|
+
const text = messageText(ev.data.message);
|
|
129
|
+
if (text.trim())
|
|
130
|
+
this.lastMessageText = text;
|
|
119
131
|
return [];
|
|
120
132
|
}
|
|
121
133
|
case 'tool/call': {
|
|
@@ -188,6 +200,9 @@ export class DshTranslator {
|
|
|
188
200
|
? `turn ended with reason: ${reason}: ${detail}`
|
|
189
201
|
: `turn ended with reason: ${reason}`;
|
|
190
202
|
}
|
|
203
|
+
if (this.lastMessageText) {
|
|
204
|
+
result.finalAnswerText = this.lastMessageText;
|
|
205
|
+
}
|
|
191
206
|
return [result];
|
|
192
207
|
}
|
|
193
208
|
default:
|
|
@@ -214,6 +229,27 @@ function parseToolArguments(raw) {
|
|
|
214
229
|
return trimmed;
|
|
215
230
|
}
|
|
216
231
|
}
|
|
232
|
+
/**
|
|
233
|
+
* 取 `assistant/message.message.content` 里的正文(type==='text' 的块)。
|
|
234
|
+
*
|
|
235
|
+
* 只收 text 块:reasoning 走独立面板(且另有 4000 字符窗口),tool-call 由
|
|
236
|
+
* `tool/call` 事件承载。块之间直接拼接、不插分隔符——同一条消息里的多个 text
|
|
237
|
+
* 块本来就是一段连续回答。
|
|
238
|
+
*/
|
|
239
|
+
function messageText(message) {
|
|
240
|
+
const content = message?.content;
|
|
241
|
+
if (!Array.isArray(content))
|
|
242
|
+
return '';
|
|
243
|
+
const parts = [];
|
|
244
|
+
for (const block of content) {
|
|
245
|
+
if (!block || typeof block !== 'object')
|
|
246
|
+
continue;
|
|
247
|
+
const rec = block;
|
|
248
|
+
if (rec.type === 'text' && typeof rec.text === 'string')
|
|
249
|
+
parts.push(rec.text);
|
|
250
|
+
}
|
|
251
|
+
return parts.join('');
|
|
252
|
+
}
|
|
217
253
|
/**
|
|
218
254
|
* Flatten a `tool/result.message.content` block list into display text.
|
|
219
255
|
*
|
package/dist/runner/index.d.ts
CHANGED
|
@@ -15,6 +15,6 @@ export { KimiAcpRunner } from './kimi/index.js';
|
|
|
15
15
|
export { DshRunner } from './dsh/index.js';
|
|
16
16
|
export { BashProcessRunner, type BashRunner } from './bash/index.js';
|
|
17
17
|
export { AgentRegistry } from './registry.js';
|
|
18
|
-
export type { ResultEvent, PlanEvent, FileChangeEvent, AgentEvent, Runner, RunnerLifetime, AgentRunner, TurnStartedEvent, TurnDiffEvent, FinalAnswerEvent, NoticeEvent, SessionInfoEvent, ToolHint, ApprovalView, ApprovalRequestedEvent, ApprovalResolvedEvent, GoalView, GoalStateEvent, } from './types.js';
|
|
18
|
+
export type { ResultEvent, PlanEvent, FileChangeEvent, AgentEvent, Runner, RunnerLifetime, AgentRunner, TurnStartedEvent, TurnDiffEvent, FinalAnswerEvent, NoticeEvent, SessionInfoEvent, ToolHint, ApprovalView, ApprovalRequestedEvent, ApprovalResolvedEvent, BackgroundJobRoster, BackgroundJobsEvent, GoalView, GoalStateEvent, } from './types.js';
|
|
19
19
|
export { resolveAgentChoices } from './resolve-agent-choices.js';
|
|
20
20
|
export { syncAgentChoices } from './sync-agent-choices.js';
|
package/dist/runner/types.d.ts
CHANGED
|
@@ -294,6 +294,17 @@ export interface ResultEvent {
|
|
|
294
294
|
subtype: 'success' | 'error' | 'interrupted';
|
|
295
295
|
session_id: string;
|
|
296
296
|
timestamp?: string;
|
|
297
|
+
/**
|
|
298
|
+
* 本 run 的权威最终回答全文(runner 手里有一份完整文本时才填)。
|
|
299
|
+
*
|
|
300
|
+
* 为什么必须由 runner 带过来:状态层对进度正文有 `MAX_TEXT_CHARS`(12000
|
|
301
|
+
* 字符)存储窗口,最终回答超过窗口时 blocks 里只剩尾部 + `…(前 N 字符已
|
|
302
|
+
* 省略)` 标记——渲染层再怎么「回答优先」也找不回被丢掉的头部。runner 在
|
|
303
|
+
* 收 turn 时拿到的是**完整** assistant 文本(一条消息,不是逐 delta 拼接),
|
|
304
|
+
* 把它挂在 result 上,`promoteFinalAnswer` 用它建立 finalAnswer,回答因此
|
|
305
|
+
* 不再被进度窗口截断(2026-11-01 卡片输出块被截断缺陷)。
|
|
306
|
+
*/
|
|
307
|
+
finalAnswerText?: string;
|
|
297
308
|
usage?: {
|
|
298
309
|
/** Non-cached input tokens. For codex this is raw `input_tokens` minus
|
|
299
310
|
* `cached_input_tokens`; for pi/claude/opencode the raw value is already
|
|
@@ -405,6 +416,20 @@ export interface GoalStateEvent {
|
|
|
405
416
|
known: boolean;
|
|
406
417
|
timestamp?: string;
|
|
407
418
|
}
|
|
419
|
+
/**
|
|
420
|
+
* 后台任务名单里的一行(宿主侧权威数据)。
|
|
421
|
+
*
|
|
422
|
+
* `status` 保留宿主原词(running / stopping / completed / failed …)——各
|
|
423
|
+
* agent 的终态词表不同,桥侧只按「是不是 running/stopping」二分,不在这里
|
|
424
|
+
* 维护一份会漂移的枚举。桥侧自己产出的 `ended` / `unknown` 见
|
|
425
|
+
* `BackgroundJobStatus`(card/run-state)。
|
|
426
|
+
*/
|
|
427
|
+
export interface BackgroundJobRoster {
|
|
428
|
+
id: string;
|
|
429
|
+
label: string;
|
|
430
|
+
status?: string;
|
|
431
|
+
progress?: string;
|
|
432
|
+
}
|
|
408
433
|
/**
|
|
409
434
|
* 运行结束后仍在后台运行的任务(终态卡展示 + 逐个停止入口)。
|
|
410
435
|
*
|
|
@@ -414,12 +439,7 @@ export interface GoalStateEvent {
|
|
|
414
439
|
*/
|
|
415
440
|
export interface BackgroundJobsEvent {
|
|
416
441
|
type: 'background_jobs';
|
|
417
|
-
jobs:
|
|
418
|
-
id: string;
|
|
419
|
-
label: string;
|
|
420
|
-
status?: string;
|
|
421
|
-
progress?: string;
|
|
422
|
-
}>;
|
|
442
|
+
jobs: BackgroundJobRoster[];
|
|
423
443
|
timestamp?: string;
|
|
424
444
|
}
|
|
425
445
|
/** File change event — for agents that report file modifications (e.g. Codex). */
|
|
@@ -600,6 +620,14 @@ export interface AgentRunner extends Runner {
|
|
|
600
620
|
* agent 实现;调用方按能力存在与否门控。
|
|
601
621
|
*/
|
|
602
622
|
killJob?(sessionId: string, jobId: string): Promise<boolean>;
|
|
623
|
+
/**
|
|
624
|
+
* 订阅一个 session 的后台任务名单(整份替换),直到 `signal` abort 或宿主
|
|
625
|
+
* 关闭流。只有支持后台任务的 agent 实现;调用方按能力存在与否门控。
|
|
626
|
+
*
|
|
627
|
+
* 必须在 run 结束之后仍然可用:后台任务的生命周期跨 turn,收尾那一刻的快照
|
|
628
|
+
* 只能画一张会过期的卡。
|
|
629
|
+
*/
|
|
630
|
+
watchJobs?(sessionId: string, signal: AbortSignal): AsyncIterable<ReadonlyArray<BackgroundJobRoster>>;
|
|
603
631
|
/**
|
|
604
632
|
* 热更审批模式/权限预设(config.save 后推给在跑的 runner,不必重启会话)。
|
|
605
633
|
* 各家 settings 词表不同(codex: approvalPolicy/sandbox;kimi: permissionMode;
|
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
* — a synchronous HTTP round-trip to the local DSH host. This keeps the
|
|
8
8
|
* router/bridge interface contract intact without touching any upstream logic.
|
|
9
9
|
*
|
|
10
|
-
* listSessions: session/list → filter by cwd → sort by
|
|
11
|
-
* pagination (DSH v1 has no cursor pagination).
|
|
10
|
+
* listSessions: session/list → 排除 subagent 派生会话 → filter by cwd → sort by
|
|
11
|
+
* updatedAt desc → local pagination (DSH v1 has no cursor pagination).
|
|
12
12
|
* readSessionContent: session/projections → session/page → map SessionEvents +
|
|
13
13
|
* session-wide usage accumulation (assistant/message.usage);本 run 的 flow
|
|
14
14
|
* 字段 = 末个 turn(turn/start–turn/end)内所有采样之和,与 codex 同口径。
|
|
@@ -77,6 +77,11 @@ export declare class DshSessionReader implements AgentSessionReader {
|
|
|
77
77
|
* Find the session row in `session/list` that matches BOTH id and cwd.
|
|
78
78
|
* Returns undefined when the id belongs to another workspace (or is unknown) —
|
|
79
79
|
* every caller treats that as "not usable here".
|
|
80
|
+
*
|
|
81
|
+
* subagent 派生会话也排除:它的 id + cwd 都能命中,但只能按
|
|
82
|
+
* `{kind:'subagent', parentSessionId, childSessionId}` 寻址,按顶层会话读会被
|
|
83
|
+
* 服务端拒绝(session/agent-busy)。在这里统一 fail-closed,比让每个调用方
|
|
84
|
+
* 各自吃一次拒绝异常更省事(那异常会被 catch 成空事件列表 = 一张空卡)。
|
|
80
85
|
*/
|
|
81
86
|
private resolveSession;
|
|
82
87
|
/** `session/page` needs an inclusive cursor; projections carry the watermark. */
|
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
* — a synchronous HTTP round-trip to the local DSH host. This keeps the
|
|
8
8
|
* router/bridge interface contract intact without touching any upstream logic.
|
|
9
9
|
*
|
|
10
|
-
* listSessions: session/list → filter by cwd → sort by
|
|
11
|
-
* pagination (DSH v1 has no cursor pagination).
|
|
10
|
+
* listSessions: session/list → 排除 subagent 派生会话 → filter by cwd → sort by
|
|
11
|
+
* updatedAt desc → local pagination (DSH v1 has no cursor pagination).
|
|
12
12
|
* readSessionContent: session/projections → session/page → map SessionEvents +
|
|
13
13
|
* session-wide usage accumulation (assistant/message.usage);本 run 的 flow
|
|
14
14
|
* 字段 = 末个 turn(turn/start–turn/end)内所有采样之和,与 codex 同口径。
|
|
@@ -198,6 +198,7 @@ export class DshSessionReader {
|
|
|
198
198
|
const value = this.unary('session/list', {});
|
|
199
199
|
const items = value.items ?? [];
|
|
200
200
|
const filtered = items
|
|
201
|
+
.filter((i) => !isSubagentSession(i))
|
|
201
202
|
.filter((i) => i.cwd !== undefined && samePath(i.cwd, cwd))
|
|
202
203
|
.map((i) => ({
|
|
203
204
|
sessionId: String(i.sessionId),
|
|
@@ -374,10 +375,18 @@ export class DshSessionReader {
|
|
|
374
375
|
* Find the session row in `session/list` that matches BOTH id and cwd.
|
|
375
376
|
* Returns undefined when the id belongs to another workspace (or is unknown) —
|
|
376
377
|
* every caller treats that as "not usable here".
|
|
378
|
+
*
|
|
379
|
+
* subagent 派生会话也排除:它的 id + cwd 都能命中,但只能按
|
|
380
|
+
* `{kind:'subagent', parentSessionId, childSessionId}` 寻址,按顶层会话读会被
|
|
381
|
+
* 服务端拒绝(session/agent-busy)。在这里统一 fail-closed,比让每个调用方
|
|
382
|
+
* 各自吃一次拒绝异常更省事(那异常会被 catch 成空事件列表 = 一张空卡)。
|
|
377
383
|
*/
|
|
378
384
|
resolveSession(sessionId, cwd) {
|
|
379
385
|
const value = this.unary('session/list', {});
|
|
380
|
-
return (value.items ?? []).find((i) =>
|
|
386
|
+
return (value.items ?? []).find((i) => !isSubagentSession(i) &&
|
|
387
|
+
String(i.sessionId) === sessionId &&
|
|
388
|
+
i.cwd !== undefined &&
|
|
389
|
+
samePath(i.cwd, cwd));
|
|
381
390
|
}
|
|
382
391
|
/** `session/page` needs an inclusive cursor; projections carry the watermark. */
|
|
383
392
|
resolveThroughSeq(sessionId, known) {
|
|
@@ -387,6 +396,18 @@ export class DshSessionReader {
|
|
|
387
396
|
return typeof known.projections?.asOfSeq === 'number' ? known.projections.asOfSeq : -1;
|
|
388
397
|
}
|
|
389
398
|
}
|
|
399
|
+
/**
|
|
400
|
+
* subagent 派生会话判定(DSH 0.2.x 起与顶层会话并列在 `session/list` 里)。
|
|
401
|
+
*
|
|
402
|
+
* 只认 `origin === 'subagent'`,别用更"顺手"的两个信号:
|
|
403
|
+
* - **id 前缀**:顶层是 `session-<uuid>`、subagent 是裸 `<uuid>`,但 v0 老会话
|
|
404
|
+
* 也是裸 uuid 且能正常读(`delegationDepth: 0`、无 `origin`);
|
|
405
|
+
* - **`parentSessionId` 非空**:fork 与 `session-btw-*` 旁支同样带 parent,却是
|
|
406
|
+
* 可恢复的顶层会话。
|
|
407
|
+
*/
|
|
408
|
+
function isSubagentSession(item) {
|
|
409
|
+
return item.origin === 'subagent';
|
|
410
|
+
}
|
|
390
411
|
/** Preset moved from the summary top level to projections in DSH 0.2.x. */
|
|
391
412
|
function sessionPreset(item) {
|
|
392
413
|
const preset = item.projections?.values?.agentPreset ?? item.agentPreset;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lark-remote",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.19",
|
|
4
4
|
"description": "Feishu (Lark) private-chat bridge to local coding agents — Claude Code / Codex / opencode / pi / kimi, with live-streaming CardKit 2.0 cards",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare function splitFinalAnswer(text: string, maxBytes?: number): string[];
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Split an authoritative final answer into plain-text messages without losing
|
|
3
|
-
* any content. Feishu cards have a hard size budget; a long answer must spill
|
|
4
|
-
* rather than being silently truncated inside the progress card.
|
|
5
|
-
*/
|
|
6
|
-
const FINAL_ANSWER_CHUNK_BYTES = 20_000;
|
|
7
|
-
export function splitFinalAnswer(text, maxBytes = FINAL_ANSWER_CHUNK_BYTES) {
|
|
8
|
-
if (!text || maxBytes <= 0)
|
|
9
|
-
return text ? [text] : [];
|
|
10
|
-
const chunks = [];
|
|
11
|
-
let rest = text;
|
|
12
|
-
let remainingBytes = Buffer.byteLength(rest, 'utf8');
|
|
13
|
-
while (remainingBytes > maxBytes) {
|
|
14
|
-
let bytes = 0;
|
|
15
|
-
let end = 0;
|
|
16
|
-
let lastNewline = 0;
|
|
17
|
-
for (const char of rest) {
|
|
18
|
-
const charBytes = Buffer.byteLength(char, 'utf8');
|
|
19
|
-
if (bytes + charBytes > maxBytes)
|
|
20
|
-
break;
|
|
21
|
-
bytes += charBytes;
|
|
22
|
-
end += char.length;
|
|
23
|
-
if (char === '\n')
|
|
24
|
-
lastNewline = end;
|
|
25
|
-
}
|
|
26
|
-
if (end === 0) {
|
|
27
|
-
// A caller can choose a byte limit smaller than one codepoint. Keep one
|
|
28
|
-
// codepoint per chunk rather than looping forever with a zero-length cut.
|
|
29
|
-
const first = Array.from(rest)[0];
|
|
30
|
-
chunks.push(first);
|
|
31
|
-
rest = rest.slice(first.length);
|
|
32
|
-
remainingBytes -= Buffer.byteLength(first, 'utf8');
|
|
33
|
-
continue;
|
|
34
|
-
}
|
|
35
|
-
// Prefer a paragraph/line boundary when it does not waste most of a chunk.
|
|
36
|
-
const cut = lastNewline > 0 && lastNewline >= Math.floor(end * 0.6) ? lastNewline : end;
|
|
37
|
-
const chunk = rest.slice(0, cut);
|
|
38
|
-
chunks.push(chunk);
|
|
39
|
-
rest = rest.slice(cut);
|
|
40
|
-
remainingBytes -= Buffer.byteLength(chunk, 'utf8');
|
|
41
|
-
}
|
|
42
|
-
if (rest)
|
|
43
|
-
chunks.push(rest);
|
|
44
|
-
return chunks;
|
|
45
|
-
}
|
|
46
|
-
//# sourceMappingURL=final-answer-overflow.js.map
|