@eddyskywalker/dsh-chatgpt-subscription 0.43.1 → 0.44.0
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 +16 -0
- package/cordis.patch.yml +5 -0
- package/lib/client.js +81 -8
- package/lib/client.js.map +1 -1
- package/lib/entry-progress-updates-CkGJr_fe.js +1004 -0
- package/lib/index.js +44 -970
- package/lib/progress-updates.js +2 -0
- package/lib/types/client/progress-updates/host-source.d.ts +16 -0
- package/lib/types/client/progress-updates/host-source.d.ts.map +1 -1
- package/lib/types/client/progress-updates/index.d.ts.map +1 -1
- package/lib/types/client/progress-updates/settings-binding.d.ts +5 -0
- package/lib/types/client/progress-updates/settings-binding.d.ts.map +1 -1
- package/lib/types/compat.d.ts +1 -1
- package/lib/types/entry-progress-updates.d.ts +43 -0
- package/lib/types/entry-progress-updates.d.ts.map +1 -0
- package/lib/types/host/proxy-manager.d.ts +6 -0
- package/lib/types/host/proxy-manager.d.ts.map +1 -1
- package/lib/types/index.d.ts +21 -25
- package/lib/types/index.d.ts.map +1 -1
- package/locale/progress-updates/en.json +6 -0
- package/locale/progress-updates/zh.json +6 -0
- package/package.json +13 -3
- package/progress-updates.svg +9 -0
package/lib/index.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import { a as resolveProgressUpdatesSettings, n as attachProgressSettings } from "./entry-progress-updates-CkGJr_fe.js";
|
|
1
2
|
import { createRequire } from "node:module";
|
|
2
|
-
import z from "@deepseek-ai/schemastery";
|
|
3
3
|
import * as LlmModule from "@deepseek-ai/dsh-llm";
|
|
4
|
-
import { EMPTY_RESPONSE_CODE, HarnessError, LlmAdapter, LlmError, LlmError as LlmError$1, ProviderRequestId, ReasoningEffortId, ToolCallId, attributionHeaders,
|
|
4
|
+
import { EMPTY_RESPONSE_CODE, HarnessError, LlmAdapter, LlmError, LlmError as LlmError$1, ProviderRequestId, ReasoningEffortId, ToolCallId, attributionHeaders, errorChain, resolveRetryPolicy } from "@deepseek-ai/dsh-llm";
|
|
5
|
+
import { defineTool } from "@deepseek-ai/dsh-tools";
|
|
6
|
+
import z from "@deepseek-ai/schemastery";
|
|
5
7
|
import fs, { chmod, lstat, mkdir, open, readFile, realpath, rename, stat, unlink, writeFile } from "node:fs/promises";
|
|
6
8
|
import path, { basename, dirname, join, relative, resolve } from "node:path";
|
|
7
9
|
import os, { homedir, platform, userInfo } from "node:os";
|
|
@@ -15,12 +17,10 @@ import { lookup } from "node:dns/promises";
|
|
|
15
17
|
import { isIP } from "node:net";
|
|
16
18
|
import { MAX_TIMER_DELAY_MS, idleWatchdog, timeoutOf } from "@deepseek-ai/dsh-timeout";
|
|
17
19
|
import { boundContextSummary, createUserMessage as createUserMessage$1 } from "@deepseek-ai/dsh-llm/message";
|
|
18
|
-
import { defineTool } from "@deepseek-ai/dsh-tools";
|
|
19
20
|
import * as SettingsModule from "@deepseek-ai/dsh-settings";
|
|
20
21
|
import { URL as URL$1, URLSearchParams as URLSearchParams$1, fileURLToPath } from "node:url";
|
|
21
22
|
import { ProxyAgent, fetch as fetch$1 } from "undici";
|
|
22
23
|
import { setTimeout as setTimeout$1 } from "node:timers/promises";
|
|
23
|
-
import { z as z$1 } from "zod";
|
|
24
24
|
//#region src/host/common/output-reservation.ts
|
|
25
25
|
/**
|
|
26
26
|
* Keep fixed defaults only when they fit DSH compaction-basic's default policy.
|
|
@@ -141,7 +141,7 @@ const ROUTE_PREFIX$4 = "/api/dsh-chatgpt-subscription";
|
|
|
141
141
|
* `test/responses-client.test.ts` asserts the two agree, so a release that
|
|
142
142
|
* forgets this constant fails the suite instead of shipping quietly.
|
|
143
143
|
*/
|
|
144
|
-
const PLUGIN_VERSION = "0.
|
|
144
|
+
const PLUGIN_VERSION = "0.44.0";
|
|
145
145
|
const CODEX_CHATGPT_PROVIDER_ID = "codex-chatgpt";
|
|
146
146
|
const CODEX_API_BASE = "https://chatgpt.com/backend-api/codex";
|
|
147
147
|
const CODEX_RESPONSES_URL = `${CODEX_API_BASE}/responses`;
|
|
@@ -7007,7 +7007,7 @@ function sanitizeToolCallId(id, fallbackName) {
|
|
|
7007
7007
|
function toolCallIdNeeded(modelId, runtimeModel) {
|
|
7008
7008
|
return modelId.startsWith("claude-") || modelId.startsWith("gpt-oss-") || runtimeModel.startsWith("claude-") || runtimeModel.startsWith("gpt-oss-");
|
|
7009
7009
|
}
|
|
7010
|
-
function parseArguments$
|
|
7010
|
+
function parseArguments$1(raw) {
|
|
7011
7011
|
if (isRecord$47(raw)) return raw;
|
|
7012
7012
|
if (raw === void 0 || raw === null || raw === "") return {};
|
|
7013
7013
|
const parsed = typeof raw === "string" ? safeJsonParse$8(raw) : raw;
|
|
@@ -7497,7 +7497,7 @@ function assistantParts(message, model, runtimeModel, toolCalls) {
|
|
|
7497
7497
|
parts.push({
|
|
7498
7498
|
functionCall: {
|
|
7499
7499
|
name: toolName,
|
|
7500
|
-
args: parseArguments$
|
|
7500
|
+
args: parseArguments$1(block.arguments),
|
|
7501
7501
|
...wireId ? { id: wireId } : {}
|
|
7502
7502
|
},
|
|
7503
7503
|
...effectiveSignature ? { thoughtSignature: effectiveSignature } : {}
|
|
@@ -9568,7 +9568,7 @@ function normalizeHostForMatch(hostname) {
|
|
|
9568
9568
|
* is a normal way to run a local gateway, and a proxy cannot reach it any
|
|
9569
9569
|
* better than the process itself can.
|
|
9570
9570
|
*/
|
|
9571
|
-
function isLoopbackHostname
|
|
9571
|
+
function isLoopbackHostname(hostname) {
|
|
9572
9572
|
const host = normalizeHostForMatch(hostname);
|
|
9573
9573
|
if (host === "localhost" || host.endsWith(".localhost")) return true;
|
|
9574
9574
|
if (host === "::1") return true;
|
|
@@ -9622,7 +9622,7 @@ function shouldBypassProxy(target, noProxyList = "") {
|
|
|
9622
9622
|
} catch {
|
|
9623
9623
|
return false;
|
|
9624
9624
|
}
|
|
9625
|
-
if (isLoopbackHostname
|
|
9625
|
+
if (isLoopbackHostname(url.hostname)) return true;
|
|
9626
9626
|
if (!noProxyList) return false;
|
|
9627
9627
|
return noProxyBypasses(url.hostname, url.port || defaultPortFor(url.protocol), noProxyList);
|
|
9628
9628
|
}
|
|
@@ -9708,9 +9708,28 @@ function requestTarget(input) {
|
|
|
9708
9708
|
if (typeof input === "string" || input instanceof URL) return input;
|
|
9709
9709
|
return input.url;
|
|
9710
9710
|
}
|
|
9711
|
+
/**
|
|
9712
|
+
* Whether an error is undici saying the pooled client under this request was
|
|
9713
|
+
* destroyed — the host surfaces it as `terminated: The client is destroyed`.
|
|
9714
|
+
*
|
|
9715
|
+
* Only that family qualifies: an ordinary connect failure belongs to the other
|
|
9716
|
+
* decision (the agent is healthy, the path is not), so it must not be replayed
|
|
9717
|
+
* here.
|
|
9718
|
+
*/
|
|
9719
|
+
function isDestroyedClientError(error) {
|
|
9720
|
+
let current = error;
|
|
9721
|
+
for (let depth = 0; depth < 4; depth += 1) {
|
|
9722
|
+
if (typeof current === "string") return /client is destroyed/i.test(current);
|
|
9723
|
+
if (!(current instanceof Error)) return false;
|
|
9724
|
+
if (/client is destroyed|UND_ERR_DESTROYED/i.test(current.name + ": " + current.message)) return true;
|
|
9725
|
+
current = current.cause;
|
|
9726
|
+
}
|
|
9727
|
+
return false;
|
|
9728
|
+
}
|
|
9711
9729
|
var ProxyManager = class {
|
|
9712
9730
|
getPreferences;
|
|
9713
9731
|
baseFetch;
|
|
9732
|
+
proxyFetchFn;
|
|
9714
9733
|
systemProxyDetector;
|
|
9715
9734
|
noProxyReader;
|
|
9716
9735
|
logger;
|
|
@@ -9724,6 +9743,7 @@ var ProxyManager = class {
|
|
|
9724
9743
|
constructor(options) {
|
|
9725
9744
|
this.getPreferences = options.getPreferences;
|
|
9726
9745
|
this.baseFetch = options.baseFetch ?? fetch;
|
|
9746
|
+
this.proxyFetchFn = options.proxyFetchFn ?? fetch$1;
|
|
9727
9747
|
this.systemProxyDetector = options.systemProxyDetector ?? (() => detectSystemProxy());
|
|
9728
9748
|
this.noProxyReader = options.noProxyReader ?? (() => parseNoProxy());
|
|
9729
9749
|
this.logger = options.logger;
|
|
@@ -9803,20 +9823,24 @@ var ProxyManager = class {
|
|
|
9803
9823
|
const activeProxy = this.resolveActiveProxyUrl();
|
|
9804
9824
|
if (!activeProxy) return this.baseFetch(input, init);
|
|
9805
9825
|
if (shouldBypassProxy(requestTarget(input), this.getNoProxyList())) return this.baseFetch(input, init);
|
|
9806
|
-
try {
|
|
9826
|
+
for (let attempt = 0;; attempt += 1) try {
|
|
9807
9827
|
const agent = this.getOrCreateAgent(activeProxy);
|
|
9808
|
-
return await
|
|
9828
|
+
return await this.proxyFetchFn(input, {
|
|
9809
9829
|
...init,
|
|
9810
9830
|
dispatcher: agent
|
|
9811
9831
|
});
|
|
9812
9832
|
} catch (error) {
|
|
9833
|
+
if (attempt === 0 && isDestroyedClientError(error)) {
|
|
9834
|
+
this.agents.delete(activeProxy);
|
|
9835
|
+
continue;
|
|
9836
|
+
}
|
|
9813
9837
|
throw error;
|
|
9814
9838
|
}
|
|
9815
9839
|
};
|
|
9816
9840
|
}
|
|
9817
9841
|
dispose() {
|
|
9818
9842
|
this.proxyListeners.clear();
|
|
9819
|
-
for (const agent of this.agents.values()) agent.
|
|
9843
|
+
for (const agent of this.agents.values()) agent.close().catch(() => void 0);
|
|
9820
9844
|
this.agents.clear();
|
|
9821
9845
|
}
|
|
9822
9846
|
};
|
|
@@ -22886,7 +22910,7 @@ function toDataUrl(image, defaultMediaType = "image/png") {
|
|
|
22886
22910
|
if (image.startsWith("data:") || image.startsWith("http://") || image.startsWith("https://")) return image;
|
|
22887
22911
|
return `data:${defaultMediaType};base64,${image}`;
|
|
22888
22912
|
}
|
|
22889
|
-
function parseArguments
|
|
22913
|
+
function parseArguments(raw) {
|
|
22890
22914
|
if (typeof raw === "object" && raw !== null && !Array.isArray(raw)) return raw;
|
|
22891
22915
|
if (raw === void 0 || raw === null || raw === "") return {};
|
|
22892
22916
|
if (typeof raw === "string") {
|
|
@@ -22977,7 +23001,7 @@ function buildNativeBody(request) {
|
|
|
22977
23001
|
content: message.content,
|
|
22978
23002
|
tool_calls: message.toolCalls.map((call) => ({ function: {
|
|
22979
23003
|
name: call.name,
|
|
22980
|
-
arguments: parseArguments
|
|
23004
|
+
arguments: parseArguments(call.arguments)
|
|
22981
23005
|
} }))
|
|
22982
23006
|
};
|
|
22983
23007
|
return nativeContent(message);
|
|
@@ -47075,7 +47099,7 @@ function sleep$2(ms, signal) {
|
|
|
47075
47099
|
* so that spelling is accepted too — but only for the 127/8 block, never for a
|
|
47076
47100
|
* v4-mapped public address.
|
|
47077
47101
|
*/
|
|
47078
|
-
function isLoopbackAddress
|
|
47102
|
+
function isLoopbackAddress(address) {
|
|
47079
47103
|
if (address === void 0) return false;
|
|
47080
47104
|
if (address === "127.0.0.1" || address === "::1" || address === "::ffff:127.0.0.1") return true;
|
|
47081
47105
|
const mapped = /^::ffff:(\d{1,3}(?:\.\d{1,3}){3})$/i.exec(address);
|
|
@@ -47647,7 +47671,7 @@ const SUCCESS_PAGE = "Claude sign-in completed. You can close this window.";
|
|
|
47647
47671
|
*/
|
|
47648
47672
|
function callbackHandler(request, response) {
|
|
47649
47673
|
(async () => {
|
|
47650
|
-
if (!isLoopbackAddress
|
|
47674
|
+
if (!isLoopbackAddress(request.socket.remoteAddress)) {
|
|
47651
47675
|
await writeHtml(response, 403, "This callback only answers on this machine.");
|
|
47652
47676
|
return;
|
|
47653
47677
|
}
|
|
@@ -80937,955 +80961,6 @@ function releaseListener(handle) {
|
|
|
80937
80961
|
}
|
|
80938
80962
|
handle?.dispose?.();
|
|
80939
80963
|
}
|
|
80940
|
-
/**
|
|
80941
|
-
* 判断是否该催促。达到阈值即触发,注入之后调用方把计数清零。
|
|
80942
|
-
* @param rounds - 自上次进展更新以来的连续轮次数。
|
|
80943
|
-
* @returns 是否达到 {@link NUDGE_THRESHOLD}。
|
|
80944
|
-
*/
|
|
80945
|
-
function shouldNudge(rounds) {
|
|
80946
|
-
return rounds >= 8;
|
|
80947
|
-
}
|
|
80948
|
-
/**
|
|
80949
|
-
* 把进展列表截断到环形缓冲上限,保留最新的一批。
|
|
80950
|
-
* @param list - 任意长度的进展列表(不会被就地修改)。
|
|
80951
|
-
* @returns 截断后的新数组。
|
|
80952
|
-
*/
|
|
80953
|
-
function capUpdates(list) {
|
|
80954
|
-
return list.length <= 50 ? [...list] : list.slice(list.length - 50);
|
|
80955
|
-
}
|
|
80956
|
-
/** 进程内自增序号,只用于拼出稳定 id;不含任何运行时依赖。 */
|
|
80957
|
-
let sequence = 0;
|
|
80958
|
-
/**
|
|
80959
|
-
* 生成一条进展的稳定 id。
|
|
80960
|
-
* @param at - 产生时刻(Unix 毫秒)。
|
|
80961
|
-
* @returns 形如 `pu-<时间戳>-<序号>` 的字符串。
|
|
80962
|
-
*/
|
|
80963
|
-
function nextUpdateId(at) {
|
|
80964
|
-
sequence += 1;
|
|
80965
|
-
return `pu-${at}-${sequence}`;
|
|
80966
|
-
}
|
|
80967
|
-
/**
|
|
80968
|
-
* 创建一个空的进展更新 store。
|
|
80969
|
-
* @returns 新的会话维度 store。
|
|
80970
|
-
*/
|
|
80971
|
-
function createProgressUpdateStore() {
|
|
80972
|
-
let current = {
|
|
80973
|
-
updates: [],
|
|
80974
|
-
roundsSinceUpdate: 0,
|
|
80975
|
-
nudges: 0
|
|
80976
|
-
};
|
|
80977
|
-
return {
|
|
80978
|
-
state: () => current,
|
|
80979
|
-
push: (update) => {
|
|
80980
|
-
current = {
|
|
80981
|
-
...current,
|
|
80982
|
-
updates: capUpdates([...current.updates, update])
|
|
80983
|
-
};
|
|
80984
|
-
return current;
|
|
80985
|
-
},
|
|
80986
|
-
bumpRound: () => {
|
|
80987
|
-
current = {
|
|
80988
|
-
...current,
|
|
80989
|
-
roundsSinceUpdate: current.roundsSinceUpdate + 1
|
|
80990
|
-
};
|
|
80991
|
-
return current.roundsSinceUpdate;
|
|
80992
|
-
},
|
|
80993
|
-
markUpdated: () => {
|
|
80994
|
-
current = {
|
|
80995
|
-
...current,
|
|
80996
|
-
roundsSinceUpdate: 0
|
|
80997
|
-
};
|
|
80998
|
-
return current;
|
|
80999
|
-
},
|
|
81000
|
-
markNudged: () => {
|
|
81001
|
-
current = {
|
|
81002
|
-
...current,
|
|
81003
|
-
roundsSinceUpdate: 0,
|
|
81004
|
-
nudges: current.nudges + 1
|
|
81005
|
-
};
|
|
81006
|
-
return current;
|
|
81007
|
-
},
|
|
81008
|
-
reset: () => {
|
|
81009
|
-
current = {
|
|
81010
|
-
updates: [],
|
|
81011
|
-
roundsSinceUpdate: 0,
|
|
81012
|
-
nudges: 0
|
|
81013
|
-
};
|
|
81014
|
-
return current;
|
|
81015
|
-
}
|
|
81016
|
-
};
|
|
81017
|
-
}
|
|
81018
|
-
//#endregion
|
|
81019
|
-
//#region src/host/progress-updates/tool.ts
|
|
81020
|
-
/** 注册的工具名。冻结契约的一部分,不要改动。 */
|
|
81021
|
-
const NOTIFY_USER_TOOL = "progress_update";
|
|
81022
|
-
/** 工具关闭时返回给模型的文本(canonical 输出的 render,而不是抛错)。 */
|
|
81023
|
-
const DISABLED_OUTPUT_TEXT = "Progress updates are disabled; the update was not displayed.";
|
|
81024
|
-
/** 工具描述的最终全文。 */
|
|
81025
|
-
const PROGRESS_UPDATE_DESCRIPTION = [
|
|
81026
|
-
"Report progress to the user without ending your turn.",
|
|
81027
|
-
"Two updates are part of the job, not optional narration: send one BEFORE your first tool call of a multi-step task (the plan), and one BEFORE your final answer (what was done and what was verified).",
|
|
81028
|
-
"Use it during the rest of the work so the user can follow along and correct course early:",
|
|
81029
|
-
"state your plan near the beginning of the task (what you will do, and in what order);",
|
|
81030
|
-
"report a meaningful conclusion the moment you have it (a key fact, the root cause, a verified assumption) rather than narrating every step;",
|
|
81031
|
-
"give a stage judgement when one phase ends and the next begins (where things stand, what happens next);",
|
|
81032
|
-
"explain what you are waiting for before a long wait such as a test run, an install, or a build;",
|
|
81033
|
-
"and say what blocks you — and what you will try next, or what you need from the user — when you are stuck.",
|
|
81034
|
-
"Each update has one short title line (plain text, no Markdown, no trailing period) with the details in the message body.",
|
|
81035
|
-
"Do not repeat a status that has not changed.",
|
|
81036
|
-
"When you run as a subagent, describe only your own subtask: finishing your subtask is not the same as finishing the overall task."
|
|
81037
|
-
].join(" ");
|
|
81038
|
-
/**
|
|
81039
|
-
* 构造 progress_update 工具。
|
|
81040
|
-
* @param deps - 依赖:当前开关与进展接收端。
|
|
81041
|
-
* @returns 可注册的工具定义。
|
|
81042
|
-
*/
|
|
81043
|
-
function createProgressUpdateTool(deps) {
|
|
81044
|
-
return defineTool({
|
|
81045
|
-
name: NOTIFY_USER_TOOL,
|
|
81046
|
-
description: PROGRESS_UPDATE_DESCRIPTION,
|
|
81047
|
-
parameters: {
|
|
81048
|
-
title: {
|
|
81049
|
-
type: "string",
|
|
81050
|
-
description: "One line, about 60 characters at most, in the user's language: plain text, no Markdown, no trailing period."
|
|
81051
|
-
},
|
|
81052
|
-
message: {
|
|
81053
|
-
type: "string",
|
|
81054
|
-
required: true,
|
|
81055
|
-
description: "The details, about 1000 characters at most, in light Markdown: one paragraph of prose plus a few bullets. Tables are supported: when several items share fields, use a GFM pipe table (header row plus a | --- | separator row) instead of one item per line."
|
|
81056
|
-
}
|
|
81057
|
-
},
|
|
81058
|
-
output: {
|
|
81059
|
-
schema: {
|
|
81060
|
-
type: "object",
|
|
81061
|
-
additionalProperties: false,
|
|
81062
|
-
properties: { displayed: {
|
|
81063
|
-
type: "boolean",
|
|
81064
|
-
description: "Whether the update was actually shown to the user."
|
|
81065
|
-
} }
|
|
81066
|
-
},
|
|
81067
|
-
render(_args, value) {
|
|
81068
|
-
return [{
|
|
81069
|
-
type: "text",
|
|
81070
|
-
text: value.displayed ? "Progress update displayed." : DISABLED_OUTPUT_TEXT
|
|
81071
|
-
}];
|
|
81072
|
-
}
|
|
81073
|
-
},
|
|
81074
|
-
async execute(args, exec) {
|
|
81075
|
-
if (!deps.isEnabled()) return { displayed: false };
|
|
81076
|
-
const sink = deps.sink?.();
|
|
81077
|
-
if (sink === void 0) return { displayed: false };
|
|
81078
|
-
const at = Date.now();
|
|
81079
|
-
const source = deps.sourceOf?.(exec);
|
|
81080
|
-
const update = {
|
|
81081
|
-
id: nextUpdateId(at),
|
|
81082
|
-
message: args.message,
|
|
81083
|
-
at,
|
|
81084
|
-
...args.title === void 0 || args.title.trim() === "" ? {} : { title: args.title.trim() },
|
|
81085
|
-
...source === void 0 ? {} : { source }
|
|
81086
|
-
};
|
|
81087
|
-
return { displayed: sink.push(update) };
|
|
81088
|
-
}
|
|
81089
|
-
});
|
|
81090
|
-
}
|
|
81091
|
-
//#endregion
|
|
81092
|
-
//#region src/host/progress-updates/nudge.ts
|
|
81093
|
-
/** 注入到模型上下文里的催促正文。 */
|
|
81094
|
-
const NUDGE_TEXT = [
|
|
81095
|
-
"You have made a run of tool calls without reporting progress to the user.",
|
|
81096
|
-
"If you are still in the middle of multi-step work, send one progress update now with the progress_update tool:",
|
|
81097
|
-
"one short title line and the details in the body — what you have established so far, where things stand, and what you are doing next.",
|
|
81098
|
-
"Skip it when there is genuinely nothing new to report."
|
|
81099
|
-
].join(" ");
|
|
81100
|
-
/** 催促上下文在消息来源上的 kind(供客户端与日志区分这类注入)。 */
|
|
81101
|
-
const NUDGE_SOURCE_KIND = "progress-updates-nudge";
|
|
81102
|
-
/**
|
|
81103
|
-
* 造一条催促上下文。
|
|
81104
|
-
* @param sequence - 本会话第几次催促,写进 notice 摘要便于日志辨认。
|
|
81105
|
-
* @returns 可直接放进 `additionalContexts` 的用户角色消息。
|
|
81106
|
-
*/
|
|
81107
|
-
function createNudgeContext(sequence) {
|
|
81108
|
-
return createUserMessage({
|
|
81109
|
-
content: [{
|
|
81110
|
-
type: "text",
|
|
81111
|
-
text: NUDGE_TEXT
|
|
81112
|
-
}],
|
|
81113
|
-
source: {
|
|
81114
|
-
kind: NUDGE_SOURCE_KIND,
|
|
81115
|
-
form: "notice",
|
|
81116
|
-
summary: `progress update nudge #${sequence}`
|
|
81117
|
-
}
|
|
81118
|
-
});
|
|
81119
|
-
}
|
|
81120
|
-
/**
|
|
81121
|
-
* 找出一次工具调用所属的**计数会话**:子会话归并到最近的顶层祖先,整棵委派
|
|
81122
|
-
* 树共用一个计数器。
|
|
81123
|
-
* @param agent - 事件里的调用方 agent。
|
|
81124
|
-
* @param sessions - 会话查找表,缺省时退化为只认调用方自己的会话。
|
|
81125
|
-
* @returns 计数用的会话 id,无法判定时返回 undefined。
|
|
81126
|
-
*/
|
|
81127
|
-
function counterSessionIdOf(agent, sessions) {
|
|
81128
|
-
const own = stringOf$1(agent?.session?.header?.id) ?? stringOf$1(agent?.id);
|
|
81129
|
-
if (own === void 0) return void 0;
|
|
81130
|
-
if (sessions === void 0) return own;
|
|
81131
|
-
let current = own;
|
|
81132
|
-
let header = agent?.session?.header;
|
|
81133
|
-
for (let depth = 0; depth < 16 && current !== void 0; depth += 1) {
|
|
81134
|
-
const resolved = header ?? sessions.get(current)?.header;
|
|
81135
|
-
if (resolved === void 0) return current;
|
|
81136
|
-
const parentId = stringOf$1(resolved.parentSession);
|
|
81137
|
-
if (parentId === void 0) return current;
|
|
81138
|
-
const parent = sessions.get(parentId);
|
|
81139
|
-
if (parent === void 0) return current;
|
|
81140
|
-
header = parent.header;
|
|
81141
|
-
current = parentId;
|
|
81142
|
-
}
|
|
81143
|
-
return current;
|
|
81144
|
-
}
|
|
81145
|
-
/**
|
|
81146
|
-
* 读一次工具调用所属的「步」标识,用于把同一个模型步里的多条工具结果合并成一轮。
|
|
81147
|
-
*
|
|
81148
|
-
* 拿不到 turnBoundary(没有 agent-loop,或宿主不是真实会话)时返回 undefined,
|
|
81149
|
-
* 调用方退化为按工具结果计数——宁可多计,也不要漏掉催促。
|
|
81150
|
-
* @param agent - 事件里的调用方 agent。
|
|
81151
|
-
* @param projections - 会话投影服务,可选。
|
|
81152
|
-
* @returns 该步的唯一键,或 undefined。
|
|
81153
|
-
*/
|
|
81154
|
-
function stepKeyOf(agent, projections) {
|
|
81155
|
-
if (projections === void 0) return void 0;
|
|
81156
|
-
const session = agent?.session;
|
|
81157
|
-
if (session === void 0) return void 0;
|
|
81158
|
-
try {
|
|
81159
|
-
const seq = projections.stateOf(session, "turnBoundary")?.lastStepStartSeq;
|
|
81160
|
-
return typeof seq === "number" ? `step:${seq}` : void 0;
|
|
81161
|
-
} catch {
|
|
81162
|
-
return;
|
|
81163
|
-
}
|
|
81164
|
-
}
|
|
81165
|
-
/**
|
|
81166
|
-
* 读一个未知字段为字符串。
|
|
81167
|
-
* @param value - 候选值。
|
|
81168
|
-
* @returns 字符串,或任何其它类型的 undefined。
|
|
81169
|
-
*/
|
|
81170
|
-
function stringOf$1(value) {
|
|
81171
|
-
return typeof value === "string" && value !== "" ? value : void 0;
|
|
81172
|
-
}
|
|
81173
|
-
//#endregion
|
|
81174
|
-
//#region src/host/progress-updates/prompt.ts
|
|
81175
|
-
/**
|
|
81176
|
-
* 系统提示词引导:让模型知道「多步任务里要主动汇报进展」这件事该怎么用。
|
|
81177
|
-
*
|
|
81178
|
-
* 文本用函数形式提供(而不是静态字符串):开关关闭时返回空串,装配期就地
|
|
81179
|
-
* 抹掉引导,不需要重启会话、也不需要重挂插件行。
|
|
81180
|
-
*
|
|
81181
|
-
* ## 为什么是英文
|
|
81182
|
-
*
|
|
81183
|
-
* 提示词是写给模型看的,不是写给用户看的:同一段要求在英文下最短、歧义最少,
|
|
81184
|
-
* 也和工具描述(`tool.ts` 的 `PROGRESS_UPDATE_DESCRIPTION`,本来就是英文)保持
|
|
81185
|
-
* 同一套措辞。用户看到的语言由 {@link progressLanguageDirective} 单独钉住——
|
|
81186
|
-
* 「指令用英文」与「输出用中文」是两件事,不要混在一起。
|
|
81187
|
-
*
|
|
81188
|
-
* @module dsh-chatgpt-subscription/host/progress-updates/prompt
|
|
81189
|
-
*/
|
|
81190
|
-
/**
|
|
81191
|
-
* 本模块提示词分节的名称,全局唯一(重复注册会抛错)。
|
|
81192
|
-
*
|
|
81193
|
-
* 保留原插件的 `plugin:progress-updates` 拼写:会话里已有的分节名不能因为搬家
|
|
81194
|
-
* 而变成第二个分节。
|
|
81195
|
-
*/
|
|
81196
|
-
const PROGRESS_SECTION_NAME = "plugin:progress-updates";
|
|
81197
|
-
/** 允许的取值,供设置解析做白名单校验(配置里写错就等于没写)。 */
|
|
81198
|
-
const PROGRESS_UPDATE_LANGUAGES = [
|
|
81199
|
-
"auto",
|
|
81200
|
-
"zh",
|
|
81201
|
-
"en"
|
|
81202
|
-
];
|
|
81203
|
-
/**
|
|
81204
|
-
* 引导词正文。写法对齐 Kimi 的「进展更新」:先说清多步任务早期的思路,再报
|
|
81205
|
-
* 有意义的结论、阶段判断、长等待与阻塞;每条给一行标题,细节放正文;不重复
|
|
81206
|
-
* 没有变化的状态。开头与结尾两条被提成硬要求——没有它们,面板在任务的两端
|
|
81207
|
-
* 都是空的。
|
|
81208
|
-
*/
|
|
81209
|
-
const PROGRESS_GUIDANCE = [
|
|
81210
|
-
"## Progress updates",
|
|
81211
|
-
"",
|
|
81212
|
-
"During multi-step work you can call `progress_update` to report progress without ending your turn and without spending a question: report, then keep working.",
|
|
81213
|
-
"",
|
|
81214
|
-
"**Two updates are mandatory — missing either one means the job is not finished:**",
|
|
81215
|
-
"",
|
|
81216
|
-
"- **Opening**: before the first tool call of the task, send one update stating the plan (which steps, what you touch first). The user must be able to redirect you before you disappear into execution.",
|
|
81217
|
-
"- **Closing**: after the work is done and before your final answer, send one update stating the result (what changed, what was verified, what is left). The panel and the conversation both close on this line.",
|
|
81218
|
-
"",
|
|
81219
|
-
"Beyond those two, these moments are worth a report:",
|
|
81220
|
-
"- **Early planning**: the opening update covers it; report again only when the plan really changes (different approach, smaller scope, a broken premise).",
|
|
81221
|
-
"- **A meaningful conclusion**: a key fact, a root cause, a verified assumption — report the conclusion itself, not \"I just read a file\".",
|
|
81222
|
-
"- **A stage judgement**: one phase ends and the next begins — one line on where things stand and what happens next.",
|
|
81223
|
-
"- **Before a long wait**: a test run, an install, a build — say what you are waiting for and roughly how long.",
|
|
81224
|
-
"- **When blocked**: missing information, missing permission, an external failure — say where you are stuck, what you will try next, or what you need from the user.",
|
|
81225
|
-
"",
|
|
81226
|
-
"How to write one:",
|
|
81227
|
-
"- One short title line (plain text, no Markdown, no trailing period); the details go in the message body.",
|
|
81228
|
-
"- The body uses light Markdown: one paragraph of prose plus a few bullets. **When several rows share fields, use a GFM pipe table** (header row plus a `| --- |` separator row) instead of one item per line.",
|
|
81229
|
-
"- Do not repeat a status that has not changed: if the last update already said it is running, it needs no second report.",
|
|
81230
|
-
"- A progress update is for the user, not for the log: write it in human words, do not restate tool arguments.",
|
|
81231
|
-
"",
|
|
81232
|
-
"When you run as a subagent: describe only your own subtask progress. Finishing your subtask is not the same as finishing the overall task — do not report the overall progress as done."
|
|
81233
|
-
].join("\n");
|
|
81234
|
-
/**
|
|
81235
|
-
* 每个语言档位对应的一句指令。
|
|
81236
|
-
*
|
|
81237
|
-
* 只约束输出的标题与正文,不约束引导词本身;三条都必须自洽——`auto` 不许在
|
|
81238
|
-
* 用户说中文时滑回英文,`zh`/`en` 则明确压过对话语言。
|
|
81239
|
-
*/
|
|
81240
|
-
const PROGRESS_LANGUAGE_DIRECTIVES = {
|
|
81241
|
-
auto: "Write the title and the message of every progress update in the language the user is writing in, whatever it is. Follow the conversation; do not fall back to English unless the user does.",
|
|
81242
|
-
zh: "Write the title and the message of every progress update in Simplified Chinese, even when the conversation is in another language.",
|
|
81243
|
-
en: "Write the title and the message of every progress update in English, even when the conversation is in another language."
|
|
81244
|
-
};
|
|
81245
|
-
/**
|
|
81246
|
-
* 取一个语言档位的指令文本;未知取值按 `auto` 处理。
|
|
81247
|
-
* @param language - 配置里读到的语言档位。
|
|
81248
|
-
* @returns 追加到引导词末尾的一句指令。
|
|
81249
|
-
*/
|
|
81250
|
-
function progressLanguageDirective(language) {
|
|
81251
|
-
return PROGRESS_LANGUAGE_DIRECTIVES[language] ?? PROGRESS_LANGUAGE_DIRECTIVES.auto;
|
|
81252
|
-
}
|
|
81253
|
-
/**
|
|
81254
|
-
* 生成提示词分节的文本提供者。
|
|
81255
|
-
*
|
|
81256
|
-
* 文本与开关无关,这是刻意的:系统提示是缓存前缀的第一段,会话中途改一个字
|
|
81257
|
-
* 就会让整段前缀失效、整段重新计费(Kimi 的缓存是内容前缀匹配)。开关的语义
|
|
81258
|
-
* 因此只留给工具自己——关闭时 `execute` 直接返回 `displayed:false`。
|
|
81259
|
-
* @param language - 读取当前输出语言的函数;缺省按 `auto` 处理。
|
|
81260
|
-
* @returns 引导词加语言指令。
|
|
81261
|
-
*/
|
|
81262
|
-
function progressSectionText(language = () => "auto") {
|
|
81263
|
-
return () => PROGRESS_GUIDANCE + "\n\n" + progressLanguageDirective(language());
|
|
81264
|
-
}
|
|
81265
|
-
//#endregion
|
|
81266
|
-
//#region src/host/progress-updates/projection.ts
|
|
81267
|
-
/**
|
|
81268
|
-
* 「进展更新」的会话投影:把日志里的 progress_update 调用折叠成一条条进展。
|
|
81269
|
-
*
|
|
81270
|
-
* ## 为什么要有它
|
|
81271
|
-
*
|
|
81272
|
-
* 进展更新原来只活在进程内的一个 Map 里(`store.ts` 的环形缓冲):重开对话、
|
|
81273
|
-
* 刷新页面、宿主重启之后那份状态就没了,用户看到的是「进展面板空了」。而数据其实
|
|
81274
|
-
* 一直没丢 —— `progress_update` 的每次调用本身就是一条 `tool/call` 会话事件,
|
|
81275
|
-
* 早已被 DSH 持久化。所以这里把**日志**当成唯一真源:事件提交时折叠一次,读的
|
|
81276
|
-
* 时候就是一份可以直接回放的历史。
|
|
81277
|
-
*
|
|
81278
|
-
* ## 为什么不 import 投影包
|
|
81279
|
-
*
|
|
81280
|
-
* `@deepseek-ai/dsh-session-projection` **不在** peerDependencies 里:本插件要
|
|
81281
|
-
* 同时支持 0.1.2-alpha.5 ~ 0.2.1-rc.2,那一串宿主里有的根本没有这个包,运行时
|
|
81282
|
-
* import 会让整个插件在那代宿主上装载失败。所以这里按**结构化类型**自己声明
|
|
81283
|
-
* 契约(与 `nudge.ts` 的 `NudgeSessionLookup` / `TurnBoundaryLookup` 同一套
|
|
81284
|
-
* 做法),运行时只经 `ctx.get('sessionProjections')` 拿服务。
|
|
81285
|
-
*
|
|
81286
|
-
* ## 折叠是日志重放路径,任何情况都不许抛错
|
|
81287
|
-
*
|
|
81288
|
-
* `apply` 在每条提交事件上跑一次。这里抛出一次,毁掉的不只是本单元:重放链路
|
|
81289
|
-
* 会当场断掉,会话可能连读都读不出来。所以对事件形状一律**不信任** —— 不是
|
|
81290
|
-
* tool/call、不是 progress_update、arguments 不是合法 JSON、message 不是字符串、
|
|
81291
|
-
* seq/time 不是数字 —— 全部原样返回**同一个 state 引用**(`Object.is` 相等,
|
|
81292
|
-
* 下游零工作)。
|
|
81293
|
-
*
|
|
81294
|
-
* ## id 为什么用 seq,还要带上会话 id
|
|
81295
|
-
*
|
|
81296
|
-
* 客户端(`src/client/progress-updates/host-source.ts` 的 `sameList`)按 id 比较
|
|
81297
|
-
* 两份快照,面板列表也拿 id 当 React key。时间戳会随重放时的时钟漂移,seq 不会:
|
|
81298
|
-
* 同一段日志重放两次必须得到**逐字相同**的 id。会话 id 后缀解决的是另一半问题 ——
|
|
81299
|
-
* 读值要合并整棵委派树(见 `index.ts` 的 `projectionUpdatesOf`),而 seq 在
|
|
81300
|
-
* 每个会话里都从 0 开始,只带 seq 的话根会话与子会话的第一条进展会撞 id。
|
|
81301
|
-
*
|
|
81302
|
-
* @module dsh-chatgpt-subscription/host/progress-updates/projection
|
|
81303
|
-
*/
|
|
81304
|
-
/** 投影键:宿主读状态、客户端取 wire 值都用这个名字。 */
|
|
81305
|
-
const PROGRESS_UPDATES_PROJECTION_KEY = "progressUpdates";
|
|
81306
|
-
/** 单条进展的 schema:与 `ProgressUpdate` 逐字同形。 */
|
|
81307
|
-
const UPDATE_SCHEMA = z$1.object({
|
|
81308
|
-
id: z$1.string(),
|
|
81309
|
-
title: z$1.string().optional(),
|
|
81310
|
-
message: z$1.string(),
|
|
81311
|
-
at: z$1.number(),
|
|
81312
|
-
source: z$1.string().optional()
|
|
81313
|
-
});
|
|
81314
|
-
/** 折叠状态的 schema(宿主状态)。 */
|
|
81315
|
-
const progressUpdatesStateSchema = z$1.object({
|
|
81316
|
-
updates: z$1.array(UPDATE_SCHEMA),
|
|
81317
|
-
source: z$1.string().optional()
|
|
81318
|
-
});
|
|
81319
|
-
/** wire 值的 schema(客户端看到的数组)。 */
|
|
81320
|
-
const progressUpdatesViewSchema = z$1.array(UPDATE_SCHEMA);
|
|
81321
|
-
/**
|
|
81322
|
-
* 造一条进展的稳定 id。
|
|
81323
|
-
* @param seq - 事件在会话日志里的序号(重放稳定)。
|
|
81324
|
-
* @param source - 所属会话 id;拿不到时省略后缀。
|
|
81325
|
-
* @returns 形如 `pu-<seq>-<sessionId>` 的字符串。
|
|
81326
|
-
*/
|
|
81327
|
-
function updateIdOf(seq, source) {
|
|
81328
|
-
return source === void 0 ? "pu-" + seq : "pu-" + seq + "-" + source;
|
|
81329
|
-
}
|
|
81330
|
-
/**
|
|
81331
|
-
* 读一个未知值为非空字符串。
|
|
81332
|
-
* @param value - 候选值。
|
|
81333
|
-
* @returns 非空字符串,其它类型的 undefined。
|
|
81334
|
-
*/
|
|
81335
|
-
function stringOf(value) {
|
|
81336
|
-
return typeof value === "string" && value !== "" ? value : void 0;
|
|
81337
|
-
}
|
|
81338
|
-
/**
|
|
81339
|
-
* 把 `tool/call` 里的 `arguments` 原文解析成进展内容。
|
|
81340
|
-
*
|
|
81341
|
-
* **任何一点不对就返回 undefined**(而不是抛错,也不是造一条半截进展):这是重放
|
|
81342
|
-
* 路径,一条形状坏掉的历史事件只该被跳过。
|
|
81343
|
-
* @param raw - 工具入参 JSON 原文。
|
|
81344
|
-
* @returns 正文与(去空白后的)可选标题。
|
|
81345
|
-
*/
|
|
81346
|
-
function parseArguments(raw) {
|
|
81347
|
-
if (typeof raw !== "string") return void 0;
|
|
81348
|
-
let value;
|
|
81349
|
-
try {
|
|
81350
|
-
value = JSON.parse(raw);
|
|
81351
|
-
} catch {
|
|
81352
|
-
return;
|
|
81353
|
-
}
|
|
81354
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) return void 0;
|
|
81355
|
-
const record = value;
|
|
81356
|
-
if (typeof record.message !== "string") return void 0;
|
|
81357
|
-
const title = typeof record.title === "string" ? record.title.trim() : "";
|
|
81358
|
-
return title === "" ? { message: record.message } : {
|
|
81359
|
-
message: record.message,
|
|
81360
|
-
title
|
|
81361
|
-
};
|
|
81362
|
-
}
|
|
81363
|
-
/**
|
|
81364
|
-
* 把 PTC 子调用里的 `arguments`(**已解析的对象**)读成进展内容。
|
|
81365
|
-
*
|
|
81366
|
-
* 与 {@link parseArguments} 只差一层:那条路径拿到的是模型吐出的 JSON 原文,
|
|
81367
|
-
* 这条拿到的是宿主已经归一化过的对象(见 `dsh-tools` 的 `PtcDispatchEventData`)。
|
|
81368
|
-
* 同样是"任何一点不对就返回 undefined",同样不抛错。
|
|
81369
|
-
* @param raw - 已归一化的入参对象。
|
|
81370
|
-
* @returns 正文与(去空白后的)可选标题。
|
|
81371
|
-
*/
|
|
81372
|
-
function parseDispatchedArguments(raw) {
|
|
81373
|
-
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return void 0;
|
|
81374
|
-
const record = raw;
|
|
81375
|
-
if (typeof record.message !== "string") return void 0;
|
|
81376
|
-
const title = typeof record.title === "string" ? record.title.trim() : "";
|
|
81377
|
-
return title === "" ? { message: record.message } : {
|
|
81378
|
-
message: record.message,
|
|
81379
|
-
title
|
|
81380
|
-
};
|
|
81381
|
-
}
|
|
81382
|
-
/**
|
|
81383
|
-
* 造一个进展折叠单元。
|
|
81384
|
-
*
|
|
81385
|
-
* 每次登记都要一份**新的**定义对象:它是纯函数集合,没有跨会话的状态,但把
|
|
81386
|
-
* `init` 闭包里的会话头漏进来就会把不同会话串成一份。
|
|
81387
|
-
* @returns 可交给 `sessionProjections.register` 的定义。
|
|
81388
|
-
*/
|
|
81389
|
-
function progressUpdatesProjection() {
|
|
81390
|
-
return {
|
|
81391
|
-
key: PROGRESS_UPDATES_PROJECTION_KEY,
|
|
81392
|
-
stateVersion: 2,
|
|
81393
|
-
stateSchema: progressUpdatesStateSchema,
|
|
81394
|
-
init(header) {
|
|
81395
|
-
const source = stringOf(header?.id);
|
|
81396
|
-
return source === void 0 ? { updates: [] } : {
|
|
81397
|
-
updates: [],
|
|
81398
|
-
source
|
|
81399
|
-
};
|
|
81400
|
-
},
|
|
81401
|
-
apply(state, event) {
|
|
81402
|
-
const isNative = event?.type === "tool/call";
|
|
81403
|
-
const isDispatch = event?.type === "tool/ptc-dispatch";
|
|
81404
|
-
if (!isNative && !isDispatch) return state;
|
|
81405
|
-
const data = event.data;
|
|
81406
|
-
if (typeof data !== "object" || data === null) return state;
|
|
81407
|
-
if (data.name !== "progress_update") return state;
|
|
81408
|
-
const body = isNative ? parseArguments(data.arguments) : parseDispatchedArguments(data.arguments);
|
|
81409
|
-
if (body === void 0) return state;
|
|
81410
|
-
const seq = event.seq;
|
|
81411
|
-
if (typeof seq !== "number" || !Number.isFinite(seq)) return state;
|
|
81412
|
-
const at = event.time;
|
|
81413
|
-
if (typeof at !== "number" || !Number.isFinite(at)) return state;
|
|
81414
|
-
const update = {
|
|
81415
|
-
id: updateIdOf(seq, state.source),
|
|
81416
|
-
message: body.message,
|
|
81417
|
-
at,
|
|
81418
|
-
...body.title === void 0 ? {} : { title: body.title },
|
|
81419
|
-
...state.source === void 0 ? {} : { source: state.source }
|
|
81420
|
-
};
|
|
81421
|
-
return {
|
|
81422
|
-
...state,
|
|
81423
|
-
updates: capUpdates([...state.updates, update])
|
|
81424
|
-
};
|
|
81425
|
-
},
|
|
81426
|
-
wire: {
|
|
81427
|
-
viewSchema: progressUpdatesViewSchema,
|
|
81428
|
-
view: (state) => state.updates
|
|
81429
|
-
}
|
|
81430
|
-
};
|
|
81431
|
-
}
|
|
81432
|
-
/**
|
|
81433
|
-
* 读一个会话上折叠出来的进展。
|
|
81434
|
-
*
|
|
81435
|
-
* 两条读取路径,都按**不可用就当没读到**处理(返回 undefined,调用方回退到内存
|
|
81436
|
-
* 实现):先问 `stateOf`(宿主状态,最直接),没有再问 `snapshot`(wire 切面,
|
|
81437
|
-
* 老一点的投影服务上只有它)。读出来的值一律过 schema,形状不对就当投影不可用 ——
|
|
81438
|
-
* 宁可用进程内那份,也不要把一份坏数据渲染成历史。
|
|
81439
|
-
* @param projections - 会话投影服务。
|
|
81440
|
-
* @param session - 会话对象。
|
|
81441
|
-
* @returns 该会话的进展;投影不可用时 undefined。
|
|
81442
|
-
*/
|
|
81443
|
-
function readProjectedUpdates(projections, session) {
|
|
81444
|
-
const stateOf = projections.stateOf;
|
|
81445
|
-
if (typeof stateOf === "function") {
|
|
81446
|
-
let state;
|
|
81447
|
-
try {
|
|
81448
|
-
state = stateOf.call(projections, session, PROGRESS_UPDATES_PROJECTION_KEY);
|
|
81449
|
-
} catch {
|
|
81450
|
-
return;
|
|
81451
|
-
}
|
|
81452
|
-
if (state === void 0) return void 0;
|
|
81453
|
-
const parsed = progressUpdatesStateSchema.safeParse(state);
|
|
81454
|
-
return parsed.success ? parsed.data.updates : void 0;
|
|
81455
|
-
}
|
|
81456
|
-
const snapshot = projections.snapshot;
|
|
81457
|
-
if (typeof snapshot !== "function") return void 0;
|
|
81458
|
-
let values;
|
|
81459
|
-
try {
|
|
81460
|
-
values = snapshot.call(projections, session, [PROGRESS_UPDATES_PROJECTION_KEY])?.values;
|
|
81461
|
-
} catch {
|
|
81462
|
-
return;
|
|
81463
|
-
}
|
|
81464
|
-
if (values === void 0) return void 0;
|
|
81465
|
-
const parsed = progressUpdatesViewSchema.safeParse(values[PROGRESS_UPDATES_PROJECTION_KEY]);
|
|
81466
|
-
return parsed.success ? parsed.data : void 0;
|
|
81467
|
-
}
|
|
81468
|
-
/**
|
|
81469
|
-
* 把多个会话的进展合并成一份。
|
|
81470
|
-
*
|
|
81471
|
-
* 按 `at` 升序排(同一毫秒时按输入顺序,JS 的排序是稳定的:传入方把"被查询的
|
|
81472
|
-
* 会话自己"放在最前面),再按 {@link MAX_UPDATES} 截断保留最新的。整棵委派树共用
|
|
81473
|
-
* 一份进展是今天的语义,所以合并后的上限与单会话一致,不随子会话数量膨胀。
|
|
81474
|
-
* @param lists - 各会话各自的进展(每条已各自截断过)。
|
|
81475
|
-
* @returns 合并后的进展,最多 {@link MAX_UPDATES} 条。
|
|
81476
|
-
*/
|
|
81477
|
-
function mergeProjectedUpdates(lists) {
|
|
81478
|
-
const merged = [].concat(...lists.map((list) => [...list]));
|
|
81479
|
-
merged.sort((left, right) => left.at - right.at);
|
|
81480
|
-
return capUpdates(merged);
|
|
81481
|
-
}
|
|
81482
|
-
//#endregion
|
|
81483
|
-
//#region src/host/progress-updates/routes.ts
|
|
81484
|
-
/** 进展读取路由的完整路径。 */
|
|
81485
|
-
const PROGRESS_UPDATES_PATH = "/api/dsh-progress-updates/updates";
|
|
81486
|
-
/** 路由默认的 JSON 响应头。 */
|
|
81487
|
-
const JSON_HEADERS = {
|
|
81488
|
-
"content-type": "application/json; charset=utf-8",
|
|
81489
|
-
"referrer-policy": "no-referrer"
|
|
81490
|
-
};
|
|
81491
|
-
/**
|
|
81492
|
-
* 断言本次请求来自本机。
|
|
81493
|
-
*
|
|
81494
|
-
* 进展内容会包含用户在会话里做的事,属于个人数据,所以只有 loopback(桌面端
|
|
81495
|
-
* 自己)能读:socket 地址是权威判断,Host 头必须也是 loopback 权威(挡住 DNS
|
|
81496
|
-
* 重绑定),`X-Forwarded-For` 永远不信任。
|
|
81497
|
-
* @param request - 进来的 HTTP 请求。
|
|
81498
|
-
* @returns 是否放行。
|
|
81499
|
-
*/
|
|
81500
|
-
function isLoopbackRequest(request) {
|
|
81501
|
-
if (!isLoopbackAddress(request.socket.remoteAddress)) return false;
|
|
81502
|
-
const host = request.headers.host;
|
|
81503
|
-
if (typeof host !== "string" || host === "") return false;
|
|
81504
|
-
let hostname;
|
|
81505
|
-
try {
|
|
81506
|
-
hostname = new URL("http://" + host).hostname.toLowerCase();
|
|
81507
|
-
} catch {
|
|
81508
|
-
return false;
|
|
81509
|
-
}
|
|
81510
|
-
if (!isLoopbackHostname(hostname)) return false;
|
|
81511
|
-
return request.headers["sec-fetch-site"] !== "cross-site";
|
|
81512
|
-
}
|
|
81513
|
-
/** socket 远端地址是否落在 loopback 范围(127/8、::1、IPv4-mapped)。 */
|
|
81514
|
-
function isLoopbackAddress(address) {
|
|
81515
|
-
if (address === void 0) return false;
|
|
81516
|
-
const normalized = address.toLowerCase();
|
|
81517
|
-
if (normalized === "::1") return true;
|
|
81518
|
-
if (normalized.startsWith("::ffff:")) return isIPv4Loopback(normalized.slice(7));
|
|
81519
|
-
return isIPv4Loopback(normalized);
|
|
81520
|
-
}
|
|
81521
|
-
/** IPv4 127/8 判定(四段十进制,首段为 127)。 */
|
|
81522
|
-
function isIPv4Loopback(v4) {
|
|
81523
|
-
const parts = v4.split(".");
|
|
81524
|
-
return parts.length === 4 && parts[0] === "127" && parts.every((part) => /^\d{1,3}$/.test(part) && Number(part) <= 255);
|
|
81525
|
-
}
|
|
81526
|
-
/** 主机名是否指向 loopback 权威(localhost、[::1]、127/8)。 */
|
|
81527
|
-
function isLoopbackHostname(hostname) {
|
|
81528
|
-
if (hostname === "localhost" || hostname === "[::1]") return true;
|
|
81529
|
-
return isIPv4Loopback(hostname);
|
|
81530
|
-
}
|
|
81531
|
-
/**
|
|
81532
|
-
* 写一个 JSON 响应。
|
|
81533
|
-
* @param res - 响应对象。
|
|
81534
|
-
* @param status - HTTP 状态码。
|
|
81535
|
-
* @param body - 要序列化的值。
|
|
81536
|
-
* @param headers - 追加或覆盖的头。
|
|
81537
|
-
*/
|
|
81538
|
-
function writeJson(res, status, body, headers = {}) {
|
|
81539
|
-
res.writeHead(status, {
|
|
81540
|
-
...JSON_HEADERS,
|
|
81541
|
-
...headers
|
|
81542
|
-
});
|
|
81543
|
-
res.end(JSON.stringify(body));
|
|
81544
|
-
}
|
|
81545
|
-
/**
|
|
81546
|
-
* 进展读取路由:`GET /api/dsh-progress-updates/updates?session=<id>`。
|
|
81547
|
-
*
|
|
81548
|
-
* 返回 `{ updates: ProgressUpdate[] }`。响应带 no-store:这个列表随 agent 干活
|
|
81549
|
-
* 实时变化,缓存住就等于展示过期进展。
|
|
81550
|
-
* @param reader - 进展读取面。
|
|
81551
|
-
* @returns 可注册的 route。
|
|
81552
|
-
*/
|
|
81553
|
-
function makeProgressUpdatesRoute(reader) {
|
|
81554
|
-
return {
|
|
81555
|
-
kind: "exact",
|
|
81556
|
-
path: PROGRESS_UPDATES_PATH,
|
|
81557
|
-
handler: (req, res) => {
|
|
81558
|
-
if (!isLoopbackRequest(req)) {
|
|
81559
|
-
writeJson(res, 403, {
|
|
81560
|
-
ok: false,
|
|
81561
|
-
error: "forbidden: loopback-only"
|
|
81562
|
-
});
|
|
81563
|
-
return;
|
|
81564
|
-
}
|
|
81565
|
-
const session = new URL(req.url ?? "/", "http://127.0.0.1").searchParams.get("session") ?? "";
|
|
81566
|
-
writeJson(res, 200, { updates: reader.updatesOf(session) }, { "cache-control": "no-store" });
|
|
81567
|
-
}
|
|
81568
|
-
};
|
|
81569
|
-
}
|
|
81570
|
-
//#endregion
|
|
81571
|
-
//#region src/host/progress-updates/index.ts
|
|
81572
|
-
/**
|
|
81573
|
-
* 读取一个配置字段,沿着 volatile 活引用取当前值。
|
|
81574
|
-
* @param field - 字段本身。
|
|
81575
|
-
* @returns 当前值,或字段缺失时的 undefined。
|
|
81576
|
-
*/
|
|
81577
|
-
function readProgressConfigField(field) {
|
|
81578
|
-
if (field === void 0 || field === null) return void 0;
|
|
81579
|
-
if (typeof field === "object" && typeof field.get === "function") return field.get();
|
|
81580
|
-
return field;
|
|
81581
|
-
}
|
|
81582
|
-
/**
|
|
81583
|
-
* 解析一次读取的有效设置:每次调用都重读活引用,因此设置保存后立刻生效。
|
|
81584
|
-
* @param config - 主入口收到的插件配置(活引用或普通值)。
|
|
81585
|
-
* @returns 生效的开关与催促阈值。
|
|
81586
|
-
*/
|
|
81587
|
-
function resolveProgressUpdatesSettings(config) {
|
|
81588
|
-
const enabled = readProgressConfigField(config?.progressUpdates);
|
|
81589
|
-
const threshold = readProgressConfigField(config?.progressUpdatesNudgeThreshold);
|
|
81590
|
-
const language = readProgressConfigField(config?.progressUpdatesLanguage);
|
|
81591
|
-
return {
|
|
81592
|
-
enabled: enabled !== false,
|
|
81593
|
-
nudgeThreshold: typeof threshold === "number" && Number.isSafeInteger(threshold) && threshold > 0 ? threshold : 8,
|
|
81594
|
-
language: language !== void 0 && PROGRESS_UPDATE_LANGUAGES.includes(language) ? language : "auto"
|
|
81595
|
-
};
|
|
81596
|
-
}
|
|
81597
|
-
/** 血缘回溯的深度上限:与 `nudge.ts` 的 `counterSessionIdOf` 同一个数。 */
|
|
81598
|
-
const LINEAGE_DEPTH_LIMIT = 16;
|
|
81599
|
-
/**
|
|
81600
|
-
* 找出一个会话的全部子孙(广度优先,只看 `sessions.list()` 里活跃的那些)。
|
|
81601
|
-
*
|
|
81602
|
-
* 重开对话之后历史子孙并不在 `list()` 里 —— 那时它们各自的进展本来就取不到,
|
|
81603
|
-
* 这里能做的只是把**当前活跃**的那几支合进来,剩下的等它们 attach。
|
|
81604
|
-
* @param rootId - 祖先会话 id。
|
|
81605
|
-
* @param sessions - 会话服务。
|
|
81606
|
-
* @returns 子孙会话(不含根自己),按发现顺序。
|
|
81607
|
-
*/
|
|
81608
|
-
function descendantSessionsOf(rootId, sessions) {
|
|
81609
|
-
const list = sessions.list;
|
|
81610
|
-
if (typeof list !== "function") return [];
|
|
81611
|
-
let all;
|
|
81612
|
-
try {
|
|
81613
|
-
all = list.call(sessions);
|
|
81614
|
-
} catch {
|
|
81615
|
-
return [];
|
|
81616
|
-
}
|
|
81617
|
-
if (!Array.isArray(all) || all.length === 0) return [];
|
|
81618
|
-
/** 父会话 id → 直接子会话。 */
|
|
81619
|
-
const childrenOf = /* @__PURE__ */ new Map();
|
|
81620
|
-
for (const session of all) {
|
|
81621
|
-
const parent = typeof session?.header?.parentSession === "string" ? session.header.parentSession : void 0;
|
|
81622
|
-
if (parent === void 0) continue;
|
|
81623
|
-
const bucket = childrenOf.get(parent);
|
|
81624
|
-
if (bucket === void 0) childrenOf.set(parent, [session]);
|
|
81625
|
-
else bucket.push(session);
|
|
81626
|
-
}
|
|
81627
|
-
const found = [];
|
|
81628
|
-
const seen = /* @__PURE__ */ new Set([rootId]);
|
|
81629
|
-
let frontier = [rootId];
|
|
81630
|
-
for (let depth = 0; depth < LINEAGE_DEPTH_LIMIT && frontier.length > 0; depth += 1) {
|
|
81631
|
-
const next = [];
|
|
81632
|
-
for (const parentId of frontier) for (const child of childrenOf.get(parentId) ?? []) {
|
|
81633
|
-
const childId = typeof child.header?.id === "string" ? child.header.id : void 0;
|
|
81634
|
-
if (childId !== void 0) {
|
|
81635
|
-
if (seen.has(childId)) continue;
|
|
81636
|
-
seen.add(childId);
|
|
81637
|
-
next.push(childId);
|
|
81638
|
-
}
|
|
81639
|
-
found.push(child);
|
|
81640
|
-
}
|
|
81641
|
-
frontier = next;
|
|
81642
|
-
}
|
|
81643
|
-
return found;
|
|
81644
|
-
}
|
|
81645
|
-
/**
|
|
81646
|
-
* 已挂载的模块登记表,走一个 global symbol:同一个包的两份模块实例(npm 拷贝与
|
|
81647
|
-
* 仓库 link)仍然共享一次判定,而重复注册同名工具或同名提示词分节会让宿主启动
|
|
81648
|
-
* 失败。
|
|
81649
|
-
*/
|
|
81650
|
-
const MOUNTED = Symbol.for("dsh-chatgpt-subscription.mounted-modules");
|
|
81651
|
-
/** 本模块在登记表里的名字。 */
|
|
81652
|
-
const MODULE_ID = "progress-updates";
|
|
81653
|
-
/**
|
|
81654
|
-
* 把一条催促挂到一个 post-execute 决策上,同时保住该决策原有的附加上下文。
|
|
81655
|
-
* @param decision - `next()` 交回来的决策。
|
|
81656
|
-
* @param context - 要追加的催促上下文。
|
|
81657
|
-
* @returns 追加后的决策(形状不变)。
|
|
81658
|
-
*/
|
|
81659
|
-
function withNudge(decision, context) {
|
|
81660
|
-
const additionalContexts = [...decision.additionalContexts ?? [], context];
|
|
81661
|
-
if (decision.kind === "block") return {
|
|
81662
|
-
kind: "block",
|
|
81663
|
-
feedback: decision.feedback,
|
|
81664
|
-
additionalContexts
|
|
81665
|
-
};
|
|
81666
|
-
if (decision.value !== void 0) return {
|
|
81667
|
-
kind: "accept",
|
|
81668
|
-
value: decision.value,
|
|
81669
|
-
additionalContexts
|
|
81670
|
-
};
|
|
81671
|
-
if (decision.content !== void 0) return {
|
|
81672
|
-
kind: "accept",
|
|
81673
|
-
content: decision.content,
|
|
81674
|
-
additionalContexts
|
|
81675
|
-
};
|
|
81676
|
-
return {
|
|
81677
|
-
kind: "accept",
|
|
81678
|
-
additionalContexts
|
|
81679
|
-
};
|
|
81680
|
-
}
|
|
81681
|
-
/**
|
|
81682
|
-
* 单实例守卫:本模块已挂载时放弃本次安装。
|
|
81683
|
-
* @param ctx - 宿主上下文。
|
|
81684
|
-
* @returns 释放函数(交出挂载权);已经挂载过时返回 undefined。
|
|
81685
|
-
*/
|
|
81686
|
-
function claimMount(ctx) {
|
|
81687
|
-
const registry = globalThis;
|
|
81688
|
-
const existing = registry[MOUNTED];
|
|
81689
|
-
const mounted = existing instanceof Set ? existing : /* @__PURE__ */ new Set();
|
|
81690
|
-
if (existing !== mounted) registry[MOUNTED] = mounted;
|
|
81691
|
-
if (mounted.has(MODULE_ID)) return void 0;
|
|
81692
|
-
mounted.add(MODULE_ID);
|
|
81693
|
-
const release = () => {
|
|
81694
|
-
mounted.delete(MODULE_ID);
|
|
81695
|
-
};
|
|
81696
|
-
if (typeof ctx.effect === "function") ctx.effect(() => release, "dsh-chatgpt-subscription: progress updates mount");
|
|
81697
|
-
return release;
|
|
81698
|
-
}
|
|
81699
|
-
/**
|
|
81700
|
-
* 挂载「进展更新」的宿主半边。
|
|
81701
|
-
* @param ctx - 宿主插件上下文。
|
|
81702
|
-
* @param options - 挂载选项(设置读取器)。
|
|
81703
|
-
* @returns 卸载函数;重复挂载时返回一个空操作。
|
|
81704
|
-
*/
|
|
81705
|
-
function installProgressUpdates(ctx, options) {
|
|
81706
|
-
const release = claimMount(ctx);
|
|
81707
|
-
if (release === void 0) return () => void 0;
|
|
81708
|
-
/** 每次调用重新读服务:宿主树可能在本模块挂载之后才 settle,读一次存下来会漏。 */
|
|
81709
|
-
const serviceOf = (name) => {
|
|
81710
|
-
const read = ctx.get;
|
|
81711
|
-
return typeof read === "function" ? read.call(ctx, name) : void 0;
|
|
81712
|
-
};
|
|
81713
|
-
/** 登记一个副作用;上下文没有 effect 时立即执行(测试用假上下文)。 */
|
|
81714
|
-
const track = (execute, label) => {
|
|
81715
|
-
if (typeof ctx.effect === "function") ctx.effect(execute, label);
|
|
81716
|
-
else execute();
|
|
81717
|
-
};
|
|
81718
|
-
/** 会话查找表:够把子会话回溯到顶层祖先即可。 */
|
|
81719
|
-
const sessionsOf = () => serviceOf("sessions");
|
|
81720
|
-
/** 会话投影服务:只用来读 turnBoundary,把同一个模型步里的多条工具结果合并成一轮。 */
|
|
81721
|
-
const projectionsOf = () => serviceOf("sessionProjections");
|
|
81722
|
-
/**
|
|
81723
|
-
* 会话投影服务:登记/读取进展折叠。
|
|
81724
|
-
*
|
|
81725
|
-
* 与 {@link projectionsOf} 是同一个服务的两份结构化视图(`nudge` 那边只读
|
|
81726
|
-
* `turnBoundary`,这里读写进展键),分开写是为了让各自的类型面只声明自己真正
|
|
81727
|
-
* 用到的成员。服务缺席(老宿主、没装 agent-loop)时是 undefined。
|
|
81728
|
-
*/
|
|
81729
|
-
const sessionProjectionsOf = () => serviceOf("sessionProjections");
|
|
81730
|
-
/**
|
|
81731
|
-
* 登记进展折叠单元。服务缺席时**安静跳过**:这是可选服务,缺了只是回到内存
|
|
81732
|
-
* 实现(今天的行为),不是一次安装失败。
|
|
81733
|
-
*/
|
|
81734
|
-
track(() => {
|
|
81735
|
-
const projections = sessionProjectionsOf();
|
|
81736
|
-
if (typeof projections?.register !== "function") return () => void 0;
|
|
81737
|
-
try {
|
|
81738
|
-
return projections.register(progressUpdatesProjection());
|
|
81739
|
-
} catch (error) {
|
|
81740
|
-
serviceOf("logger")?.warn("[dsh-chatgpt-subscription] progress updates projection not registered: " + String(error));
|
|
81741
|
-
return () => void 0;
|
|
81742
|
-
}
|
|
81743
|
-
}, "dsh-chatgpt-subscription: progress update projection");
|
|
81744
|
-
/** 会话维度状态;键是计数会话 id(子会话已归并到顶层祖先)。 */
|
|
81745
|
-
const stores = /* @__PURE__ */ new Map();
|
|
81746
|
-
/** 已经计过数的「步」:同一个模型步里的后续工具结果不再重复计数。 */
|
|
81747
|
-
const countedSteps = /* @__PURE__ */ new Map();
|
|
81748
|
-
/**
|
|
81749
|
-
* 取一个计数会话的 store,按需创建。
|
|
81750
|
-
* @param key - 计数会话 id。
|
|
81751
|
-
* @returns 该会话的 store。
|
|
81752
|
-
*/
|
|
81753
|
-
const storeOf = (key) => {
|
|
81754
|
-
let store = stores.get(key);
|
|
81755
|
-
if (store === void 0) {
|
|
81756
|
-
store = createProgressUpdateStore();
|
|
81757
|
-
stores.set(key, store);
|
|
81758
|
-
}
|
|
81759
|
-
return store;
|
|
81760
|
-
};
|
|
81761
|
-
/**
|
|
81762
|
-
* 一次工具调用归属的计数会话 id。
|
|
81763
|
-
* @param exec - 工具执行上下文(或任意带 agent 的对象)。
|
|
81764
|
-
* @returns 计数会话 id;判定不出来时归入 `session` 这一桶。
|
|
81765
|
-
*/
|
|
81766
|
-
const counterKeyOf = (exec) => counterSessionIdOf(exec?.agent, sessionsOf()) ?? "session";
|
|
81767
|
-
/**
|
|
81768
|
-
* 从会话日志折叠出来的进展 —— 面板的**首选**取数路径。
|
|
81769
|
-
*
|
|
81770
|
-
* 读三样东西,任缺其一就返回 undefined,让调用方回退到内存实现:
|
|
81771
|
-
* 会话服务、投影服务,以及"这个 id 对应的会话对象"。最后一件事在重开对话之后
|
|
81772
|
-
* 并不必然成立:`sessions` 里只有**活跃**会话,历史会话要等宿主 attach 上来
|
|
81773
|
-
* 才读得到 —— 那之前面板显示的是内存里那一份(通常为空),attach 之后同一个
|
|
81774
|
-
* 轮询请求就能读到完整历史。
|
|
81775
|
-
*
|
|
81776
|
-
* 合并范围是**整棵委派树**:今天的语义是一个子会话的进展归并到顶层祖先的桶里,
|
|
81777
|
-
* 折叠之后靠 `header.parentSession` 把子孙找回来(与 `nudge.ts` 的 lineage
|
|
81778
|
-
* 走法同一套,深度上限 16 是防一条损坏的日志把这里变成死循环),按 `at` 升序
|
|
81779
|
-
* 排 —— 子会话汇报的进展因此落在它真正发生的那个时间点上,而不是全排在最后。
|
|
81780
|
-
* @param sessionId - 浏览器问的会话 id。
|
|
81781
|
-
* @returns 合并后的进展;这条路径不可用时 undefined。
|
|
81782
|
-
*/
|
|
81783
|
-
const projectionUpdatesOf = (sessionId) => {
|
|
81784
|
-
if (sessionId === "") return void 0;
|
|
81785
|
-
const sessions = sessionsOf();
|
|
81786
|
-
if (sessions === void 0) return void 0;
|
|
81787
|
-
const projections = sessionProjectionsOf();
|
|
81788
|
-
if (projections === void 0) return void 0;
|
|
81789
|
-
let own;
|
|
81790
|
-
try {
|
|
81791
|
-
own = sessions.get?.(sessionId);
|
|
81792
|
-
} catch {
|
|
81793
|
-
return;
|
|
81794
|
-
}
|
|
81795
|
-
if (own === void 0) return void 0;
|
|
81796
|
-
const updates = readProjectedUpdates(projections, own);
|
|
81797
|
-
if (updates === void 0) return void 0;
|
|
81798
|
-
const lists = [updates];
|
|
81799
|
-
for (const child of descendantSessionsOf(sessionId, sessions)) {
|
|
81800
|
-
const childUpdates = readProjectedUpdates(projections, child);
|
|
81801
|
-
if (childUpdates !== void 0) lists.push(childUpdates);
|
|
81802
|
-
}
|
|
81803
|
-
return lists.length === 1 ? updates : mergeProjectedUpdates(lists);
|
|
81804
|
-
};
|
|
81805
|
-
/**
|
|
81806
|
-
* 浏览器半边的读取面:给定会话 id 就交出那个会话的进展快照。
|
|
81807
|
-
*
|
|
81808
|
-
* **先投影、后内存**:投影是从已持久化的会话日志折出来的,重开对话、刷新页面、
|
|
81809
|
-
* 宿主重启之后都还在;内存 `stores` 只在投影这条路不可用时兜底(老宿主没有
|
|
81810
|
-
* 投影服务、会话还没 attach、或者压根没装会话服务)。
|
|
81811
|
-
*
|
|
81812
|
-
* 请求不带 session(或带一个本进程没见过的 id)时,回退到那个"合成会话桶":
|
|
81813
|
-
* 这是单会话部署里的常态,也是插件第一次被调用、祖先血缘还没建立时的落点。
|
|
81814
|
-
*/
|
|
81815
|
-
const reader = { updatesOf: (sessionId) => {
|
|
81816
|
-
const projected = projectionUpdatesOf(sessionId);
|
|
81817
|
-
if (projected !== void 0) return projected;
|
|
81818
|
-
const direct = stores.get(sessionId);
|
|
81819
|
-
if (direct !== void 0) return direct.state().updates;
|
|
81820
|
-
if (sessionId !== "") {
|
|
81821
|
-
const merged = counterSessionIdOf({ id: sessionId }, sessionsOf());
|
|
81822
|
-
const parent = merged === void 0 ? void 0 : stores.get(merged);
|
|
81823
|
-
if (parent !== void 0) return parent.state().updates;
|
|
81824
|
-
return [];
|
|
81825
|
-
}
|
|
81826
|
-
const fallback = stores.get("session");
|
|
81827
|
-
return fallback === void 0 ? [] : fallback.state().updates;
|
|
81828
|
-
} };
|
|
81829
|
-
track(() => {
|
|
81830
|
-
const tools = serviceOf("tools");
|
|
81831
|
-
if (tools === void 0) {
|
|
81832
|
-
serviceOf("logger")?.warn("[dsh-chatgpt-subscription] progress updates tool skipped: no tools service");
|
|
81833
|
-
return () => void 0;
|
|
81834
|
-
}
|
|
81835
|
-
const tool = createProgressUpdateTool({
|
|
81836
|
-
isEnabled: () => options.settings().enabled,
|
|
81837
|
-
sink: () => ({ push: (update) => {
|
|
81838
|
-
if (!options.settings().enabled) return false;
|
|
81839
|
-
storeOf(update.source ?? "session").push(update);
|
|
81840
|
-
return true;
|
|
81841
|
-
} }),
|
|
81842
|
-
sourceOf: (exec) => counterKeyOf(exec)
|
|
81843
|
-
});
|
|
81844
|
-
return tools.register(tool);
|
|
81845
|
-
}, "dsh-chatgpt-subscription: progress update tool");
|
|
81846
|
-
track(() => {
|
|
81847
|
-
const systemPrompt = serviceOf("systemPrompt");
|
|
81848
|
-
if (systemPrompt === void 0) return () => void 0;
|
|
81849
|
-
return systemPrompt.section({
|
|
81850
|
-
name: PROGRESS_SECTION_NAME,
|
|
81851
|
-
order: 220,
|
|
81852
|
-
text: progressSectionText(() => options.settings().language)
|
|
81853
|
-
});
|
|
81854
|
-
}, "dsh-chatgpt-subscription: progress update prompt section");
|
|
81855
|
-
track(() => {
|
|
81856
|
-
const webServer = serviceOf("webServer");
|
|
81857
|
-
if (webServer === void 0) return () => void 0;
|
|
81858
|
-
return webServer.register(makeProgressUpdatesRoute(reader));
|
|
81859
|
-
}, "dsh-chatgpt-subscription: progress update route");
|
|
81860
|
-
const disposeListener = ctx.on?.("tools/post-execute", (async (exec, _result, next) => {
|
|
81861
|
-
const decision = await next();
|
|
81862
|
-
const current = options.settings();
|
|
81863
|
-
if (!current.enabled) return decision;
|
|
81864
|
-
const key = counterKeyOf(exec);
|
|
81865
|
-
const store = storeOf(key);
|
|
81866
|
-
if (exec.name === "progress_update") {
|
|
81867
|
-
store.markUpdated();
|
|
81868
|
-
const reportedStep = stepKeyOf(exec.agent, projectionsOf());
|
|
81869
|
-
if (reportedStep !== void 0) countedSteps.set(key, reportedStep);
|
|
81870
|
-
return decision;
|
|
81871
|
-
}
|
|
81872
|
-
const step = stepKeyOf(exec.agent, projectionsOf());
|
|
81873
|
-
if (step !== void 0) {
|
|
81874
|
-
if (countedSteps.get(key) === step) return decision;
|
|
81875
|
-
countedSteps.set(key, step);
|
|
81876
|
-
}
|
|
81877
|
-
const rounds = store.bumpRound();
|
|
81878
|
-
if (!(current.nudgeThreshold === 8 ? shouldNudge(rounds) : rounds >= current.nudgeThreshold)) return decision;
|
|
81879
|
-
const nudges = store.markNudged().nudges;
|
|
81880
|
-
return withNudge(decision, createNudgeContext(nudges));
|
|
81881
|
-
}));
|
|
81882
|
-
return () => {
|
|
81883
|
-
disposeListener?.();
|
|
81884
|
-
stores.clear();
|
|
81885
|
-
countedSteps.clear();
|
|
81886
|
-
release();
|
|
81887
|
-
};
|
|
81888
|
-
}
|
|
81889
80964
|
//#endregion
|
|
81890
80965
|
//#region src/index.ts
|
|
81891
80966
|
const Config = z.object({
|
|
@@ -81903,15 +80978,15 @@ const Config = z.object({
|
|
|
81903
80978
|
subagentModelScope: z.union([z.const("session"), z.const("preference")]).default("session"),
|
|
81904
80979
|
reasoningCollapseGuard: z.union([z.object({}), z.const(false)]).default(false),
|
|
81905
80980
|
noAnswerResume: z.union([z.object({}), z.const(false)]).default({}),
|
|
81906
|
-
codexStreamIdleTimeoutMs: z.number().min(1).max(MAX_TIMER_DELAY_MS).step(1).default(DEFAULT_STREAM_IDLE_TIMEOUT_MS$2),
|
|
81907
|
-
poolStorage: z.union([z.const("keyring"), z.const("credentials")]).default("keyring").description("号池存储后端:keyring(默认,桌面平台的系统密钥环)或 credentials(DSH 凭据库,服务器推荐)。"),
|
|
81908
80981
|
progressUpdates: z.boolean().default(true).volatile().description("启用进展更新:允许 agent 用 progress_update 工具主动汇报多步任务的进展,并在侧栏展示。"),
|
|
81909
80982
|
progressUpdatesNudgeThreshold: z.number().step(1).min(1).max(50).default(8).volatile().description("催促阈值:连续这么多轮带工具调用却没有进展更新时,提醒 agent 发一条进展。"),
|
|
81910
80983
|
progressUpdatesLanguage: z.union([
|
|
81911
80984
|
z.const("auto"),
|
|
81912
80985
|
z.const("zh"),
|
|
81913
80986
|
z.const("en")
|
|
81914
|
-
]).default("auto").volatile().description("进展更新的输出语言:auto 跟随对话语言,zh 中文,en 英文。")
|
|
80987
|
+
]).default("auto").volatile().description("进展更新的输出语言:auto 跟随对话语言,zh 中文,en 英文。"),
|
|
80988
|
+
codexStreamIdleTimeoutMs: z.number().min(1).max(MAX_TIMER_DELAY_MS).step(1).default(DEFAULT_STREAM_IDLE_TIMEOUT_MS$2),
|
|
80989
|
+
poolStorage: z.union([z.const("keyring"), z.const("credentials")]).default("keyring").description("号池存储后端:keyring(默认,桌面平台的系统密钥环)或 credentials(DSH 凭据库,服务器推荐)。")
|
|
81915
80990
|
});
|
|
81916
80991
|
const inject = [
|
|
81917
80992
|
"webServer",
|
|
@@ -81932,6 +81007,7 @@ function apply(ctx, pluginConfig = {}) {
|
|
|
81932
81007
|
logger: ctx.logger
|
|
81933
81008
|
});
|
|
81934
81009
|
if (pluginConfig.syncAgentPresets !== false) ctx.effect(() => installBundledPresets(ctx, installDispatchPreset(ctx)), "dsh-chatgpt-subscription: agent preset sync");
|
|
81010
|
+
attachProgressSettings(() => resolveProgressUpdatesSettings(pluginConfig));
|
|
81935
81011
|
const store = createPlatformTokenStore();
|
|
81936
81012
|
const preferences = registerPreferenceStore(ctx.settings);
|
|
81937
81013
|
preferences.hydrate().catch(() => void 0);
|
|
@@ -82515,7 +81591,6 @@ function apply(ctx, pluginConfig = {}) {
|
|
|
82515
81591
|
fetchFn: proxyFetch,
|
|
82516
81592
|
pool: minimaxCodeAccountPool
|
|
82517
81593
|
})) : () => void 0;
|
|
82518
|
-
const disposeProgressUpdates = installProgressUpdates(ctx, { settings: () => resolveProgressUpdatesSettings(pluginConfig) });
|
|
82519
81594
|
ctx.inject(["web"], (ctx) => {
|
|
82520
81595
|
ctx.web.registerSearchProvider(createCodexSearchProvider(oauth, { fetchFn: proxyFetch }));
|
|
82521
81596
|
ctx.web.registerFetchProvider(createCodexFetchProvider({
|
|
@@ -82542,7 +81617,6 @@ function apply(ctx, pluginConfig = {}) {
|
|
|
82542
81617
|
disposeVideoTool();
|
|
82543
81618
|
disposeMinimaxUnderstandVideoTool();
|
|
82544
81619
|
disposeMinimaxGenerateVideoTool();
|
|
82545
|
-
disposeProgressUpdates();
|
|
82546
81620
|
disposeAdapter();
|
|
82547
81621
|
disposeRoutes();
|
|
82548
81622
|
disposeHubOverview();
|