@yoooclaw/phone-notifications 1.12.2 → 1.12.3
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/dist/bin/ntf.cjs +2 -5
- package/dist/bin/ntf.cjs.map +3 -3
- package/dist/env.d.ts +8 -1
- package/dist/env.d.ts.map +1 -1
- package/dist/index.cjs +431 -1242
- package/dist/index.cjs.map +12 -15
- package/dist/index.d.ts.map +1 -1
- package/dist/plugin/recordings.d.ts +0 -2
- package/dist/plugin/recordings.d.ts.map +1 -1
- package/dist/recording/downloader.d.ts +0 -7
- package/dist/recording/downloader.d.ts.map +1 -1
- package/dist/recording/index.d.ts +2 -4
- package/dist/recording/index.d.ts.map +1 -1
- package/dist/recording/result-writer.d.ts +1 -2
- package/dist/recording/result-writer.d.ts.map +1 -1
- package/dist/recording/storage.d.ts +1 -1
- package/dist/tunnel/frame-slimmer.d.ts +15 -1
- package/dist/tunnel/frame-slimmer.d.ts.map +1 -1
- package/dist/tunnel/relay-client.d.ts.map +1 -1
- package/dist/types.d.ts +12 -67
- package/dist/types.d.ts.map +1 -1
- package/dist/update/index.d.ts +10 -0
- package/dist/update/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/dist/recording/asr.d.ts +0 -119
- package/dist/recording/asr.d.ts.map +0 -1
- package/dist/recording/handler.d.ts +0 -38
- package/dist/recording/handler.d.ts.map +0 -1
- package/dist/recording/whisper-local.d.ts +0 -81
- package/dist/recording/whisper-local.d.ts.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -2959,7 +2959,7 @@ var import_node_path = require("node:path");
|
|
|
2959
2959
|
var import_node_fs = require("node:fs");
|
|
2960
2960
|
function readBuildInjectedVersion() {
|
|
2961
2961
|
if (false) {}
|
|
2962
|
-
const version = "1.12.
|
|
2962
|
+
const version = "1.12.3".trim();
|
|
2963
2963
|
return version || undefined;
|
|
2964
2964
|
}
|
|
2965
2965
|
function readPluginVersionFromPackageJson() {
|
|
@@ -5903,9 +5903,6 @@ function readPersistedEnvName() {
|
|
|
5903
5903
|
return;
|
|
5904
5904
|
}
|
|
5905
5905
|
function loadEnvName() {
|
|
5906
|
-
const fromEnvVar = process.env.PHONE_NOTIFICATIONS_ENV?.trim();
|
|
5907
|
-
if (fromEnvVar && VALID_ENVS.has(fromEnvVar))
|
|
5908
|
-
return fromEnvVar;
|
|
5909
5906
|
const fromDotEnv = readPersistedEnvName();
|
|
5910
5907
|
if (fromDotEnv)
|
|
5911
5908
|
return fromDotEnv;
|
|
@@ -6560,7 +6557,7 @@ class LightRuleRegistry {
|
|
|
6560
6557
|
}
|
|
6561
6558
|
|
|
6562
6559
|
// src/index.ts
|
|
6563
|
-
var
|
|
6560
|
+
var import_node_path31 = require("node:path");
|
|
6564
6561
|
|
|
6565
6562
|
// src/update/channel.ts
|
|
6566
6563
|
var PRODUCTION_UPDATE_BASE_URL = "https://artifact.yoooclaw.com/plugin";
|
|
@@ -7401,6 +7398,7 @@ function scheduleGatewayRestart(logger, deps = {}) {
|
|
|
7401
7398
|
}
|
|
7402
7399
|
|
|
7403
7400
|
// src/update/index.ts
|
|
7401
|
+
|
|
7404
7402
|
var PLUGIN_ID2 = "phone-notifications";
|
|
7405
7403
|
var VERSION_PATTERN2 = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/;
|
|
7406
7404
|
function shouldSkipAutoRestart() {
|
|
@@ -7543,8 +7541,14 @@ async function reconcileSucceededUpdateRecord(api, logger, targetDir) {
|
|
|
7543
7541
|
logger.warn(`更新已应用,但配置记录补写失败: ${String(err2)}`);
|
|
7544
7542
|
}
|
|
7545
7543
|
}
|
|
7546
|
-
function
|
|
7547
|
-
|
|
7544
|
+
function resolveModulePackageJsonPath() {
|
|
7545
|
+
return import_node_path11.join(__dirname, "..", "package.json");
|
|
7546
|
+
}
|
|
7547
|
+
function resolveEffectiveCurrentVersion(targetDir, modulePackageJsonPath = resolveModulePackageJsonPath()) {
|
|
7548
|
+
const installedVersion = pickNewestVersion([
|
|
7549
|
+
readPackageVersionAt(import_node_path11.join(targetDir, "package.json")),
|
|
7550
|
+
readPackageVersionAt(modulePackageJsonPath)
|
|
7551
|
+
]);
|
|
7548
7552
|
if (installedVersion && isNewerVersion(installedVersion, PLUGIN_VERSION)) {
|
|
7549
7553
|
return installedVersion;
|
|
7550
7554
|
}
|
|
@@ -7554,9 +7558,9 @@ function resolveEffectiveCurrentVersion(targetDir) {
|
|
|
7554
7558
|
}
|
|
7555
7559
|
return PLUGIN_VERSION;
|
|
7556
7560
|
}
|
|
7557
|
-
function
|
|
7561
|
+
function readPackageVersionAt(packageJsonPath) {
|
|
7558
7562
|
try {
|
|
7559
|
-
const parsed = JSON.parse(import_node_fs11.readFileSync(
|
|
7563
|
+
const parsed = JSON.parse(import_node_fs11.readFileSync(packageJsonPath, "utf-8"));
|
|
7560
7564
|
const version = typeof parsed.version === "string" ? parsed.version.trim() : "";
|
|
7561
7565
|
return VERSION_PATTERN2.test(version) ? version : undefined;
|
|
7562
7566
|
} catch {
|
|
@@ -7780,6 +7784,7 @@ function registerAutoUpdate(api, logger, config, getBroadcast, rememberBroadcast
|
|
|
7780
7784
|
const targetDir = resolveTargetDir(api);
|
|
7781
7785
|
const info = await checker.check();
|
|
7782
7786
|
const current = resolveEffectiveCurrentVersion(targetDir);
|
|
7787
|
+
logger.info(`plugin.checkUpdate: running=${PLUGIN_VERSION} effective=${current} ` + `latest=${info?.latest ?? "-"} hasUpdate=${!!info} targetDir=${targetDir}`);
|
|
7783
7788
|
const lastUpdateStatus = readLastUpdateStatus(targetDir);
|
|
7784
7789
|
const lastUpdateNotice = lastUpdateStatus ? buildLastUpdateNotice(lastUpdateStatus) : undefined;
|
|
7785
7790
|
if (!info) {
|
|
@@ -10789,7 +10794,7 @@ function pushCandidate(candidates, dir, label, onPath) {
|
|
|
10789
10794
|
}
|
|
10790
10795
|
|
|
10791
10796
|
// src/plugin/lifecycle.ts
|
|
10792
|
-
var
|
|
10797
|
+
var import_node_fs34 = require("node:fs");
|
|
10793
10798
|
|
|
10794
10799
|
// src/notification/app-name-map.ts
|
|
10795
10800
|
var import_node_fs25 = require("node:fs");
|
|
@@ -10956,9 +10961,6 @@ function transition(from, to) {
|
|
|
10956
10961
|
}
|
|
10957
10962
|
return to;
|
|
10958
10963
|
}
|
|
10959
|
-
function canStartTranscription(status) {
|
|
10960
|
-
return status === "synced" || status === "transcribe_failed" || status === "transcribed";
|
|
10961
|
-
}
|
|
10962
10964
|
class TransitionError extends Error {
|
|
10963
10965
|
from;
|
|
10964
10966
|
to;
|
|
@@ -11746,17 +11748,6 @@ async function downloadFile(url, destPath, logger, options) {
|
|
|
11746
11748
|
}
|
|
11747
11749
|
return { ok: false, error: lastError ?? "下载失败" };
|
|
11748
11750
|
}
|
|
11749
|
-
async function downloadRecordingFiles(ossAudioUrl, ossSrtUrl, audioDestPath, srtDestPath, logger, options) {
|
|
11750
|
-
const audio = await downloadFile(ossAudioUrl, audioDestPath, logger, options);
|
|
11751
|
-
let srt = null;
|
|
11752
|
-
if (ossSrtUrl) {
|
|
11753
|
-
srt = await downloadFile(ossSrtUrl, srtDestPath, logger, options);
|
|
11754
|
-
if (!srt.ok) {
|
|
11755
|
-
logger.warn(`[downloader] 打点文件下载失败(非致命): ${srt.error}`);
|
|
11756
|
-
}
|
|
11757
|
-
}
|
|
11758
|
-
return { audio, srt };
|
|
11759
|
-
}
|
|
11760
11751
|
function formatBytes(bytes) {
|
|
11761
11752
|
if (bytes < 1024)
|
|
11762
11753
|
return `${bytes}B`;
|
|
@@ -11767,1010 +11758,194 @@ function formatBytes(bytes) {
|
|
|
11767
11758
|
function sleep(ms) {
|
|
11768
11759
|
return new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
11769
11760
|
}
|
|
11770
|
-
// src/recording/
|
|
11761
|
+
// src/recording/result-writer.ts
|
|
11771
11762
|
var import_node_fs28 = require("node:fs");
|
|
11772
11763
|
var import_node_path24 = require("node:path");
|
|
11773
|
-
|
|
11774
|
-
|
|
11775
|
-
|
|
11776
|
-
|
|
11777
|
-
var DEFAULT_HTTP_MAX_ATTEMPTS = 3;
|
|
11778
|
-
var DEFAULT_HTTP_RETRY_BACKOFF_MS = 2000;
|
|
11779
|
-
function isAsrConfigured(config) {
|
|
11780
|
-
return !!config && !validateAsrConfig(config);
|
|
11781
|
-
}
|
|
11782
|
-
function validateAsrConfig(config) {
|
|
11783
|
-
if (!config?.mode) {
|
|
11784
|
-
return "asr.mode is required";
|
|
11785
|
-
}
|
|
11786
|
-
switch (config.mode) {
|
|
11787
|
-
case "api":
|
|
11788
|
-
return;
|
|
11789
|
-
case "local":
|
|
11790
|
-
return '本地 Whisper 模式已停用,请改用 asr.mode = "api"';
|
|
11791
|
-
case "yoooclaw":
|
|
11792
|
-
return "YoooClaw ASR 尚未实现(P2)";
|
|
11793
|
-
default:
|
|
11794
|
-
return `未知的 ASR mode: ${config.mode}`;
|
|
11795
|
-
}
|
|
11796
|
-
}
|
|
11797
|
-
async function initializeAsr(config, dataDir, logger) {
|
|
11798
|
-
const validationError = validateAsrConfig(config);
|
|
11799
|
-
if (validationError) {
|
|
11800
|
-
return {
|
|
11801
|
-
ok: false,
|
|
11802
|
-
mode: config.mode,
|
|
11803
|
-
error: validationError
|
|
11804
|
-
};
|
|
11764
|
+
function handleRecordingResultWrite(params, storage2, logger, options = {}) {
|
|
11765
|
+
const recordingId = normalizeRequiredText(params.recordingId, "recordingId");
|
|
11766
|
+
if (!params.transcript && !params.summary) {
|
|
11767
|
+
throw new Error("transcript or summary is required");
|
|
11805
11768
|
}
|
|
11806
|
-
|
|
11807
|
-
|
|
11808
|
-
|
|
11809
|
-
|
|
11810
|
-
|
|
11811
|
-
|
|
11812
|
-
ok: false,
|
|
11813
|
-
mode: "api",
|
|
11814
|
-
provider: "model-proxy",
|
|
11815
|
-
endpoint,
|
|
11816
|
-
language: config.api?.language ?? "auto",
|
|
11817
|
-
keyConfigured: false,
|
|
11818
|
-
error: "API Key 未设置,请在本次 asr.api.apiKey 中传入,或先写入 credentials.json / 执行 ntf auth set-api-key <apiKey>"
|
|
11819
|
-
};
|
|
11820
|
-
}
|
|
11821
|
-
return {
|
|
11822
|
-
ok: true,
|
|
11823
|
-
mode: "api",
|
|
11824
|
-
provider: "model-proxy",
|
|
11825
|
-
endpoint,
|
|
11826
|
-
language: config.api?.language ?? "auto",
|
|
11827
|
-
keyConfigured: true
|
|
11828
|
-
};
|
|
11769
|
+
let entry = storage2.findById(recordingId);
|
|
11770
|
+
if (!entry) {
|
|
11771
|
+
storage2.ingest(recordingId, buildPlaceholderMetadata(recordingId, params));
|
|
11772
|
+
entry = storage2.findById(recordingId);
|
|
11773
|
+
if (!entry) {
|
|
11774
|
+
throw new Error(`Recording not found: ${recordingId}`);
|
|
11829
11775
|
}
|
|
11830
|
-
|
|
11831
|
-
return {
|
|
11832
|
-
ok: false,
|
|
11833
|
-
mode: "local",
|
|
11834
|
-
error: '本地 Whisper 模式已停用,请改用 asr.mode = "api"'
|
|
11835
|
-
};
|
|
11836
|
-
case "yoooclaw":
|
|
11837
|
-
return {
|
|
11838
|
-
ok: false,
|
|
11839
|
-
mode: "yoooclaw",
|
|
11840
|
-
error: "YoooClaw ASR 尚未实现(P2)"
|
|
11841
|
-
};
|
|
11842
|
-
default:
|
|
11843
|
-
return {
|
|
11844
|
-
ok: false,
|
|
11845
|
-
mode: config.mode,
|
|
11846
|
-
error: `未知的 ASR mode: ${config.mode}`
|
|
11847
|
-
};
|
|
11776
|
+
logger.info(`[recording-result] 录音不存在,已按结果写入新建: ${recordingId}`);
|
|
11848
11777
|
}
|
|
11849
|
-
|
|
11850
|
-
|
|
11851
|
-
|
|
11852
|
-
|
|
11778
|
+
let transcript;
|
|
11779
|
+
let title = entry.title ?? entry.metadata.name ?? recordingId;
|
|
11780
|
+
let summaryText;
|
|
11781
|
+
if (params.transcript) {
|
|
11782
|
+
const written = writeTranscript(recordingId, entry.metadata, params.transcript, storage2, logger);
|
|
11783
|
+
transcript = written.transcript;
|
|
11784
|
+
title = written.title;
|
|
11853
11785
|
}
|
|
11854
|
-
|
|
11855
|
-
|
|
11856
|
-
switch (config.mode) {
|
|
11857
|
-
case "api":
|
|
11858
|
-
return await transcribeWithModelProxy(options.audioOssUrl, options.audioDurationMs, config.api, logger);
|
|
11859
|
-
case "local":
|
|
11860
|
-
return {
|
|
11861
|
-
ok: false,
|
|
11862
|
-
error: '本地 Whisper 模式已停用,请改用 asr.mode = "api"'
|
|
11863
|
-
};
|
|
11864
|
-
case "yoooclaw":
|
|
11865
|
-
return { ok: false, error: "YoooClaw ASR 尚未实现(P2)" };
|
|
11866
|
-
default:
|
|
11867
|
-
return {
|
|
11868
|
-
ok: false,
|
|
11869
|
-
error: `未知的 ASR mode: ${config.mode}`
|
|
11870
|
-
};
|
|
11871
|
-
}
|
|
11872
|
-
} catch (err2) {
|
|
11873
|
-
const msg = err2?.message ?? String(err2);
|
|
11874
|
-
logger.error(`[asr] 转写异常: ${msg}`);
|
|
11875
|
-
return { ok: false, error: msg };
|
|
11786
|
+
if (params.summary) {
|
|
11787
|
+
summaryText = writeSummary(recordingId, params.summary, storage2, logger);
|
|
11876
11788
|
}
|
|
11877
|
-
|
|
11878
|
-
|
|
11879
|
-
|
|
11880
|
-
|
|
11881
|
-
|
|
11882
|
-
|
|
11883
|
-
|
|
11884
|
-
|
|
11885
|
-
|
|
11886
|
-
if (markers.length > 0) {
|
|
11887
|
-
lines.push(`> 关键点数:${markers.length}`);
|
|
11789
|
+
const updated = storage2.markResultWritten(recordingId);
|
|
11790
|
+
emitResultStatus(recordingId, storage2, logger, options.notifyStatus, {
|
|
11791
|
+
transcript,
|
|
11792
|
+
summary: summaryText ?? storage2.readSummary(recordingId),
|
|
11793
|
+
title
|
|
11794
|
+
});
|
|
11795
|
+
const ossUrl = normalizeText(params.ossUrl);
|
|
11796
|
+
if (ossUrl) {
|
|
11797
|
+
downloadAudioInBackground(recordingId, ossUrl, storage2, logger, options);
|
|
11888
11798
|
}
|
|
11889
|
-
|
|
11890
|
-
|
|
11891
|
-
|
|
11892
|
-
|
|
11893
|
-
|
|
11894
|
-
|
|
11895
|
-
|
|
11896
|
-
|
|
11897
|
-
const m = sortedMarkers[markerIdx];
|
|
11898
|
-
lines.push(`**[关键点 ${formatTimestamp(m.timestamp_ms)}]**`);
|
|
11899
|
-
lines.push("");
|
|
11900
|
-
markerIdx++;
|
|
11901
|
-
}
|
|
11902
|
-
const segmentText = formatTranscriptSegmentText(seg);
|
|
11903
|
-
if (segmentText) {
|
|
11904
|
-
lines.push(segmentText);
|
|
11905
|
-
lines.push("");
|
|
11906
|
-
}
|
|
11907
|
-
}
|
|
11908
|
-
while (markerIdx < sortedMarkers.length) {
|
|
11909
|
-
const m = sortedMarkers[markerIdx];
|
|
11910
|
-
lines.push(`**[关键点 ${formatTimestamp(m.timestamp_ms)}]**`);
|
|
11911
|
-
lines.push("");
|
|
11912
|
-
markerIdx++;
|
|
11913
|
-
}
|
|
11914
|
-
} else if (result.text) {
|
|
11915
|
-
if (markers.length > 0) {
|
|
11916
|
-
lines.push("### 关键点");
|
|
11917
|
-
lines.push("");
|
|
11918
|
-
for (const m of markers) {
|
|
11919
|
-
lines.push(`- **[关键点 ${formatTimestamp(m.timestamp_ms)}]**`);
|
|
11920
|
-
}
|
|
11921
|
-
lines.push("");
|
|
11922
|
-
lines.push("---");
|
|
11923
|
-
lines.push("");
|
|
11799
|
+
return {
|
|
11800
|
+
ok: true,
|
|
11801
|
+
recordingId,
|
|
11802
|
+
transfer_status: updated.status,
|
|
11803
|
+
stored: {
|
|
11804
|
+
transcriptDataFile: updated.transcriptDataFile,
|
|
11805
|
+
transcriptFile: updated.transcriptFile,
|
|
11806
|
+
summaryFile: updated.summaryFile
|
|
11924
11807
|
}
|
|
11925
|
-
|
|
11926
|
-
lines.push("");
|
|
11927
|
-
}
|
|
11928
|
-
return lines.join(`
|
|
11929
|
-
`);
|
|
11808
|
+
};
|
|
11930
11809
|
}
|
|
11931
|
-
function
|
|
11932
|
-
|
|
11933
|
-
|
|
11934
|
-
|
|
11935
|
-
|
|
11936
|
-
|
|
11810
|
+
function buildPlaceholderMetadata(recordingId, params) {
|
|
11811
|
+
return {
|
|
11812
|
+
name: normalizeText(params.transcript?.title) ?? recordingId,
|
|
11813
|
+
duration_sec: 0,
|
|
11814
|
+
file_size_bytes: 0,
|
|
11815
|
+
created_at: normalizeText(params.transcript?.generatedAt) ?? new Date().toISOString(),
|
|
11816
|
+
oss_audio_url: normalizeText(params.ossUrl) ?? "",
|
|
11817
|
+
markers: [],
|
|
11818
|
+
transfer_status: "synced"
|
|
11819
|
+
};
|
|
11937
11820
|
}
|
|
11938
|
-
|
|
11939
|
-
const
|
|
11940
|
-
|
|
11941
|
-
|
|
11942
|
-
|
|
11943
|
-
markers,
|
|
11944
|
-
recordingName,
|
|
11945
|
-
durationSec,
|
|
11946
|
-
createdAt,
|
|
11947
|
-
transcriptsDir,
|
|
11948
|
-
transcriptDataDir,
|
|
11949
|
-
summariesDir,
|
|
11950
|
-
recordingId,
|
|
11951
|
-
logger
|
|
11952
|
-
} = params;
|
|
11953
|
-
const result = await transcribeAudio(audioFilePath, config, logger, {
|
|
11954
|
-
audioOssUrl,
|
|
11955
|
-
audioDurationMs: Math.max(0, Math.round(durationSec * 1000))
|
|
11956
|
-
});
|
|
11957
|
-
if (!result.ok) {
|
|
11958
|
-
return { ok: false, error: result.error };
|
|
11959
|
-
}
|
|
11960
|
-
const title = normalizeOptionalText3(result.summary) ? normalizeOptionalText3(result.summary) : extractSummary(result.text ?? "");
|
|
11961
|
-
const summary = result.summaryText ?? "";
|
|
11962
|
-
result.summary = title;
|
|
11963
|
-
const transcriptData = buildTranscriptDocument({
|
|
11821
|
+
function writeTranscript(recordingId, recording, transcript, storage2, logger) {
|
|
11822
|
+
const title = normalizeText(transcript.title) ?? normalizeText(recording.name) ?? recordingId;
|
|
11823
|
+
const segments = normalizeSegments2(transcript.segments);
|
|
11824
|
+
const text = normalizePossiblyEmptyText2(transcript.text) ?? joinSegmentTexts(segments) ?? normalizePossiblyEmptyText2(transcript.markdown) ?? "";
|
|
11825
|
+
const transcriptDocument = buildTranscriptDocument({
|
|
11964
11826
|
recordingId,
|
|
11965
|
-
generatedAt: new Date().toISOString(),
|
|
11966
|
-
source:
|
|
11967
|
-
provider:
|
|
11827
|
+
generatedAt: normalizeText(transcript.generatedAt) ?? new Date().toISOString(),
|
|
11828
|
+
source: {
|
|
11829
|
+
provider: normalizeText(transcript.source?.provider) ?? "app",
|
|
11830
|
+
taskId: normalizeText(transcript.source?.taskId),
|
|
11831
|
+
requestId: normalizeText(transcript.source?.requestId),
|
|
11832
|
+
status: normalizeText(transcript.source?.status),
|
|
11833
|
+
delivery: "result-write"
|
|
11968
11834
|
},
|
|
11969
11835
|
title,
|
|
11970
|
-
category:
|
|
11971
|
-
summary,
|
|
11972
|
-
text
|
|
11973
|
-
segments
|
|
11974
|
-
|
|
11975
|
-
startMs: segment.start_ms,
|
|
11976
|
-
endMs: segment.end_ms,
|
|
11977
|
-
speakerId: segment.speaker_id
|
|
11978
|
-
})),
|
|
11979
|
-
raw: result.rawResponse
|
|
11836
|
+
category: normalizeText(transcript.category),
|
|
11837
|
+
summary: normalizePossiblyEmptyText2(transcript.brief),
|
|
11838
|
+
text,
|
|
11839
|
+
segments,
|
|
11840
|
+
raw: transcript
|
|
11980
11841
|
});
|
|
11981
|
-
const
|
|
11982
|
-
|
|
11983
|
-
|
|
11984
|
-
|
|
11985
|
-
|
|
11986
|
-
|
|
11987
|
-
|
|
11988
|
-
|
|
11989
|
-
|
|
11990
|
-
logger.info(`[asr] 转写文本已写入: ${filePath}`);
|
|
11991
|
-
let summaryFilename;
|
|
11992
|
-
if (summary) {
|
|
11993
|
-
summaryFilename = `${recordingId}.md`;
|
|
11994
|
-
const summaryFilePath = import_node_path24.join(summariesDir, summaryFilename);
|
|
11995
|
-
import_node_fs28.writeFileSync(summaryFilePath, summary, "utf-8");
|
|
11996
|
-
logger.info(`[asr] 摘要文本已写入: ${summaryFilePath}`);
|
|
11997
|
-
}
|
|
11842
|
+
const transcriptDataFilename = storage2.buildTranscriptDataFilename(recordingId);
|
|
11843
|
+
import_node_fs28.writeFileSync(storage2.getTranscriptDataFilePath(recordingId), JSON.stringify(transcriptDocument, null, 2), "utf-8");
|
|
11844
|
+
storage2.setTranscriptDataFile(recordingId, transcriptDataFilename);
|
|
11845
|
+
const transcriptFilename = storage2.buildTranscriptFilename(recordingId, title);
|
|
11846
|
+
const markdown = normalizePossiblyEmptyText2(transcript.markdown) ?? buildTranscriptMarkdown(recording, title, text, segments);
|
|
11847
|
+
import_node_fs28.writeFileSync(import_node_path24.join(storage2.getTranscriptsDir(), transcriptFilename), ensureTrailingNewline(markdown), "utf-8");
|
|
11848
|
+
storage2.setTranscriptFile(recordingId, transcriptFilename);
|
|
11849
|
+
storage2.setTitle(recordingId, title);
|
|
11850
|
+
logger.info(`[recording-result] 转录文本已写入: ${recordingId}`);
|
|
11998
11851
|
return {
|
|
11999
|
-
|
|
12000
|
-
|
|
12001
|
-
transcriptDataFilename,
|
|
12002
|
-
summaryFilename,
|
|
12003
|
-
transcript: extractSourceTextListFromDocument(transcriptData),
|
|
12004
|
-
summary,
|
|
12005
|
-
title
|
|
11852
|
+
title,
|
|
11853
|
+
transcript: extractSourceTextListFromDocument(transcriptDocument) ?? []
|
|
12006
11854
|
};
|
|
12007
11855
|
}
|
|
12008
|
-
|
|
12009
|
-
const
|
|
12010
|
-
if (
|
|
12011
|
-
return
|
|
12012
|
-
}
|
|
12013
|
-
const apiKey = resolveModelProxyApiKey(apiConfig);
|
|
12014
|
-
const submitEndpoint = resolveModelProxyLongRecordingSubmitEndpoint(apiConfig);
|
|
12015
|
-
const submitBody = buildLongRecordingSubmitRequest(normalizedAudioOssUrl, apiConfig);
|
|
12016
|
-
logger.info(`[asr-submit] 提交长录音任务: endpoint=${submitEndpoint}, body=${stringifyForLog(submitBody) ?? "{}"}`);
|
|
12017
|
-
let res;
|
|
12018
|
-
try {
|
|
12019
|
-
res = await fetchWithRetry(submitEndpoint, {
|
|
12020
|
-
method: "POST",
|
|
12021
|
-
headers: {
|
|
12022
|
-
"Content-Type": "application/json",
|
|
12023
|
-
"X-Api-Key-Id": apiKey
|
|
12024
|
-
},
|
|
12025
|
-
body: JSON.stringify(submitBody)
|
|
12026
|
-
}, { logger, context: "asr-submit" });
|
|
12027
|
-
} catch (err2) {
|
|
12028
|
-
const msg = err2?.message ?? String(err2);
|
|
12029
|
-
logger.error(`[asr-submit] 提交长录音任务网络异常 (已重试): ${msg}`);
|
|
12030
|
-
return {
|
|
12031
|
-
ok: false,
|
|
12032
|
-
error: `Model Proxy ASR submit network error: ${msg}`
|
|
12033
|
-
};
|
|
12034
|
-
}
|
|
12035
|
-
if (!res.ok) {
|
|
12036
|
-
const errText = await res.text();
|
|
12037
|
-
logger.error(`[asr-submit-response] 提交长录音任务失败: status=${res.status}, body=${errText.slice(0, 500)}`);
|
|
12038
|
-
return {
|
|
12039
|
-
ok: false,
|
|
12040
|
-
error: `Model Proxy ASR error: ${res.status} ${errText.slice(0, 200)}`
|
|
12041
|
-
};
|
|
12042
|
-
}
|
|
12043
|
-
const raw = await res.json();
|
|
12044
|
-
logger.info(`[asr-submit-response] 提交长录音任务响应: ${stringifyForLog(raw) ?? "{}"}`);
|
|
12045
|
-
const submitEnvelopeError = buildModelProxyEnvelopeError(raw);
|
|
12046
|
-
if (submitEnvelopeError) {
|
|
12047
|
-
logger.error(`[asr-submit-response] 提交长录音任务失败: ${submitEnvelopeError}`);
|
|
12048
|
-
return {
|
|
12049
|
-
ok: false,
|
|
12050
|
-
error: `Model Proxy ASR 提交失败: ${submitEnvelopeError}`
|
|
12051
|
-
};
|
|
12052
|
-
}
|
|
12053
|
-
const data = unwrapResponse(raw);
|
|
12054
|
-
const taskId = normalizeOptionalText3(data?.taskId);
|
|
12055
|
-
const requestId = normalizeOptionalText3(data?.requestId);
|
|
12056
|
-
const status = normalizeLongRecordingStatus(data?.status);
|
|
12057
|
-
if (!taskId) {
|
|
12058
|
-
return {
|
|
12059
|
-
ok: false,
|
|
12060
|
-
error: "Model Proxy ASR 响应缺少 taskId"
|
|
12061
|
-
};
|
|
12062
|
-
}
|
|
12063
|
-
logger.info(`[asr] Model Proxy 长录音任务已提交: taskId=${taskId}, status=${status ?? "UNKNOWN"}, requestId=${requestId ?? "n/a"}`);
|
|
12064
|
-
if (status && LONG_RECORDING_TERMINAL_FAILURE_STATUSES.has(status)) {
|
|
12065
|
-
return {
|
|
12066
|
-
ok: false,
|
|
12067
|
-
error: buildLongRecordingStatusError(data, status)
|
|
12068
|
-
};
|
|
11856
|
+
function writeSummary(recordingId, summary, storage2, logger) {
|
|
11857
|
+
const markdown = normalizePossiblyEmptyText2(summary.markdown) ?? buildStructuredSummaryMarkdown(summary.structured);
|
|
11858
|
+
if (markdown === undefined) {
|
|
11859
|
+
return;
|
|
12069
11860
|
}
|
|
12070
|
-
|
|
12071
|
-
|
|
12072
|
-
|
|
12073
|
-
|
|
12074
|
-
|
|
12075
|
-
apiConfig,
|
|
12076
|
-
logger
|
|
12077
|
-
});
|
|
11861
|
+
const summaryFilename = storage2.buildSummaryFilename(recordingId);
|
|
11862
|
+
import_node_fs28.writeFileSync(storage2.getSummaryFilePath(recordingId), ensureTrailingNewline(markdown), "utf-8");
|
|
11863
|
+
storage2.setSummaryFile(recordingId, summaryFilename);
|
|
11864
|
+
logger.info(`[recording-result] 总结已写入: ${recordingId}`);
|
|
11865
|
+
return markdown.trim();
|
|
12078
11866
|
}
|
|
12079
|
-
function
|
|
12080
|
-
|
|
11867
|
+
function downloadAudioInBackground(recordingId, ossUrl, storage2, logger, options) {
|
|
11868
|
+
const audioDestPath = storage2.getAudioFilePath(recordingId, ossUrl);
|
|
11869
|
+
logger.info(`[recording-result] 开始下载音频: ${recordingId}, audio=${ossUrl}`);
|
|
11870
|
+
downloadFile(ossUrl, audioDestPath, logger).then((result) => {
|
|
11871
|
+
if (!result.ok) {
|
|
11872
|
+
const error = `音频下载失败: ${result.error}`;
|
|
11873
|
+
logger.error(`[recording-result] ${error}: ${recordingId}`);
|
|
11874
|
+
storage2.setLastError(recordingId, error);
|
|
11875
|
+
emitDownloadStatus(recordingId, storage2, logger, options.notifyStatus);
|
|
11876
|
+
return;
|
|
11877
|
+
}
|
|
11878
|
+
storage2.setAudioFile(recordingId, storage2.buildAudioFilename(recordingId, ossUrl));
|
|
11879
|
+
logger.info(`[recording-result] 音频已更新: ${recordingId}`);
|
|
11880
|
+
emitDownloadStatus(recordingId, storage2, logger, options.notifyStatus);
|
|
11881
|
+
}).catch((err2) => {
|
|
11882
|
+
const error = `音频下载异常: ${err2?.message ?? err2}`;
|
|
11883
|
+
logger.error(`[recording-result] ${error}: ${recordingId}`);
|
|
11884
|
+
storage2.setLastError(recordingId, error);
|
|
11885
|
+
emitDownloadStatus(recordingId, storage2, logger, options.notifyStatus);
|
|
11886
|
+
});
|
|
12081
11887
|
}
|
|
12082
|
-
function
|
|
12083
|
-
const
|
|
12084
|
-
|
|
11888
|
+
function emitDownloadStatus(recordingId, storage2, logger, notifyStatus) {
|
|
11889
|
+
const entry = storage2.findById(recordingId);
|
|
11890
|
+
if (!entry)
|
|
11891
|
+
return;
|
|
11892
|
+
emitResultStatus(recordingId, storage2, logger, notifyStatus, {
|
|
11893
|
+
summary: storage2.readSummary(recordingId),
|
|
11894
|
+
title: entry.title ?? entry.metadata.name ?? recordingId
|
|
11895
|
+
});
|
|
12085
11896
|
}
|
|
12086
|
-
function
|
|
12087
|
-
|
|
12088
|
-
|
|
12089
|
-
|
|
11897
|
+
function emitResultStatus(recordingId, storage2, logger, notifyStatus, extras) {
|
|
11898
|
+
if (!notifyStatus)
|
|
11899
|
+
return;
|
|
11900
|
+
const entry = storage2.findById(recordingId);
|
|
11901
|
+
if (!entry)
|
|
11902
|
+
return;
|
|
11903
|
+
try {
|
|
11904
|
+
notifyStatus({
|
|
11905
|
+
recordingId: entry.id,
|
|
11906
|
+
transfer_status: entry.status,
|
|
11907
|
+
audioFile: entry.audioFile,
|
|
11908
|
+
srtFile: entry.srtFile,
|
|
11909
|
+
transcriptDataFile: entry.transcriptDataFile,
|
|
11910
|
+
transcriptFile: entry.transcriptFile,
|
|
11911
|
+
summaryFile: entry.summaryFile,
|
|
11912
|
+
transcript: extras.transcript,
|
|
11913
|
+
summary: extras.summary,
|
|
11914
|
+
title: extras.title,
|
|
11915
|
+
updatedAt: entry.updatedAt,
|
|
11916
|
+
error: entry.lastError
|
|
11917
|
+
});
|
|
11918
|
+
} catch (err2) {
|
|
11919
|
+
logger.error(`[recording-result] 状态事件发送失败: ${recordingId}, ${err2?.message ?? err2}`);
|
|
12090
11920
|
}
|
|
12091
|
-
return getEnvUrls().modelProxyLongRecordingQueryTaskResultBaseUrl;
|
|
12092
11921
|
}
|
|
12093
|
-
function
|
|
12094
|
-
|
|
12095
|
-
|
|
12096
|
-
|
|
12097
|
-
|
|
12098
|
-
|
|
11922
|
+
function normalizeSegments2(segments) {
|
|
11923
|
+
if (!Array.isArray(segments))
|
|
11924
|
+
return [];
|
|
11925
|
+
return segments.flatMap((segment) => {
|
|
11926
|
+
const text = normalizeText(segment.text);
|
|
11927
|
+
if (!text)
|
|
11928
|
+
return [];
|
|
11929
|
+
return [{
|
|
11930
|
+
text,
|
|
11931
|
+
startMs: typeof segment.startMs === "number" ? segment.startMs : undefined,
|
|
11932
|
+
endMs: typeof segment.endMs === "number" ? segment.endMs : undefined,
|
|
11933
|
+
speakerId: Number.isInteger(segment.speakerId) ? segment.speakerId : undefined
|
|
11934
|
+
}];
|
|
11935
|
+
});
|
|
12099
11936
|
}
|
|
12100
|
-
function
|
|
12101
|
-
const
|
|
12102
|
-
|
|
12103
|
-
|
|
12104
|
-
|
|
12105
|
-
body.language = apiConfig.language.trim();
|
|
12106
|
-
}
|
|
12107
|
-
if (typeof apiConfig?.enableNormalization === "boolean") {
|
|
12108
|
-
body.enableNormalization = apiConfig.enableNormalization;
|
|
12109
|
-
}
|
|
12110
|
-
return body;
|
|
11937
|
+
function joinSegmentTexts(segments) {
|
|
11938
|
+
const text = segments.map((segment) => segment.text).filter(Boolean).join(`
|
|
11939
|
+
|
|
11940
|
+
`).trim();
|
|
11941
|
+
return text || undefined;
|
|
12111
11942
|
}
|
|
12112
|
-
|
|
12113
|
-
const {
|
|
12114
|
-
apiKey,
|
|
12115
|
-
taskId,
|
|
12116
|
-
initialRequestId,
|
|
12117
|
-
audioDurationMs,
|
|
12118
|
-
apiConfig,
|
|
12119
|
-
logger
|
|
12120
|
-
} = params;
|
|
12121
|
-
const queryBaseUrl = resolveModelProxyLongRecordingQueryTaskResultBaseUrl(apiConfig);
|
|
12122
|
-
let lastStatus;
|
|
12123
|
-
const pollIntervalMs = getPollIntervalMs();
|
|
12124
|
-
for (let attempt = 1;attempt <= DEFAULT_LONG_RECORDING_MAX_POLL_ATTEMPTS; attempt++) {
|
|
12125
|
-
const queryUrl = `${queryBaseUrl}/${encodeURIComponent(taskId)}`;
|
|
12126
|
-
let res;
|
|
12127
|
-
try {
|
|
12128
|
-
res = await fetch(queryUrl, {
|
|
12129
|
-
method: "GET",
|
|
12130
|
-
headers: {
|
|
12131
|
-
"X-Api-Key-Id": apiKey
|
|
12132
|
-
}
|
|
12133
|
-
});
|
|
12134
|
-
} catch (err2) {
|
|
12135
|
-
const msg = err2?.message ?? String(err2);
|
|
12136
|
-
logger.warn(`[asr-query] 长录音任务查询网络异常: taskId=${taskId}, attempt=${attempt}, error=${msg} — 等待下次轮询`);
|
|
12137
|
-
if (attempt < DEFAULT_LONG_RECORDING_MAX_POLL_ATTEMPTS) {
|
|
12138
|
-
await sleep2(pollIntervalMs);
|
|
12139
|
-
continue;
|
|
12140
|
-
}
|
|
12141
|
-
return {
|
|
12142
|
-
ok: false,
|
|
12143
|
-
error: `Model Proxy ASR query network error: ${msg}`
|
|
12144
|
-
};
|
|
12145
|
-
}
|
|
12146
|
-
if (!res.ok) {
|
|
12147
|
-
const errText = await res.text().catch(() => "");
|
|
12148
|
-
if (isRetryableHttpStatus(res.status)) {
|
|
12149
|
-
logger.warn(`[asr-query] 长录音任务查询暂时失败: taskId=${taskId}, attempt=${attempt}, status=${res.status}, body=${errText.slice(0, 200)} — 等待下次轮询`);
|
|
12150
|
-
if (attempt < DEFAULT_LONG_RECORDING_MAX_POLL_ATTEMPTS) {
|
|
12151
|
-
await sleep2(pollIntervalMs);
|
|
12152
|
-
continue;
|
|
12153
|
-
}
|
|
12154
|
-
} else {
|
|
12155
|
-
logger.error(`[asr-query-response] 长录音任务查询失败: taskId=${taskId}, attempt=${attempt}, status=${res.status}, body=${errText.slice(0, 500)}`);
|
|
12156
|
-
}
|
|
12157
|
-
return {
|
|
12158
|
-
ok: false,
|
|
12159
|
-
error: `Model Proxy ASR query error: ${res.status} ${errText.slice(0, 200)}`
|
|
12160
|
-
};
|
|
12161
|
-
}
|
|
12162
|
-
const raw = await res.json();
|
|
12163
|
-
const queryEnvelopeError = buildModelProxyEnvelopeError(raw);
|
|
12164
|
-
if (queryEnvelopeError) {
|
|
12165
|
-
logger.error(`[asr-query-response] 长录音任务查询失败: taskId=${taskId}, attempt=${attempt}, ${queryEnvelopeError}`);
|
|
12166
|
-
return {
|
|
12167
|
-
ok: false,
|
|
12168
|
-
error: `Model Proxy ASR 查询失败: ${queryEnvelopeError}`
|
|
12169
|
-
};
|
|
12170
|
-
}
|
|
12171
|
-
const data = unwrapResponse(raw);
|
|
12172
|
-
const status = normalizeLongRecordingStatus(data?.status) ?? "UNKNOWN";
|
|
12173
|
-
const requestId = normalizeOptionalText3(data?.requestId) ?? initialRequestId;
|
|
12174
|
-
if (status !== lastStatus) {
|
|
12175
|
-
logger.info(`[asr-query-response] 长录音任务查询响应: taskId=${taskId}, attempt=${attempt}, body=${stringifyForLog(raw) ?? "{}"}`);
|
|
12176
|
-
logger.info(`[asr] Model Proxy 长录音任务状态: taskId=${taskId}, status=${status}, attempt=${attempt}, requestId=${requestId ?? "n/a"}`);
|
|
12177
|
-
lastStatus = status;
|
|
12178
|
-
}
|
|
12179
|
-
if (status === "SUCCEEDED") {
|
|
12180
|
-
return buildLongRecordingSuccessResult(taskId, requestId, data, audioDurationMs, logger);
|
|
12181
|
-
}
|
|
12182
|
-
if (LONG_RECORDING_TERMINAL_FAILURE_STATUSES.has(status)) {
|
|
12183
|
-
return {
|
|
12184
|
-
ok: false,
|
|
12185
|
-
error: buildLongRecordingStatusError(data, status)
|
|
12186
|
-
};
|
|
12187
|
-
}
|
|
12188
|
-
if (!LONG_RECORDING_RUNNING_STATUSES.has(status)) {
|
|
12189
|
-
return {
|
|
12190
|
-
ok: false,
|
|
12191
|
-
error: `Model Proxy ASR 返回未知任务状态: ${status}`
|
|
12192
|
-
};
|
|
12193
|
-
}
|
|
12194
|
-
if (attempt < DEFAULT_LONG_RECORDING_MAX_POLL_ATTEMPTS) {
|
|
12195
|
-
await sleep2(pollIntervalMs);
|
|
12196
|
-
}
|
|
12197
|
-
}
|
|
12198
|
-
return {
|
|
12199
|
-
ok: false,
|
|
12200
|
-
error: `Model Proxy ASR 轮询超时: taskId=${taskId}, waited=${DEFAULT_LONG_RECORDING_MAX_POLL_ATTEMPTS * pollIntervalMs}ms`
|
|
12201
|
-
};
|
|
12202
|
-
}
|
|
12203
|
-
function buildLongRecordingSuccessResult(taskId, requestId, data, audioDurationMs, logger) {
|
|
12204
|
-
const sourceTextList = normalizeLongRecordingSourceTextList(data?.recordResult?.sourceTextList);
|
|
12205
|
-
const listResult = extractLongRecordingTextFromList(sourceTextList, audioDurationMs);
|
|
12206
|
-
const sourceText = normalizeOptionalText3(data?.recordResult?.sourceText);
|
|
12207
|
-
const summaryText = normalizeOptionalText3(data?.recordResult?.summaryResult) ?? "";
|
|
12208
|
-
const title = normalizeOptionalText3(data?.recordResult?.title);
|
|
12209
|
-
const category = normalizeOptionalText3(data?.recordResult?.category);
|
|
12210
|
-
const text = listResult.text ?? sourceText ?? summaryText;
|
|
12211
|
-
const status = normalizeLongRecordingStatus(data?.status) ?? "SUCCEEDED";
|
|
12212
|
-
if (!listResult.text && !sourceText && summaryText) {
|
|
12213
|
-
logger.warn(`[asr] Model Proxy 长录音结果缺少 sourceTextList/sourceText,已回退使用 summaryResult 作为转写文本: taskId=${taskId}`);
|
|
12214
|
-
}
|
|
12215
|
-
logger.info(`[asr] Model Proxy 长录音转写完成: taskId=${taskId}, requestId=${requestId ?? "n/a"}, chars=${text.length}`);
|
|
12216
|
-
return {
|
|
12217
|
-
ok: true,
|
|
12218
|
-
text,
|
|
12219
|
-
segments: listResult.segments,
|
|
12220
|
-
summary: title,
|
|
12221
|
-
summaryText,
|
|
12222
|
-
category,
|
|
12223
|
-
sourceInfo: {
|
|
12224
|
-
provider: "model-proxy",
|
|
12225
|
-
taskId,
|
|
12226
|
-
requestId,
|
|
12227
|
-
status
|
|
12228
|
-
},
|
|
12229
|
-
rawResponse: data
|
|
12230
|
-
};
|
|
12231
|
-
}
|
|
12232
|
-
function buildLongRecordingStatusError(data, status) {
|
|
12233
|
-
const errorMessage = normalizeOptionalText3(data?.errorMessage);
|
|
12234
|
-
return errorMessage ? `Model Proxy ASR ${status}: ${errorMessage}` : `Model Proxy ASR ${status}`;
|
|
12235
|
-
}
|
|
12236
|
-
function buildModelProxyEnvelopeError(payload) {
|
|
12237
|
-
if (!payload || typeof payload !== "object" || !("data" in payload)) {
|
|
12238
|
-
return;
|
|
12239
|
-
}
|
|
12240
|
-
const envelope = payload;
|
|
12241
|
-
const explicitFailure = envelope.success === false;
|
|
12242
|
-
const message = normalizeOptionalText3(envelope.message);
|
|
12243
|
-
if (!explicitFailure && (!message || envelope.data)) {
|
|
12244
|
-
return;
|
|
12245
|
-
}
|
|
12246
|
-
const code = normalizeOptionalCode(envelope.code);
|
|
12247
|
-
if (code && message) {
|
|
12248
|
-
return `${code} ${message}`;
|
|
12249
|
-
}
|
|
12250
|
-
return message ?? code ?? "response envelope indicates failure";
|
|
12251
|
-
}
|
|
12252
|
-
function unwrapResponse(payload) {
|
|
12253
|
-
if (payload && typeof payload === "object" && "data" in payload && payload.data && typeof payload.data === "object") {
|
|
12254
|
-
return payload.data;
|
|
12255
|
-
}
|
|
12256
|
-
return payload;
|
|
12257
|
-
}
|
|
12258
|
-
function normalizeApiKeyHeaderValue(apiKey) {
|
|
12259
|
-
return apiKey.startsWith("Bearer ") ? apiKey.slice("Bearer ".length) : apiKey;
|
|
12260
|
-
}
|
|
12261
|
-
function normalizeLongRecordingStatus(status) {
|
|
12262
|
-
return typeof status === "string" && status.trim() ? status.trim().toUpperCase() : undefined;
|
|
12263
|
-
}
|
|
12264
|
-
function normalizeOptionalText3(value) {
|
|
12265
|
-
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
12266
|
-
}
|
|
12267
|
-
function normalizeOptionalCode(value) {
|
|
12268
|
-
if (typeof value === "string" && value.trim()) {
|
|
12269
|
-
return value.trim();
|
|
12270
|
-
}
|
|
12271
|
-
if (typeof value === "number" && Number.isFinite(value)) {
|
|
12272
|
-
return String(value);
|
|
12273
|
-
}
|
|
12274
|
-
return;
|
|
12275
|
-
}
|
|
12276
|
-
function normalizeOptionalInteger2(value) {
|
|
12277
|
-
return Number.isInteger(value) ? Number(value) : undefined;
|
|
12278
|
-
}
|
|
12279
|
-
function normalizeOptionalNonNegativeNumber(value) {
|
|
12280
|
-
return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : undefined;
|
|
12281
|
-
}
|
|
12282
|
-
function normalizeLongRecordingSourceTextList(value) {
|
|
12283
|
-
if (!Array.isArray(value)) {
|
|
12284
|
-
return [];
|
|
12285
|
-
}
|
|
12286
|
-
return value.flatMap((item) => {
|
|
12287
|
-
if (!item || typeof item !== "object") {
|
|
12288
|
-
return [];
|
|
12289
|
-
}
|
|
12290
|
-
const record = item;
|
|
12291
|
-
const content = normalizeOptionalText3(record.content);
|
|
12292
|
-
if (!content) {
|
|
12293
|
-
return [];
|
|
12294
|
-
}
|
|
12295
|
-
return [{
|
|
12296
|
-
content,
|
|
12297
|
-
speakerId: normalizeOptionalInteger2(record.speakerId),
|
|
12298
|
-
startTime: normalizeOptionalNonNegativeNumber(record.startTime),
|
|
12299
|
-
endTime: normalizeOptionalNonNegativeNumber(record.endTime)
|
|
12300
|
-
}];
|
|
12301
|
-
});
|
|
12302
|
-
}
|
|
12303
|
-
function extractLongRecordingTextFromList(items, finalFallbackEndMs) {
|
|
12304
|
-
if (items.length === 0) {
|
|
12305
|
-
return {
|
|
12306
|
-
segments: []
|
|
12307
|
-
};
|
|
12308
|
-
}
|
|
12309
|
-
const segments = items.map((item, index) => {
|
|
12310
|
-
const startMs = item.startTime ?? 0;
|
|
12311
|
-
const explicitEndMs = item.endTime;
|
|
12312
|
-
const nextStart = items[index + 1]?.startTime;
|
|
12313
|
-
const fallbackEndMs = index === items.length - 1 ? normalizeOptionalNonNegativeNumber(finalFallbackEndMs) : undefined;
|
|
12314
|
-
const endMs = explicitEndMs ?? nextStart ?? fallbackEndMs ?? startMs;
|
|
12315
|
-
return {
|
|
12316
|
-
start_ms: startMs,
|
|
12317
|
-
end_ms: Math.max(startMs, endMs),
|
|
12318
|
-
text: item.content,
|
|
12319
|
-
speaker_id: item.speakerId
|
|
12320
|
-
};
|
|
12321
|
-
});
|
|
12322
|
-
return {
|
|
12323
|
-
text: items.map((item) => item.content).join(`
|
|
12324
|
-
|
|
12325
|
-
`),
|
|
12326
|
-
segments
|
|
12327
|
-
};
|
|
12328
|
-
}
|
|
12329
|
-
function stringifyForLog(value, maxLength = 500) {
|
|
12330
|
-
if (value == null) {
|
|
12331
|
-
return;
|
|
12332
|
-
}
|
|
12333
|
-
if (typeof value === "string") {
|
|
12334
|
-
const trimmed = value.trim();
|
|
12335
|
-
return trimmed ? trimmed.slice(0, maxLength) : undefined;
|
|
12336
|
-
}
|
|
12337
|
-
try {
|
|
12338
|
-
const serialized = JSON.stringify(value);
|
|
12339
|
-
return serialized ? serialized.slice(0, maxLength) : undefined;
|
|
12340
|
-
} catch {
|
|
12341
|
-
return String(value).slice(0, maxLength);
|
|
12342
|
-
}
|
|
12343
|
-
}
|
|
12344
|
-
async function sleep2(ms) {
|
|
12345
|
-
await new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
12346
|
-
}
|
|
12347
|
-
function isRetryableHttpStatus(status) {
|
|
12348
|
-
return status === 429 || status >= 500;
|
|
12349
|
-
}
|
|
12350
|
-
function getHttpRetryBackoffMs() {
|
|
12351
|
-
const raw = process.env.OPENCLAW_ASR_HTTP_RETRY_BACKOFF_MS;
|
|
12352
|
-
if (raw) {
|
|
12353
|
-
const parsed = Number(raw);
|
|
12354
|
-
if (Number.isFinite(parsed) && parsed >= 0) {
|
|
12355
|
-
return parsed;
|
|
12356
|
-
}
|
|
12357
|
-
}
|
|
12358
|
-
return DEFAULT_HTTP_RETRY_BACKOFF_MS;
|
|
12359
|
-
}
|
|
12360
|
-
function getPollIntervalMs() {
|
|
12361
|
-
const raw = process.env.OPENCLAW_ASR_POLL_INTERVAL_MS;
|
|
12362
|
-
if (raw) {
|
|
12363
|
-
const parsed = Number(raw);
|
|
12364
|
-
if (Number.isFinite(parsed) && parsed >= 0) {
|
|
12365
|
-
return parsed;
|
|
12366
|
-
}
|
|
12367
|
-
}
|
|
12368
|
-
return DEFAULT_LONG_RECORDING_POLL_INTERVAL_MS;
|
|
12369
|
-
}
|
|
12370
|
-
async function fetchWithRetry(url, init, options) {
|
|
12371
|
-
const maxAttempts = options.maxAttempts ?? DEFAULT_HTTP_MAX_ATTEMPTS;
|
|
12372
|
-
const baseBackoff = options.backoffMs ?? getHttpRetryBackoffMs();
|
|
12373
|
-
let lastError;
|
|
12374
|
-
for (let attempt = 1;attempt <= maxAttempts; attempt++) {
|
|
12375
|
-
try {
|
|
12376
|
-
const res = await fetch(url, init);
|
|
12377
|
-
if (isRetryableHttpStatus(res.status) && attempt < maxAttempts) {
|
|
12378
|
-
const errText = await res.text().catch(() => "");
|
|
12379
|
-
const delay = baseBackoff * Math.pow(2, attempt - 1);
|
|
12380
|
-
options.logger.warn(`[${options.context}] HTTP ${res.status} (attempt ${attempt}/${maxAttempts}), ${delay}ms 后重试, body=${errText.slice(0, 200)}`);
|
|
12381
|
-
await sleep2(delay);
|
|
12382
|
-
continue;
|
|
12383
|
-
}
|
|
12384
|
-
return res;
|
|
12385
|
-
} catch (err2) {
|
|
12386
|
-
lastError = err2;
|
|
12387
|
-
if (attempt < maxAttempts) {
|
|
12388
|
-
const delay = baseBackoff * Math.pow(2, attempt - 1);
|
|
12389
|
-
const msg = err2?.message ?? String(err2);
|
|
12390
|
-
options.logger.warn(`[${options.context}] 网络异常 (attempt ${attempt}/${maxAttempts}): ${msg}, ${delay}ms 后重试`);
|
|
12391
|
-
await sleep2(delay);
|
|
12392
|
-
continue;
|
|
12393
|
-
}
|
|
12394
|
-
}
|
|
12395
|
-
}
|
|
12396
|
-
throw lastError instanceof Error ? lastError : new Error(String(lastError));
|
|
12397
|
-
}
|
|
12398
|
-
function formatTimestamp(ms) {
|
|
12399
|
-
const totalSeconds = Math.floor(ms / 1000);
|
|
12400
|
-
const minutes = Math.floor(totalSeconds / 60);
|
|
12401
|
-
const seconds = totalSeconds % 60;
|
|
12402
|
-
return `${String(minutes).padStart(2, "0")}:${String(seconds).padStart(2, "0")}`;
|
|
12403
|
-
}
|
|
12404
|
-
function formatDuration(seconds) {
|
|
12405
|
-
const h = Math.floor(seconds / 3600);
|
|
12406
|
-
const m = Math.floor(seconds % 3600 / 60);
|
|
12407
|
-
const s = Math.floor(seconds % 60);
|
|
12408
|
-
if (h > 0) {
|
|
12409
|
-
return `${h}:${String(m).padStart(2, "0")}:${String(s).padStart(2, "0")}`;
|
|
12410
|
-
}
|
|
12411
|
-
return `${String(m).padStart(2, "0")}:${String(s).padStart(2, "0")}`;
|
|
12412
|
-
}
|
|
12413
|
-
function hasText(value) {
|
|
12414
|
-
return typeof value === "string" && value.trim().length > 0;
|
|
12415
|
-
}
|
|
12416
|
-
function formatTranscriptSegmentText(segment) {
|
|
12417
|
-
const text = segment.text.trim();
|
|
12418
|
-
if (!text) {
|
|
12419
|
-
return text;
|
|
12420
|
-
}
|
|
12421
|
-
if (typeof segment.speaker_id === "number") {
|
|
12422
|
-
return `说话人${segment.speaker_id}:${text}`;
|
|
12423
|
-
}
|
|
12424
|
-
return text;
|
|
12425
|
-
}
|
|
12426
|
-
// src/recording/whisper-local.ts
|
|
12427
|
-
var MODEL_DISK_SIZES = {
|
|
12428
|
-
tiny: 75 * 1024 * 1024,
|
|
12429
|
-
base: 140 * 1024 * 1024,
|
|
12430
|
-
small: 460 * 1024 * 1024,
|
|
12431
|
-
medium: 1500 * 1024 * 1024,
|
|
12432
|
-
"large-v3": 3000 * 1024 * 1024
|
|
12433
|
-
};
|
|
12434
|
-
// src/recording/handler.ts
|
|
12435
|
-
function emitRecordingStatus(recordingId, storage2, logger, notifyStatus, error, extras) {
|
|
12436
|
-
if (!notifyStatus) {
|
|
12437
|
-
return;
|
|
12438
|
-
}
|
|
12439
|
-
const entry = storage2.findById(recordingId);
|
|
12440
|
-
if (!entry) {
|
|
12441
|
-
return;
|
|
12442
|
-
}
|
|
12443
|
-
const title = extras?.title?.trim() || entry.title?.trim() || entry.metadata.name?.trim() || entry.id;
|
|
12444
|
-
const persistedError = entry.lastError?.trim() || undefined;
|
|
12445
|
-
try {
|
|
12446
|
-
notifyStatus({
|
|
12447
|
-
recordingId: entry.id,
|
|
12448
|
-
transfer_status: entry.status,
|
|
12449
|
-
audioFile: entry.audioFile,
|
|
12450
|
-
srtFile: entry.srtFile,
|
|
12451
|
-
transcriptDataFile: entry.transcriptDataFile,
|
|
12452
|
-
transcriptFile: entry.transcriptFile,
|
|
12453
|
-
transcript: extras?.transcript,
|
|
12454
|
-
summary: extras?.summary,
|
|
12455
|
-
title,
|
|
12456
|
-
updatedAt: entry.updatedAt,
|
|
12457
|
-
error: error ?? persistedError
|
|
12458
|
-
});
|
|
12459
|
-
} catch (err2) {
|
|
12460
|
-
logger.error(`[recording-status] 状态事件发送失败: ${recordingId}, ${err2?.message ?? err2}`);
|
|
12461
|
-
}
|
|
12462
|
-
}
|
|
12463
|
-
async function runRecordingSyncInBackground(metadata, recordingId, storage2, asrConfig, logger, options) {
|
|
12464
|
-
const audioDestPath = storage2.getAudioFilePath(recordingId, metadata.oss_audio_url);
|
|
12465
|
-
const srtDestPath = storage2.getSrtFilePath(recordingId);
|
|
12466
|
-
logger.info(`[recording-sync] 开始下载录音文件: ${recordingId}, audio=${metadata.oss_audio_url}`);
|
|
12467
|
-
const downloadResult = await downloadRecordingFiles(metadata.oss_audio_url, metadata.oss_srt_url, audioDestPath, srtDestPath, logger);
|
|
12468
|
-
if (!downloadResult.audio.ok) {
|
|
12469
|
-
const error = `音频下载失败: ${downloadResult.audio.error}`;
|
|
12470
|
-
logger.error(`[recording-sync] ${error}: ${recordingId}`);
|
|
12471
|
-
storage2.updateStatus(recordingId, "sync_failed");
|
|
12472
|
-
storage2.setLastError(recordingId, error);
|
|
12473
|
-
emitRecordingStatus(recordingId, storage2, logger, options.notifyStatus, error);
|
|
12474
|
-
return;
|
|
12475
|
-
}
|
|
12476
|
-
storage2.setLastError(recordingId, undefined);
|
|
12477
|
-
storage2.setAudioFile(recordingId, storage2.buildAudioFilename(recordingId, metadata.oss_audio_url));
|
|
12478
|
-
if (downloadResult.srt?.ok) {
|
|
12479
|
-
storage2.setSrtFile(recordingId, storage2.buildSrtFilename(recordingId));
|
|
12480
|
-
}
|
|
12481
|
-
const currentBeforeSynced = storage2.findById(recordingId);
|
|
12482
|
-
if (currentBeforeSynced?.status === "syncing_openclaw" || currentBeforeSynced?.status === "sync_failed") {
|
|
12483
|
-
storage2.updateStatus(recordingId, "synced");
|
|
12484
|
-
} else {
|
|
12485
|
-
logger.info(`[recording-sync] 录音已被其他通道更新,保留状态: ${recordingId}, status=${currentBeforeSynced?.status}`);
|
|
12486
|
-
}
|
|
12487
|
-
logger.info(`[recording-sync] 录音已同步: ${recordingId}`);
|
|
12488
|
-
emitRecordingStatus(recordingId, storage2, logger, options.notifyStatus);
|
|
12489
|
-
const currentAfterSynced = storage2.findById(recordingId);
|
|
12490
|
-
if (isAsrConfigured(asrConfig) && canStartTranscription(currentAfterSynced?.status ?? "synced")) {
|
|
12491
|
-
await triggerTranscription(recordingId, storage2, asrConfig, logger, options);
|
|
12492
|
-
}
|
|
12493
|
-
}
|
|
12494
|
-
async function handleRecordingSync(recordingId, metadata, storage2, asrConfig, logger, options = {}) {
|
|
12495
|
-
const existing = storage2.findById(recordingId);
|
|
12496
|
-
const shouldDownloadAndSync = !existing || existing.metadata.oss_audio_url !== metadata.oss_audio_url || !existing.audioFile || existing.status === "syncing_openclaw" || existing.status === "sync_failed";
|
|
12497
|
-
storage2.ingest(recordingId, metadata);
|
|
12498
|
-
if (shouldDownloadAndSync) {
|
|
12499
|
-
runRecordingSyncInBackground(metadata, recordingId, storage2, asrConfig, logger, options).catch((err2) => {
|
|
12500
|
-
const error = `录音同步失败: ${err2?.message ?? err2}`;
|
|
12501
|
-
logger.error(`[recording-sync] ${error}: ${recordingId}`);
|
|
12502
|
-
const current2 = storage2.findById(recordingId);
|
|
12503
|
-
if (current2?.status === "syncing_openclaw") {
|
|
12504
|
-
storage2.updateStatus(recordingId, "sync_failed");
|
|
12505
|
-
}
|
|
12506
|
-
storage2.setLastError(recordingId, error);
|
|
12507
|
-
emitRecordingStatus(recordingId, storage2, logger, options.notifyStatus, error);
|
|
12508
|
-
});
|
|
12509
|
-
} else {
|
|
12510
|
-
logger.info(`[recording-sync] 录音已存在且音频未变化,跳过重复下载: ${recordingId}`);
|
|
12511
|
-
emitRecordingStatus(recordingId, storage2, logger, options.notifyStatus);
|
|
12512
|
-
const current2 = storage2.findById(recordingId);
|
|
12513
|
-
if (current2?.status === "synced" && isAsrConfigured(asrConfig)) {
|
|
12514
|
-
triggerTranscription(recordingId, storage2, asrConfig, logger, options).catch((err2) => {
|
|
12515
|
-
logger.error(`[asr-trigger] 转写触发失败: ${recordingId}, ${err2?.message ?? err2}`);
|
|
12516
|
-
});
|
|
12517
|
-
}
|
|
12518
|
-
}
|
|
12519
|
-
const current = storage2.findById(recordingId);
|
|
12520
|
-
return {
|
|
12521
|
-
ok: true,
|
|
12522
|
-
recordingId,
|
|
12523
|
-
transfer_status: current?.status ?? "syncing_openclaw",
|
|
12524
|
-
...current?.lastError ? { error: current.lastError } : {}
|
|
12525
|
-
};
|
|
12526
|
-
}
|
|
12527
|
-
async function triggerTranscription(recordingId, storage2, asrConfig, logger, options = {}) {
|
|
12528
|
-
const entry = storage2.findById(recordingId);
|
|
12529
|
-
if (!entry) {
|
|
12530
|
-
logger.warn(`[asr-trigger] 录音不存在: ${recordingId}`);
|
|
12531
|
-
return;
|
|
12532
|
-
}
|
|
12533
|
-
if (!canStartTranscription(entry.status)) {
|
|
12534
|
-
logger.warn(`[asr-trigger] 当前状态不允许转写: ${recordingId} (status=${entry.status})`);
|
|
12535
|
-
return;
|
|
12536
|
-
}
|
|
12537
|
-
storage2.updateStatus(recordingId, "transcribing");
|
|
12538
|
-
storage2.setLastError(recordingId, undefined);
|
|
12539
|
-
emitRecordingStatus(recordingId, storage2, logger, options.notifyStatus);
|
|
12540
|
-
const audioFilePath = storage2.getAudioFilePath(recordingId);
|
|
12541
|
-
let result;
|
|
12542
|
-
try {
|
|
12543
|
-
result = await runTranscriptionWorkflow({
|
|
12544
|
-
audioFilePath,
|
|
12545
|
-
audioOssUrl: entry.metadata.oss_audio_url,
|
|
12546
|
-
config: asrConfig,
|
|
12547
|
-
markers: entry.metadata.markers ?? [],
|
|
12548
|
-
recordingName: entry.metadata.name,
|
|
12549
|
-
durationSec: entry.metadata.duration_sec,
|
|
12550
|
-
createdAt: entry.metadata.created_at,
|
|
12551
|
-
transcriptDataDir: storage2.getTranscriptDataDir(),
|
|
12552
|
-
transcriptsDir: storage2.getTranscriptsDir(),
|
|
12553
|
-
summariesDir: storage2.getSummariesDir(),
|
|
12554
|
-
recordingId,
|
|
12555
|
-
logger
|
|
12556
|
-
});
|
|
12557
|
-
} catch (err2) {
|
|
12558
|
-
const error = `转写任务异常: ${err2?.message ?? err2}`;
|
|
12559
|
-
logger.error(`[asr-trigger] ${error}: ${recordingId}`);
|
|
12560
|
-
result = { ok: false, error };
|
|
12561
|
-
}
|
|
12562
|
-
if (result.ok && result.transcriptFilename) {
|
|
12563
|
-
if (result.transcriptDataFilename) {
|
|
12564
|
-
storage2.setTranscriptDataFile(recordingId, result.transcriptDataFilename);
|
|
12565
|
-
}
|
|
12566
|
-
storage2.setTranscriptFile(recordingId, result.transcriptFilename);
|
|
12567
|
-
if (result.summaryFilename) {
|
|
12568
|
-
storage2.setSummaryFile(recordingId, result.summaryFilename);
|
|
12569
|
-
}
|
|
12570
|
-
storage2.setTitle(recordingId, result.title);
|
|
12571
|
-
storage2.updateStatus(recordingId, "transcribed");
|
|
12572
|
-
emitRecordingStatus(recordingId, storage2, logger, options.notifyStatus, undefined, {
|
|
12573
|
-
transcript: result.transcript ?? [],
|
|
12574
|
-
summary: result.summary,
|
|
12575
|
-
title: result.title ?? ""
|
|
12576
|
-
});
|
|
12577
|
-
logger.info(`[asr-trigger] 转写完成: ${recordingId}, summary="${result.summary}"`);
|
|
12578
|
-
} else {
|
|
12579
|
-
const error = result.error ?? "ASR 转写失败";
|
|
12580
|
-
storage2.updateStatus(recordingId, "transcribe_failed");
|
|
12581
|
-
storage2.setLastError(recordingId, error);
|
|
12582
|
-
emitRecordingStatus(recordingId, storage2, logger, options.notifyStatus, error);
|
|
12583
|
-
logger.error(`[asr-trigger] 转写失败: ${recordingId}, error=${error}`);
|
|
12584
|
-
}
|
|
12585
|
-
}
|
|
12586
|
-
// src/recording/result-writer.ts
|
|
12587
|
-
var import_node_fs29 = require("node:fs");
|
|
12588
|
-
var import_node_path25 = require("node:path");
|
|
12589
|
-
function handleRecordingResultWrite(params, storage2, logger, options = {}) {
|
|
12590
|
-
const recordingId = normalizeRequiredText(params.recordingId, "recordingId");
|
|
12591
|
-
if (!params.transcript && !params.summary) {
|
|
12592
|
-
throw new Error("transcript or summary is required");
|
|
12593
|
-
}
|
|
12594
|
-
let entry = storage2.findById(recordingId);
|
|
12595
|
-
if (!entry) {
|
|
12596
|
-
storage2.ingest(recordingId, buildPlaceholderMetadata(recordingId, params));
|
|
12597
|
-
entry = storage2.findById(recordingId);
|
|
12598
|
-
if (!entry) {
|
|
12599
|
-
throw new Error(`Recording not found: ${recordingId}`);
|
|
12600
|
-
}
|
|
12601
|
-
logger.info(`[recording-result] 录音不存在,已按结果写入新建: ${recordingId}`);
|
|
12602
|
-
}
|
|
12603
|
-
let transcript;
|
|
12604
|
-
let title = entry.title ?? entry.metadata.name ?? recordingId;
|
|
12605
|
-
let summaryText;
|
|
12606
|
-
if (params.transcript) {
|
|
12607
|
-
const written = writeTranscript(recordingId, entry.metadata, params.transcript, storage2, logger);
|
|
12608
|
-
transcript = written.transcript;
|
|
12609
|
-
title = written.title;
|
|
12610
|
-
}
|
|
12611
|
-
if (params.summary) {
|
|
12612
|
-
summaryText = writeSummary(recordingId, params.summary, storage2, logger);
|
|
12613
|
-
}
|
|
12614
|
-
const updated = storage2.markResultWritten(recordingId);
|
|
12615
|
-
emitResultStatus(recordingId, storage2, logger, options.notifyStatus, {
|
|
12616
|
-
transcript,
|
|
12617
|
-
summary: summaryText ?? storage2.readSummary(recordingId),
|
|
12618
|
-
title
|
|
12619
|
-
});
|
|
12620
|
-
const ossUrl = normalizeText(params.ossUrl);
|
|
12621
|
-
if (ossUrl) {
|
|
12622
|
-
downloadAudioInBackground(recordingId, ossUrl, storage2, logger, options);
|
|
12623
|
-
}
|
|
12624
|
-
return {
|
|
12625
|
-
ok: true,
|
|
12626
|
-
recordingId,
|
|
12627
|
-
transfer_status: updated.status,
|
|
12628
|
-
stored: {
|
|
12629
|
-
transcriptDataFile: updated.transcriptDataFile,
|
|
12630
|
-
transcriptFile: updated.transcriptFile,
|
|
12631
|
-
summaryFile: updated.summaryFile
|
|
12632
|
-
}
|
|
12633
|
-
};
|
|
12634
|
-
}
|
|
12635
|
-
function buildPlaceholderMetadata(recordingId, params) {
|
|
12636
|
-
return {
|
|
12637
|
-
name: normalizeText(params.transcript?.title) ?? recordingId,
|
|
12638
|
-
duration_sec: 0,
|
|
12639
|
-
file_size_bytes: 0,
|
|
12640
|
-
created_at: normalizeText(params.transcript?.generatedAt) ?? new Date().toISOString(),
|
|
12641
|
-
oss_audio_url: normalizeText(params.ossUrl) ?? "",
|
|
12642
|
-
markers: [],
|
|
12643
|
-
transfer_status: "synced"
|
|
12644
|
-
};
|
|
12645
|
-
}
|
|
12646
|
-
function writeTranscript(recordingId, recording, transcript, storage2, logger) {
|
|
12647
|
-
const title = normalizeText(transcript.title) ?? normalizeText(recording.name) ?? recordingId;
|
|
12648
|
-
const segments = normalizeSegments2(transcript.segments);
|
|
12649
|
-
const text = normalizePossiblyEmptyText2(transcript.text) ?? joinSegmentTexts(segments) ?? normalizePossiblyEmptyText2(transcript.markdown) ?? "";
|
|
12650
|
-
const transcriptDocument = buildTranscriptDocument({
|
|
12651
|
-
recordingId,
|
|
12652
|
-
generatedAt: normalizeText(transcript.generatedAt) ?? new Date().toISOString(),
|
|
12653
|
-
source: {
|
|
12654
|
-
provider: normalizeText(transcript.source?.provider) ?? "app",
|
|
12655
|
-
taskId: normalizeText(transcript.source?.taskId),
|
|
12656
|
-
requestId: normalizeText(transcript.source?.requestId),
|
|
12657
|
-
status: normalizeText(transcript.source?.status),
|
|
12658
|
-
delivery: "result-write"
|
|
12659
|
-
},
|
|
12660
|
-
title,
|
|
12661
|
-
category: normalizeText(transcript.category),
|
|
12662
|
-
summary: normalizePossiblyEmptyText2(transcript.brief),
|
|
12663
|
-
text,
|
|
12664
|
-
segments,
|
|
12665
|
-
raw: transcript
|
|
12666
|
-
});
|
|
12667
|
-
const transcriptDataFilename = storage2.buildTranscriptDataFilename(recordingId);
|
|
12668
|
-
import_node_fs29.writeFileSync(storage2.getTranscriptDataFilePath(recordingId), JSON.stringify(transcriptDocument, null, 2), "utf-8");
|
|
12669
|
-
storage2.setTranscriptDataFile(recordingId, transcriptDataFilename);
|
|
12670
|
-
const transcriptFilename = storage2.buildTranscriptFilename(recordingId, title);
|
|
12671
|
-
const markdown = normalizePossiblyEmptyText2(transcript.markdown) ?? buildTranscriptMarkdown2(recording, title, text, segments);
|
|
12672
|
-
import_node_fs29.writeFileSync(import_node_path25.join(storage2.getTranscriptsDir(), transcriptFilename), ensureTrailingNewline(markdown), "utf-8");
|
|
12673
|
-
storage2.setTranscriptFile(recordingId, transcriptFilename);
|
|
12674
|
-
storage2.setTitle(recordingId, title);
|
|
12675
|
-
logger.info(`[recording-result] 转录文本已写入: ${recordingId}`);
|
|
12676
|
-
return {
|
|
12677
|
-
title,
|
|
12678
|
-
transcript: extractSourceTextListFromDocument(transcriptDocument) ?? []
|
|
12679
|
-
};
|
|
12680
|
-
}
|
|
12681
|
-
function writeSummary(recordingId, summary, storage2, logger) {
|
|
12682
|
-
const markdown = normalizePossiblyEmptyText2(summary.markdown) ?? buildStructuredSummaryMarkdown(summary.structured);
|
|
12683
|
-
if (markdown === undefined) {
|
|
12684
|
-
return;
|
|
12685
|
-
}
|
|
12686
|
-
const summaryFilename = storage2.buildSummaryFilename(recordingId);
|
|
12687
|
-
import_node_fs29.writeFileSync(storage2.getSummaryFilePath(recordingId), ensureTrailingNewline(markdown), "utf-8");
|
|
12688
|
-
storage2.setSummaryFile(recordingId, summaryFilename);
|
|
12689
|
-
logger.info(`[recording-result] 总结已写入: ${recordingId}`);
|
|
12690
|
-
return markdown.trim();
|
|
12691
|
-
}
|
|
12692
|
-
function downloadAudioInBackground(recordingId, ossUrl, storage2, logger, options) {
|
|
12693
|
-
const audioDestPath = storage2.getAudioFilePath(recordingId, ossUrl);
|
|
12694
|
-
logger.info(`[recording-result] 开始下载音频: ${recordingId}, audio=${ossUrl}`);
|
|
12695
|
-
downloadFile(ossUrl, audioDestPath, logger).then((result) => {
|
|
12696
|
-
if (!result.ok) {
|
|
12697
|
-
const error = `音频下载失败: ${result.error}`;
|
|
12698
|
-
logger.error(`[recording-result] ${error}: ${recordingId}`);
|
|
12699
|
-
storage2.setLastError(recordingId, error);
|
|
12700
|
-
emitDownloadStatus(recordingId, storage2, logger, options.notifyStatus);
|
|
12701
|
-
return;
|
|
12702
|
-
}
|
|
12703
|
-
storage2.setAudioFile(recordingId, storage2.buildAudioFilename(recordingId, ossUrl));
|
|
12704
|
-
logger.info(`[recording-result] 音频已更新: ${recordingId}`);
|
|
12705
|
-
emitDownloadStatus(recordingId, storage2, logger, options.notifyStatus);
|
|
12706
|
-
}).catch((err2) => {
|
|
12707
|
-
const error = `音频下载异常: ${err2?.message ?? err2}`;
|
|
12708
|
-
logger.error(`[recording-result] ${error}: ${recordingId}`);
|
|
12709
|
-
storage2.setLastError(recordingId, error);
|
|
12710
|
-
emitDownloadStatus(recordingId, storage2, logger, options.notifyStatus);
|
|
12711
|
-
});
|
|
12712
|
-
}
|
|
12713
|
-
function emitDownloadStatus(recordingId, storage2, logger, notifyStatus) {
|
|
12714
|
-
const entry = storage2.findById(recordingId);
|
|
12715
|
-
if (!entry)
|
|
12716
|
-
return;
|
|
12717
|
-
emitResultStatus(recordingId, storage2, logger, notifyStatus, {
|
|
12718
|
-
summary: storage2.readSummary(recordingId),
|
|
12719
|
-
title: entry.title ?? entry.metadata.name ?? recordingId
|
|
12720
|
-
});
|
|
12721
|
-
}
|
|
12722
|
-
function emitResultStatus(recordingId, storage2, logger, notifyStatus, extras) {
|
|
12723
|
-
if (!notifyStatus)
|
|
12724
|
-
return;
|
|
12725
|
-
const entry = storage2.findById(recordingId);
|
|
12726
|
-
if (!entry)
|
|
12727
|
-
return;
|
|
12728
|
-
try {
|
|
12729
|
-
notifyStatus({
|
|
12730
|
-
recordingId: entry.id,
|
|
12731
|
-
transfer_status: entry.status,
|
|
12732
|
-
audioFile: entry.audioFile,
|
|
12733
|
-
srtFile: entry.srtFile,
|
|
12734
|
-
transcriptDataFile: entry.transcriptDataFile,
|
|
12735
|
-
transcriptFile: entry.transcriptFile,
|
|
12736
|
-
summaryFile: entry.summaryFile,
|
|
12737
|
-
transcript: extras.transcript,
|
|
12738
|
-
summary: extras.summary,
|
|
12739
|
-
title: extras.title,
|
|
12740
|
-
updatedAt: entry.updatedAt,
|
|
12741
|
-
error: entry.lastError
|
|
12742
|
-
});
|
|
12743
|
-
} catch (err2) {
|
|
12744
|
-
logger.error(`[recording-result] 状态事件发送失败: ${recordingId}, ${err2?.message ?? err2}`);
|
|
12745
|
-
}
|
|
12746
|
-
}
|
|
12747
|
-
function normalizeSegments2(segments) {
|
|
12748
|
-
if (!Array.isArray(segments))
|
|
12749
|
-
return [];
|
|
12750
|
-
return segments.flatMap((segment) => {
|
|
12751
|
-
const text = normalizeText(segment.text);
|
|
12752
|
-
if (!text)
|
|
12753
|
-
return [];
|
|
12754
|
-
return [{
|
|
12755
|
-
text,
|
|
12756
|
-
startMs: typeof segment.startMs === "number" ? segment.startMs : undefined,
|
|
12757
|
-
endMs: typeof segment.endMs === "number" ? segment.endMs : undefined,
|
|
12758
|
-
speakerId: Number.isInteger(segment.speakerId) ? segment.speakerId : undefined
|
|
12759
|
-
}];
|
|
12760
|
-
});
|
|
12761
|
-
}
|
|
12762
|
-
function joinSegmentTexts(segments) {
|
|
12763
|
-
const text = segments.map((segment) => segment.text).filter(Boolean).join(`
|
|
12764
|
-
|
|
12765
|
-
`).trim();
|
|
12766
|
-
return text || undefined;
|
|
12767
|
-
}
|
|
12768
|
-
function buildTranscriptMarkdown2(recording, title, text, segments) {
|
|
11943
|
+
function buildTranscriptMarkdown(recording, title, text, segments) {
|
|
12769
11944
|
const lines = [];
|
|
12770
11945
|
lines.push(`# ${title}`);
|
|
12771
11946
|
lines.push("");
|
|
12772
11947
|
lines.push(`> 录音名称:${recording.name}`);
|
|
12773
|
-
lines.push(`> 时长:${
|
|
11948
|
+
lines.push(`> 时长:${formatDuration(recording.duration_sec)}`);
|
|
12774
11949
|
lines.push(`> 创建时间:${recording.created_at}`);
|
|
12775
11950
|
if ((recording.markers ?? []).length > 0) {
|
|
12776
11951
|
lines.push(`> 关键点数:${recording.markers.length}`);
|
|
@@ -12780,7 +11955,7 @@ function buildTranscriptMarkdown2(recording, title, text, segments) {
|
|
|
12780
11955
|
lines.push("");
|
|
12781
11956
|
if (segments.length > 0) {
|
|
12782
11957
|
for (const segment of segments) {
|
|
12783
|
-
const prefix = segment.startMs !== undefined ? `[${
|
|
11958
|
+
const prefix = segment.startMs !== undefined ? `[${formatTimestamp(segment.startMs)}] ` : "";
|
|
12784
11959
|
lines.push(`${prefix}${segment.text}`.trim());
|
|
12785
11960
|
lines.push("");
|
|
12786
11961
|
}
|
|
@@ -12841,7 +12016,7 @@ function ensureTrailingNewline(value) {
|
|
|
12841
12016
|
`) ? value : `${value}
|
|
12842
12017
|
`;
|
|
12843
12018
|
}
|
|
12844
|
-
function
|
|
12019
|
+
function formatDuration(sec) {
|
|
12845
12020
|
const hours = Math.floor(sec / 3600);
|
|
12846
12021
|
const minutes = Math.floor(sec % 3600 / 60);
|
|
12847
12022
|
const seconds = Math.floor(sec % 60);
|
|
@@ -12850,15 +12025,15 @@ function formatDuration2(sec) {
|
|
|
12850
12025
|
}
|
|
12851
12026
|
return `${minutes}:${String(seconds).padStart(2, "0")}`;
|
|
12852
12027
|
}
|
|
12853
|
-
function
|
|
12028
|
+
function formatTimestamp(ms) {
|
|
12854
12029
|
const totalSec = Math.floor(ms / 1000);
|
|
12855
12030
|
const minutes = Math.floor(totalSec / 60);
|
|
12856
12031
|
const seconds = totalSec % 60;
|
|
12857
12032
|
return `${String(minutes).padStart(2, "0")}:${String(seconds).padStart(2, "0")}`;
|
|
12858
12033
|
}
|
|
12859
12034
|
// src/image/store.ts
|
|
12860
|
-
var
|
|
12861
|
-
var
|
|
12035
|
+
var import_node_fs29 = require("node:fs");
|
|
12036
|
+
var import_node_path25 = require("node:path");
|
|
12862
12037
|
var IMAGES_DIR = "images";
|
|
12863
12038
|
var FILES_DIR = "files";
|
|
12864
12039
|
var INDEX_FILE2 = "index.json";
|
|
@@ -12873,16 +12048,16 @@ function extFromMime(mime) {
|
|
|
12873
12048
|
return "jpg";
|
|
12874
12049
|
}
|
|
12875
12050
|
function resolveImageStorageDir(ctx, logger) {
|
|
12876
|
-
const stateImagesDir =
|
|
12051
|
+
const stateImagesDir = import_node_path25.join(ctx.stateDir, "plugins", "phone-notifications", IMAGES_DIR);
|
|
12877
12052
|
try {
|
|
12878
|
-
|
|
12053
|
+
import_node_fs29.mkdirSync(stateImagesDir, { recursive: true });
|
|
12879
12054
|
logger.info(`图片将写入 stateDir 路径: ${stateImagesDir}`);
|
|
12880
12055
|
return stateImagesDir;
|
|
12881
12056
|
} catch {}
|
|
12882
12057
|
if (ctx.workspaceDir) {
|
|
12883
|
-
const wsImagesDir =
|
|
12058
|
+
const wsImagesDir = import_node_path25.join(ctx.workspaceDir, IMAGES_DIR);
|
|
12884
12059
|
try {
|
|
12885
|
-
|
|
12060
|
+
import_node_fs29.mkdirSync(wsImagesDir, { recursive: true });
|
|
12886
12061
|
logger.warn(`stateDir 不可用,图片已回退到 workspace 路径: ${wsImagesDir}`);
|
|
12887
12062
|
return wsImagesDir;
|
|
12888
12063
|
} catch {}
|
|
@@ -12899,11 +12074,11 @@ class ImageStorage {
|
|
|
12899
12074
|
constructor(dir, logger) {
|
|
12900
12075
|
this.logger = logger;
|
|
12901
12076
|
this.dir = dir;
|
|
12902
|
-
this.filesDir =
|
|
12903
|
-
this.indexPath =
|
|
12077
|
+
this.filesDir = import_node_path25.join(dir, FILES_DIR);
|
|
12078
|
+
this.indexPath = import_node_path25.join(dir, INDEX_FILE2);
|
|
12904
12079
|
}
|
|
12905
12080
|
async init() {
|
|
12906
|
-
|
|
12081
|
+
import_node_fs29.mkdirSync(this.filesDir, { recursive: true });
|
|
12907
12082
|
this.loadIndex();
|
|
12908
12083
|
this.logger.info(`图片存储已初始化: ${this.dir}(共 ${this.index.images.length} 条记录)`);
|
|
12909
12084
|
}
|
|
@@ -12921,7 +12096,7 @@ class ImageStorage {
|
|
|
12921
12096
|
return existing;
|
|
12922
12097
|
}
|
|
12923
12098
|
if (existing.localFile) {
|
|
12924
|
-
|
|
12099
|
+
import_node_fs29.rmSync(import_node_path25.join(this.dir, existing.localFile), { force: true });
|
|
12925
12100
|
}
|
|
12926
12101
|
existing.metadata = metadata;
|
|
12927
12102
|
existing.clientLabel = clientLabel;
|
|
@@ -12970,10 +12145,10 @@ class ImageStorage {
|
|
|
12970
12145
|
return `${FILES_DIR}/${this.buildFilename(imageId, mime)}`;
|
|
12971
12146
|
}
|
|
12972
12147
|
getFilePath(imageId, mime) {
|
|
12973
|
-
return
|
|
12148
|
+
return import_node_path25.join(this.filesDir, this.buildFilename(imageId, mime));
|
|
12974
12149
|
}
|
|
12975
12150
|
resolveFile(relative) {
|
|
12976
|
-
return
|
|
12151
|
+
return import_node_path25.isAbsolute(relative) ? relative : import_node_path25.join(this.dir, relative);
|
|
12977
12152
|
}
|
|
12978
12153
|
findById(id) {
|
|
12979
12154
|
return this.index.images.find((e) => e.imageId === id);
|
|
@@ -12982,12 +12157,12 @@ class ImageStorage {
|
|
|
12982
12157
|
return [...this.index.images].sort((a, b) => b.metadata.created_at.localeCompare(a.metadata.created_at));
|
|
12983
12158
|
}
|
|
12984
12159
|
loadIndex() {
|
|
12985
|
-
if (!
|
|
12160
|
+
if (!import_node_fs29.existsSync(this.indexPath)) {
|
|
12986
12161
|
this.index = { images: [] };
|
|
12987
12162
|
return;
|
|
12988
12163
|
}
|
|
12989
12164
|
try {
|
|
12990
|
-
const raw = JSON.parse(
|
|
12165
|
+
const raw = JSON.parse(import_node_fs29.readFileSync(this.indexPath, "utf-8"));
|
|
12991
12166
|
this.index = raw && Array.isArray(raw.images) ? { images: raw.images } : { images: [] };
|
|
12992
12167
|
} catch {
|
|
12993
12168
|
this.logger.warn(`图片索引文件损坏,已重建: ${this.indexPath}`);
|
|
@@ -12995,13 +12170,13 @@ class ImageStorage {
|
|
|
12995
12170
|
}
|
|
12996
12171
|
}
|
|
12997
12172
|
saveIndex() {
|
|
12998
|
-
|
|
12173
|
+
import_node_fs29.writeFileSync(this.indexPath, JSON.stringify(this.index, null, 2), "utf-8");
|
|
12999
12174
|
}
|
|
13000
12175
|
async close() {}
|
|
13001
12176
|
}
|
|
13002
12177
|
// src/image/handler.ts
|
|
13003
|
-
var
|
|
13004
|
-
var
|
|
12178
|
+
var import_node_fs30 = require("node:fs");
|
|
12179
|
+
var import_node_path26 = require("node:path");
|
|
13005
12180
|
var DEFAULT_IMAGE_MAX_BYTES = 20 * 1024 * 1024;
|
|
13006
12181
|
var DOWNLOAD_TIMEOUT_MS = 5 * 60 * 1000;
|
|
13007
12182
|
function handleImageSync(imageId, metadata, storage2, maxBytes, logger) {
|
|
@@ -13034,13 +12209,13 @@ async function runImageDownloadInBackground(imageId, metadata, storage2, maxByte
|
|
|
13034
12209
|
logger.info(`[image-sync] 图片已同步: ${imageId} (${result.sizeBytes} bytes)`);
|
|
13035
12210
|
}
|
|
13036
12211
|
async function downloadImage(url, destPath, maxBytes, logger) {
|
|
13037
|
-
|
|
12212
|
+
import_node_fs30.mkdirSync(import_node_path26.dirname(destPath), { recursive: true });
|
|
13038
12213
|
const controller = new AbortController;
|
|
13039
12214
|
const timer = setTimeout(() => controller.abort(), DOWNLOAD_TIMEOUT_MS);
|
|
13040
12215
|
const cleanup = () => {
|
|
13041
12216
|
try {
|
|
13042
|
-
if (
|
|
13043
|
-
|
|
12217
|
+
if (import_node_fs30.existsSync(destPath))
|
|
12218
|
+
import_node_fs30.unlinkSync(destPath);
|
|
13044
12219
|
} catch {}
|
|
13045
12220
|
};
|
|
13046
12221
|
try {
|
|
@@ -13055,7 +12230,7 @@ async function downloadImage(url, destPath, maxBytes, logger) {
|
|
|
13055
12230
|
if (contentLength > 0 && contentLength > maxBytes) {
|
|
13056
12231
|
return { ok: false, error: `图片超过上限 ${maxBytes} 字节` };
|
|
13057
12232
|
}
|
|
13058
|
-
const writeStream =
|
|
12233
|
+
const writeStream = import_node_fs30.createWriteStream(destPath);
|
|
13059
12234
|
let total = 0;
|
|
13060
12235
|
try {
|
|
13061
12236
|
for await (const chunk of res.body) {
|
|
@@ -13089,12 +12264,12 @@ async function downloadImage(url, destPath, maxBytes, logger) {
|
|
|
13089
12264
|
}
|
|
13090
12265
|
}
|
|
13091
12266
|
// src/tunnel/service.ts
|
|
13092
|
-
var
|
|
13093
|
-
var
|
|
12267
|
+
var import_node_fs32 = require("node:fs");
|
|
12268
|
+
var import_node_path29 = require("node:path");
|
|
13094
12269
|
|
|
13095
12270
|
// src/tunnel/relay-client.ts
|
|
13096
|
-
var
|
|
13097
|
-
var
|
|
12271
|
+
var import_node_fs31 = require("node:fs");
|
|
12272
|
+
var import_node_path27 = require("node:path");
|
|
13098
12273
|
|
|
13099
12274
|
// node_modules/ws/wrapper.mjs
|
|
13100
12275
|
var import_stream = __toESM(require_stream(), 1);
|
|
@@ -13182,6 +12357,7 @@ function maskableSecret(credential) {
|
|
|
13182
12357
|
var HANDSHAKE_TIMEOUT_MS = 15000;
|
|
13183
12358
|
var CONNECT_WATCHDOG_MS = 20000;
|
|
13184
12359
|
var SEND_SKIPPED_LOG_INTERVAL_MS = 30000;
|
|
12360
|
+
var WS_TRY_AGAIN_LATER_CODE = 1013;
|
|
13185
12361
|
|
|
13186
12362
|
class RelayClient {
|
|
13187
12363
|
opts;
|
|
@@ -13210,8 +12386,8 @@ class RelayClient {
|
|
|
13210
12386
|
lastDisconnectReason
|
|
13211
12387
|
};
|
|
13212
12388
|
try {
|
|
13213
|
-
|
|
13214
|
-
|
|
12389
|
+
import_node_fs31.mkdirSync(import_node_path27.dirname(this.opts.statusFilePath), { recursive: true });
|
|
12390
|
+
import_node_fs31.writeFileSync(this.opts.statusFilePath, JSON.stringify(info, null, 2));
|
|
13215
12391
|
} catch {}
|
|
13216
12392
|
}
|
|
13217
12393
|
onInbound(handler) {
|
|
@@ -13434,7 +12610,9 @@ class RelayClient {
|
|
|
13434
12610
|
const disconnectReason = `code=${code}, reason=${reasonStr}`;
|
|
13435
12611
|
this.writeStatus("disconnected", disconnectReason);
|
|
13436
12612
|
this.emitDisconnected(disconnectReason);
|
|
13437
|
-
this.scheduleReconnect(
|
|
12613
|
+
this.scheduleReconnect({
|
|
12614
|
+
immediate: code === WS_TRY_AGAIN_LATER_CODE
|
|
12615
|
+
});
|
|
13438
12616
|
}
|
|
13439
12617
|
settle();
|
|
13440
12618
|
});
|
|
@@ -13585,16 +12763,16 @@ class RelayClient {
|
|
|
13585
12763
|
this.skippedSendLogLastAt = now;
|
|
13586
12764
|
this.skippedSendLogSuppressed = 0;
|
|
13587
12765
|
}
|
|
13588
|
-
scheduleReconnect() {
|
|
12766
|
+
scheduleReconnect(opts = {}) {
|
|
13589
12767
|
if (this.aborted)
|
|
13590
12768
|
return;
|
|
13591
12769
|
if (this.reconnectTimer) {
|
|
13592
12770
|
return;
|
|
13593
12771
|
}
|
|
13594
12772
|
const baseMs = this.opts.reconnectBackoffMs;
|
|
13595
|
-
const delayMs = Math.min(baseMs * Math.pow(2, this.reconnectAttempt), 60000);
|
|
12773
|
+
const delayMs = opts.immediate ? 0 : Math.min(baseMs * Math.pow(2, this.reconnectAttempt), 60000);
|
|
13596
12774
|
this.reconnectAttempt++;
|
|
13597
|
-
this.opts.logger.info(`Relay tunnel: reconnecting in ${delayMs}ms (attempt ${this.reconnectAttempt})`);
|
|
12775
|
+
this.opts.logger.info(`Relay tunnel: reconnecting in ${delayMs}ms (attempt ${this.reconnectAttempt}${opts.immediate ? ", immediate" : ""})`);
|
|
13598
12776
|
this.reconnectTimer = setTimeout(() => {
|
|
13599
12777
|
this.reconnectTimer = null;
|
|
13600
12778
|
if (!this.aborted) {
|
|
@@ -13611,13 +12789,13 @@ var import_node_crypto5 = require("node:crypto");
|
|
|
13611
12789
|
|
|
13612
12790
|
// src/host-sdk.ts
|
|
13613
12791
|
var import_node_module = require("node:module");
|
|
13614
|
-
var
|
|
12792
|
+
var import_node_path28 = require("node:path");
|
|
13615
12793
|
var import_node_url = require("node:url");
|
|
13616
12794
|
function resolveHostSdkModuleFromHostEntry(specifier, hostEntry = process.argv[1]) {
|
|
13617
12795
|
if (!hostEntry?.trim())
|
|
13618
12796
|
return "";
|
|
13619
12797
|
try {
|
|
13620
|
-
return import_node_module.createRequire(
|
|
12798
|
+
return import_node_module.createRequire(import_node_path28.resolve(hostEntry)).resolve(specifier);
|
|
13621
12799
|
} catch {
|
|
13622
12800
|
return "";
|
|
13623
12801
|
}
|
|
@@ -14137,11 +13315,40 @@ class WsProxy {
|
|
|
14137
13315
|
}
|
|
14138
13316
|
|
|
14139
13317
|
// src/tunnel/frame-slimmer.ts
|
|
14140
|
-
var SLIM_TRIGGER_CHARS =
|
|
13318
|
+
var SLIM_TRIGGER_CHARS = 24 * 1024;
|
|
14141
13319
|
var AGENT_OUTPUT_CAP = 16 * 1024;
|
|
14142
13320
|
var SESSION_LIST_PREVIEW_CAP = 256;
|
|
14143
|
-
var SESSION_LIST_MESSAGE_CAP =
|
|
13321
|
+
var SESSION_LIST_MESSAGE_CAP = 256;
|
|
13322
|
+
var SESSION_LIST_ITEM_CAP = 50;
|
|
13323
|
+
var SESSION_LIST_SCALAR_CAP = 512;
|
|
13324
|
+
var SESSION_LIST_SMALL_VALUE_CAP = 1024;
|
|
13325
|
+
var CRON_RUN_TEXT_CAP = 1024;
|
|
13326
|
+
var CONFIG_TEXT_CAP = 1024;
|
|
13327
|
+
var CONFIG_ARRAY_CAP = 50;
|
|
14144
13328
|
var COMMAND_SUMMARY_CAP = 4 * 1024;
|
|
13329
|
+
var CONFIG_RAW_PLACEHOLDER = "[omitted by relay tunnel from large config.get response]";
|
|
13330
|
+
function isRecord2(value) {
|
|
13331
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
13332
|
+
}
|
|
13333
|
+
function isJsonScalar(value) {
|
|
13334
|
+
return value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean";
|
|
13335
|
+
}
|
|
13336
|
+
function jsonLength(value) {
|
|
13337
|
+
try {
|
|
13338
|
+
return JSON.stringify(value).length;
|
|
13339
|
+
} catch {
|
|
13340
|
+
return Number.POSITIVE_INFINITY;
|
|
13341
|
+
}
|
|
13342
|
+
}
|
|
13343
|
+
function slimScalar(value, cap, preview = false) {
|
|
13344
|
+
if (typeof value !== "string" || value.length <= cap) {
|
|
13345
|
+
return { value, changed: false };
|
|
13346
|
+
}
|
|
13347
|
+
return {
|
|
13348
|
+
value: preview ? truncatePreviewText(value, cap) : truncateText(value, cap),
|
|
13349
|
+
changed: true
|
|
13350
|
+
};
|
|
13351
|
+
}
|
|
14145
13352
|
function safeSliceEnd(text, end) {
|
|
14146
13353
|
const code = text.charCodeAt(end - 1);
|
|
14147
13354
|
return code >= 55296 && code <= 56319 ? end - 1 : end;
|
|
@@ -14200,41 +13407,89 @@ function slimToolResultContentItem(item) {
|
|
|
14200
13407
|
}
|
|
14201
13408
|
return changed;
|
|
14202
13409
|
}
|
|
14203
|
-
function
|
|
14204
|
-
if (
|
|
14205
|
-
return
|
|
14206
|
-
if (typeof target[prop] !== "string" || target[prop].length <= cap) {
|
|
14207
|
-
return false;
|
|
13410
|
+
function slimSessionMessageValue(value, cap) {
|
|
13411
|
+
if (isJsonScalar(value)) {
|
|
13412
|
+
return slimScalar(value, cap);
|
|
14208
13413
|
}
|
|
14209
|
-
|
|
14210
|
-
|
|
14211
|
-
|
|
14212
|
-
|
|
14213
|
-
|
|
14214
|
-
|
|
14215
|
-
|
|
14216
|
-
|
|
14217
|
-
|
|
14218
|
-
|
|
14219
|
-
|
|
14220
|
-
|
|
14221
|
-
|
|
14222
|
-
|
|
14223
|
-
|
|
14224
|
-
|
|
14225
|
-
|
|
13414
|
+
if (Array.isArray(value)) {
|
|
13415
|
+
const keepCount = Math.min(value.length, 6);
|
|
13416
|
+
const items = [];
|
|
13417
|
+
let changed2 = value.length > keepCount;
|
|
13418
|
+
for (let i = 0;i < keepCount; i++) {
|
|
13419
|
+
const item = slimSessionMessageValue(value[i], cap);
|
|
13420
|
+
items.push(item.value);
|
|
13421
|
+
if (item.changed)
|
|
13422
|
+
changed2 = true;
|
|
13423
|
+
}
|
|
13424
|
+
return { value: items, changed: changed2 };
|
|
13425
|
+
}
|
|
13426
|
+
if (!isRecord2(value))
|
|
13427
|
+
return { value, changed: false };
|
|
13428
|
+
const slimmed = {};
|
|
13429
|
+
let changed = false;
|
|
13430
|
+
for (const [key, child] of Object.entries(value)) {
|
|
13431
|
+
if (key === "content" || key === "text" || key === "summary") {
|
|
13432
|
+
const item = slimSessionMessageValue(child, cap);
|
|
13433
|
+
slimmed[key] = item.value;
|
|
13434
|
+
if (item.changed)
|
|
13435
|
+
changed = true;
|
|
13436
|
+
continue;
|
|
14226
13437
|
}
|
|
14227
|
-
|
|
13438
|
+
if (isJsonScalar(child)) {
|
|
13439
|
+
const item = slimScalar(child, SESSION_LIST_SCALAR_CAP, true);
|
|
13440
|
+
slimmed[key] = item.value;
|
|
13441
|
+
if (item.changed)
|
|
13442
|
+
changed = true;
|
|
13443
|
+
continue;
|
|
13444
|
+
}
|
|
13445
|
+
if (jsonLength(child) <= SESSION_LIST_SMALL_VALUE_CAP) {
|
|
13446
|
+
slimmed[key] = child;
|
|
13447
|
+
continue;
|
|
13448
|
+
}
|
|
13449
|
+
changed = true;
|
|
14228
13450
|
}
|
|
14229
|
-
if (
|
|
14230
|
-
|
|
14231
|
-
|
|
14232
|
-
|
|
13451
|
+
if (Object.keys(slimmed).length !== Object.keys(value).length) {
|
|
13452
|
+
changed = true;
|
|
13453
|
+
}
|
|
13454
|
+
return { value: slimmed, changed };
|
|
13455
|
+
}
|
|
13456
|
+
function slimSessionListItem(session) {
|
|
13457
|
+
if (!isRecord2(session))
|
|
13458
|
+
return { value: session, changed: false };
|
|
13459
|
+
const slimmed = {};
|
|
13460
|
+
let changed = false;
|
|
13461
|
+
for (const [key, value] of Object.entries(session)) {
|
|
13462
|
+
if (key === "lastMessagePreview") {
|
|
13463
|
+
const item = slimScalar(value, SESSION_LIST_PREVIEW_CAP, true);
|
|
13464
|
+
slimmed[key] = item.value;
|
|
13465
|
+
if (item.changed)
|
|
14233
13466
|
changed = true;
|
|
13467
|
+
continue;
|
|
14234
13468
|
}
|
|
14235
|
-
|
|
13469
|
+
if (key === "lastMessage") {
|
|
13470
|
+
const item = slimSessionMessageValue(value, SESSION_LIST_MESSAGE_CAP);
|
|
13471
|
+
slimmed[key] = item.value;
|
|
13472
|
+
if (item.changed)
|
|
13473
|
+
changed = true;
|
|
13474
|
+
continue;
|
|
13475
|
+
}
|
|
13476
|
+
if (isJsonScalar(value)) {
|
|
13477
|
+
const item = slimScalar(value, SESSION_LIST_SCALAR_CAP, true);
|
|
13478
|
+
slimmed[key] = item.value;
|
|
13479
|
+
if (item.changed)
|
|
13480
|
+
changed = true;
|
|
13481
|
+
continue;
|
|
13482
|
+
}
|
|
13483
|
+
if (jsonLength(value) <= SESSION_LIST_SMALL_VALUE_CAP) {
|
|
13484
|
+
slimmed[key] = value;
|
|
13485
|
+
continue;
|
|
13486
|
+
}
|
|
13487
|
+
changed = true;
|
|
14236
13488
|
}
|
|
14237
|
-
|
|
13489
|
+
if (Object.keys(slimmed).length !== Object.keys(session).length) {
|
|
13490
|
+
changed = true;
|
|
13491
|
+
}
|
|
13492
|
+
return changed ? { value: slimmed, changed } : { value: session, changed };
|
|
14238
13493
|
}
|
|
14239
13494
|
function slimMessagesPayload(payload) {
|
|
14240
13495
|
if (!Array.isArray(payload?.messages))
|
|
@@ -14269,18 +13524,93 @@ function slimSessionsListPayload(payload) {
|
|
|
14269
13524
|
if (!Array.isArray(payload?.sessions))
|
|
14270
13525
|
return false;
|
|
14271
13526
|
let changed = false;
|
|
14272
|
-
|
|
13527
|
+
if (payload.sessions.length > SESSION_LIST_ITEM_CAP) {
|
|
13528
|
+
payload.sessions = payload.sessions.slice(0, SESSION_LIST_ITEM_CAP);
|
|
13529
|
+
changed = true;
|
|
13530
|
+
}
|
|
13531
|
+
for (let i = 0;i < payload.sessions.length; i++) {
|
|
13532
|
+
const session = payload.sessions[i];
|
|
14273
13533
|
if (!session || typeof session !== "object")
|
|
14274
13534
|
continue;
|
|
14275
|
-
|
|
13535
|
+
const item = slimSessionListItem(session);
|
|
13536
|
+
if (item.changed) {
|
|
13537
|
+
payload.sessions[i] = item.value;
|
|
13538
|
+
changed = true;
|
|
13539
|
+
}
|
|
13540
|
+
}
|
|
13541
|
+
return changed;
|
|
13542
|
+
}
|
|
13543
|
+
function slimNestedLargeValues(value, options, depth = 0) {
|
|
13544
|
+
if (!value || typeof value !== "object" || depth > options.maxDepth) {
|
|
13545
|
+
return false;
|
|
13546
|
+
}
|
|
13547
|
+
let changed = false;
|
|
13548
|
+
if (Array.isArray(value)) {
|
|
13549
|
+
if (typeof options.arrayCap === "number" && value.length > options.arrayCap) {
|
|
13550
|
+
value.splice(options.arrayCap);
|
|
13551
|
+
changed = true;
|
|
13552
|
+
}
|
|
13553
|
+
for (const item of value) {
|
|
13554
|
+
if (slimNestedLargeValues(item, options, depth + 1))
|
|
13555
|
+
changed = true;
|
|
13556
|
+
}
|
|
13557
|
+
return changed;
|
|
13558
|
+
}
|
|
13559
|
+
for (const [key, child] of Object.entries(value)) {
|
|
13560
|
+
if (key === "raw" && options.rawPlaceholder) {
|
|
13561
|
+
if (value[key] !== options.rawPlaceholder) {
|
|
13562
|
+
value[key] = options.rawPlaceholder;
|
|
13563
|
+
changed = true;
|
|
13564
|
+
}
|
|
13565
|
+
continue;
|
|
13566
|
+
}
|
|
13567
|
+
if (typeof child === "string" && child.length > options.textCap) {
|
|
13568
|
+
value[key] = truncateText(child, options.textCap);
|
|
14276
13569
|
changed = true;
|
|
13570
|
+
continue;
|
|
13571
|
+
}
|
|
13572
|
+
if (child && typeof child === "object") {
|
|
13573
|
+
if (slimNestedLargeValues(child, options, depth + 1))
|
|
13574
|
+
changed = true;
|
|
13575
|
+
}
|
|
13576
|
+
}
|
|
13577
|
+
return changed;
|
|
13578
|
+
}
|
|
13579
|
+
function slimCronRunsPayload(payload) {
|
|
13580
|
+
if (!Array.isArray(payload?.entries))
|
|
13581
|
+
return false;
|
|
13582
|
+
let hasCronRun = false;
|
|
13583
|
+
for (const entry of payload.entries) {
|
|
13584
|
+
if (isRecord2(entry) && (("jobId" in entry) || ("action" in entry) && ("status" in entry))) {
|
|
13585
|
+
hasCronRun = true;
|
|
13586
|
+
break;
|
|
14277
13587
|
}
|
|
14278
|
-
|
|
13588
|
+
}
|
|
13589
|
+
if (!hasCronRun)
|
|
13590
|
+
return false;
|
|
13591
|
+
let changed = false;
|
|
13592
|
+
for (const entry of payload.entries) {
|
|
13593
|
+
if (slimNestedLargeValues(entry, {
|
|
13594
|
+
textCap: CRON_RUN_TEXT_CAP,
|
|
13595
|
+
arrayCap: 20,
|
|
13596
|
+
maxDepth: 6
|
|
13597
|
+
})) {
|
|
14279
13598
|
changed = true;
|
|
14280
13599
|
}
|
|
14281
13600
|
}
|
|
14282
13601
|
return changed;
|
|
14283
13602
|
}
|
|
13603
|
+
function slimConfigGetPayload(payload) {
|
|
13604
|
+
if (!isRecord2(payload) || !("parsed" in payload) || !("exists" in payload)) {
|
|
13605
|
+
return false;
|
|
13606
|
+
}
|
|
13607
|
+
return slimNestedLargeValues(payload, {
|
|
13608
|
+
textCap: CONFIG_TEXT_CAP,
|
|
13609
|
+
arrayCap: CONFIG_ARRAY_CAP,
|
|
13610
|
+
maxDepth: 8,
|
|
13611
|
+
rawPlaceholder: CONFIG_RAW_PLACEHOLDER
|
|
13612
|
+
});
|
|
13613
|
+
}
|
|
14284
13614
|
function slimRelayFrame(frame) {
|
|
14285
13615
|
if (frame?.type === "event" && frame.event === "agent") {
|
|
14286
13616
|
return slimAgentEventPayload(frame.payload);
|
|
@@ -14288,7 +13618,9 @@ function slimRelayFrame(frame) {
|
|
|
14288
13618
|
if (frame?.type === "res" && frame.ok === true) {
|
|
14289
13619
|
const sessionsChanged = slimSessionsListPayload(frame.payload);
|
|
14290
13620
|
const messagesChanged = slimMessagesPayload(frame.payload);
|
|
14291
|
-
|
|
13621
|
+
const cronChanged = slimCronRunsPayload(frame.payload);
|
|
13622
|
+
const configChanged = slimConfigGetPayload(frame.payload);
|
|
13623
|
+
return sessionsChanged || messagesChanged || cronChanged || configChanged;
|
|
14292
13624
|
}
|
|
14293
13625
|
return false;
|
|
14294
13626
|
}
|
|
@@ -15073,7 +14405,7 @@ function createTunnelService(opts) {
|
|
|
15073
14405
|
}
|
|
15074
14406
|
function readLockInfo(filePath) {
|
|
15075
14407
|
try {
|
|
15076
|
-
const parsed = JSON.parse(
|
|
14408
|
+
const parsed = JSON.parse(import_node_fs32.readFileSync(filePath, "utf-8"));
|
|
15077
14409
|
const pid = typeof parsed.pid === "number" ? parsed.pid : null;
|
|
15078
14410
|
const updatedAtMs = typeof parsed.updatedAt === "string" ? Date.parse(parsed.updatedAt) : Number.NaN;
|
|
15079
14411
|
return {
|
|
@@ -15106,7 +14438,7 @@ function createTunnelService(opts) {
|
|
|
15106
14438
|
if (!filePath)
|
|
15107
14439
|
return;
|
|
15108
14440
|
try {
|
|
15109
|
-
|
|
14441
|
+
import_node_fs32.writeFileSync(filePath, buildLockPayload(), { mode: 384 });
|
|
15110
14442
|
} catch {}
|
|
15111
14443
|
}, intervalMs);
|
|
15112
14444
|
lockRefreshTimer.unref?.();
|
|
@@ -15120,25 +14452,25 @@ function createTunnelService(opts) {
|
|
|
15120
14452
|
lockStartedAt = null;
|
|
15121
14453
|
if (fd !== null) {
|
|
15122
14454
|
try {
|
|
15123
|
-
|
|
14455
|
+
import_node_fs32.closeSync(fd);
|
|
15124
14456
|
} catch {}
|
|
15125
14457
|
}
|
|
15126
14458
|
if (filePath) {
|
|
15127
14459
|
try {
|
|
15128
|
-
|
|
14460
|
+
import_node_fs32.unlinkSync(filePath);
|
|
15129
14461
|
} catch {}
|
|
15130
14462
|
}
|
|
15131
14463
|
}
|
|
15132
14464
|
function acquireLock(filePath) {
|
|
15133
|
-
|
|
14465
|
+
import_node_fs32.mkdirSync(import_node_path29.dirname(filePath), { recursive: true });
|
|
15134
14466
|
const heartbeatSec = opts.heartbeatSec ?? DEFAULT_HEARTBEAT_SEC;
|
|
15135
14467
|
const refreshMs = heartbeatSec * 1000 * LOCK_REFRESH_HEARTBEAT_MULTIPLIER;
|
|
15136
14468
|
const staleMs = heartbeatSec * 1000 * LOCK_STALE_HEARTBEAT_MULTIPLIER;
|
|
15137
14469
|
for (let attempt = 0;attempt < 2; attempt++) {
|
|
15138
14470
|
try {
|
|
15139
14471
|
lockStartedAt = new Date().toISOString();
|
|
15140
|
-
const fd =
|
|
15141
|
-
|
|
14472
|
+
const fd = import_node_fs32.openSync(filePath, "wx", 384);
|
|
14473
|
+
import_node_fs32.writeFileSync(fd, buildLockPayload());
|
|
15142
14474
|
lockFilePath = filePath;
|
|
15143
14475
|
lockFd = fd;
|
|
15144
14476
|
startLockRefresh(refreshMs);
|
|
@@ -15156,7 +14488,7 @@ function createTunnelService(opts) {
|
|
|
15156
14488
|
const staleReason = describeStaleLock(ownerPid, updatedAt, staleMs);
|
|
15157
14489
|
opts.logger.warn(`Relay tunnel: removing stale local lock (${staleReason})`);
|
|
15158
14490
|
try {
|
|
15159
|
-
|
|
14491
|
+
import_node_fs32.unlinkSync(filePath);
|
|
15160
14492
|
} catch {}
|
|
15161
14493
|
continue;
|
|
15162
14494
|
}
|
|
@@ -15261,10 +14593,10 @@ function createTunnelService(opts) {
|
|
|
15261
14593
|
const { logger } = opts;
|
|
15262
14594
|
const hostStateDir = resolveHostStateDir(ctx.stateDir);
|
|
15263
14595
|
const stateDir = resolveStateDir(hostStateDir);
|
|
15264
|
-
const baseStateDir =
|
|
14596
|
+
const baseStateDir = import_node_path29.join(stateDir, "plugins", "phone-notifications");
|
|
15265
14597
|
logger.info(`Relay tunnel: starting (pid=${process.pid}, url=${redactUrlSecrets(opts.tunnelUrl)}, heartbeat=${opts.heartbeatSec ?? DEFAULT_HEARTBEAT_SEC}s, backoff=${opts.reconnectBackoffMs ?? DEFAULT_RECONNECT_BACKOFF_MS}ms, gateway=${opts.gatewayBaseUrl}, hasGatewayToken=${!!opts.gatewayToken}, hasGatewayPwd=${!!opts.gatewayPassword})`);
|
|
15266
|
-
const statusFilePath =
|
|
15267
|
-
const lockPath =
|
|
14598
|
+
const statusFilePath = import_node_path29.join(baseStateDir, "tunnel-status.json");
|
|
14599
|
+
const lockPath = import_node_path29.join(baseStateDir, "relay-tunnel.lock");
|
|
15268
14600
|
if (!acquireLock(lockPath)) {
|
|
15269
14601
|
return;
|
|
15270
14602
|
}
|
|
@@ -15405,8 +14737,8 @@ class ArkclawAuthProvider extends BaseApiKeyAuthProvider {
|
|
|
15405
14737
|
// src/profile/auth/jvsclaw.ts
|
|
15406
14738
|
var import_node_crypto6 = require("node:crypto");
|
|
15407
14739
|
var import_node_os3 = __toESM(require("node:os"));
|
|
15408
|
-
var
|
|
15409
|
-
var
|
|
14740
|
+
var import_node_fs33 = require("node:fs");
|
|
14741
|
+
var import_node_path30 = require("node:path");
|
|
15410
14742
|
var CLAW_CODE = "JvsClaw";
|
|
15411
14743
|
var RETRY_BASE_MS = 2000;
|
|
15412
14744
|
var RETRY_MAX_MS = 60000;
|
|
@@ -15417,7 +14749,7 @@ var FATAL_READY_CODES = new Set([
|
|
|
15417
14749
|
"54020",
|
|
15418
14750
|
"54024"
|
|
15419
14751
|
]);
|
|
15420
|
-
var JVSCLAW_LINK_SECRET_CONFIG_PATH =
|
|
14752
|
+
var JVSCLAW_LINK_SECRET_CONFIG_PATH = import_node_path30.join(DEFAULT_JVSCLAW_STATE_DIR, "openclaw.json");
|
|
15421
14753
|
var PLUGIN_ID3 = "phone-notifications";
|
|
15422
14754
|
var LINK_SECRET_WATCH_DEBOUNCE_MS = 250;
|
|
15423
14755
|
|
|
@@ -15472,7 +14804,7 @@ class JvsclawAuthProvider extends BaseApiKeyAuthProvider {
|
|
|
15472
14804
|
}
|
|
15473
14805
|
const delayMs = Math.min(RETRY_BASE_MS * 2 ** (attempt - 1), RETRY_MAX_MS);
|
|
15474
14806
|
logger.warn(`jvsclaw auth: 置换失败(attempt ${attempt}): ${err2?.message ?? String(err2)},${delayMs}ms 后重试`);
|
|
15475
|
-
const aborted = await
|
|
14807
|
+
const aborted = await sleep2(delayMs, abortSignal);
|
|
15476
14808
|
if (aborted)
|
|
15477
14809
|
break;
|
|
15478
14810
|
}
|
|
@@ -15529,8 +14861,8 @@ class JvsclawAuthProvider extends BaseApiKeyAuthProvider {
|
|
|
15529
14861
|
watch(onChange) {
|
|
15530
14862
|
const unwatchCredentials = super.watch(onChange);
|
|
15531
14863
|
const configPath = resolveConfigPath();
|
|
15532
|
-
const configDir =
|
|
15533
|
-
const configFile =
|
|
14864
|
+
const configDir = import_node_path30.dirname(configPath);
|
|
14865
|
+
const configFile = import_node_path30.basename(configPath);
|
|
15534
14866
|
let debounceTimer = null;
|
|
15535
14867
|
let configWatcher = null;
|
|
15536
14868
|
const emitChange = () => {
|
|
@@ -15543,7 +14875,7 @@ class JvsclawAuthProvider extends BaseApiKeyAuthProvider {
|
|
|
15543
14875
|
debounceTimer.unref?.();
|
|
15544
14876
|
};
|
|
15545
14877
|
try {
|
|
15546
|
-
configWatcher =
|
|
14878
|
+
configWatcher = import_node_fs33.watch(configDir, { persistent: false }, (_event, changedName) => {
|
|
15547
14879
|
if (!changedName)
|
|
15548
14880
|
return;
|
|
15549
14881
|
const name = String(changedName);
|
|
@@ -15568,7 +14900,7 @@ function hashLinkSecret(value) {
|
|
|
15568
14900
|
function readJvsclawLinkSecretFromConfigFile(configPath = resolveConfigPath()) {
|
|
15569
14901
|
let config;
|
|
15570
14902
|
try {
|
|
15571
|
-
config = JSON.parse(
|
|
14903
|
+
config = JSON.parse(import_node_fs33.readFileSync(configPath, "utf-8"));
|
|
15572
14904
|
} catch {
|
|
15573
14905
|
return;
|
|
15574
14906
|
}
|
|
@@ -15585,7 +14917,7 @@ function readObject2(value, key) {
|
|
|
15585
14917
|
const nested = value[key];
|
|
15586
14918
|
return nested && typeof nested === "object" ? nested : undefined;
|
|
15587
14919
|
}
|
|
15588
|
-
function
|
|
14920
|
+
function sleep2(ms, signal) {
|
|
15589
14921
|
return new Promise((resolve4) => {
|
|
15590
14922
|
if (signal?.aborted) {
|
|
15591
14923
|
resolve4(true);
|
|
@@ -15682,7 +15014,7 @@ function readHostGatewayConfig(params) {
|
|
|
15682
15014
|
let configData;
|
|
15683
15015
|
if (configPath) {
|
|
15684
15016
|
try {
|
|
15685
|
-
configData = JSON.parse(
|
|
15017
|
+
configData = JSON.parse(import_node_fs34.readFileSync(configPath, "utf-8"));
|
|
15686
15018
|
} catch (err2) {
|
|
15687
15019
|
if (err2?.code !== "ENOENT") {
|
|
15688
15020
|
params.logger.warn(`Relay tunnel: 无法读取 gateway 鉴权配置 (${configPath})`);
|
|
@@ -16353,56 +15685,11 @@ function registerRecordingInterfaces(deps) {
|
|
|
16353
15685
|
const {
|
|
16354
15686
|
api,
|
|
16355
15687
|
logger,
|
|
16356
|
-
asrDataDir,
|
|
16357
15688
|
getRecordingStorage,
|
|
16358
15689
|
notifyRecordingStatus,
|
|
16359
15690
|
registerGatewayMethod,
|
|
16360
|
-
shouldBroadcastStatusOnHttp,
|
|
16361
15691
|
tunnelService
|
|
16362
15692
|
} = deps;
|
|
16363
|
-
registerGatewayMethod("recordings.sync", async ({ params, respond }) => {
|
|
16364
|
-
const recordingStorage = getRecordingStorage();
|
|
16365
|
-
if (!recordingStorage) {
|
|
16366
|
-
respond(false, null, {
|
|
16367
|
-
code: "NOT_READY",
|
|
16368
|
-
message: "Recording storage service not ready"
|
|
16369
|
-
});
|
|
16370
|
-
return;
|
|
16371
|
-
}
|
|
16372
|
-
const {
|
|
16373
|
-
recordingId: rawRecordingId,
|
|
16374
|
-
recording,
|
|
16375
|
-
asr
|
|
16376
|
-
} = params;
|
|
16377
|
-
const recordingId = trimToUndefined2(rawRecordingId);
|
|
16378
|
-
if (!recordingId) {
|
|
16379
|
-
respond(false, null, {
|
|
16380
|
-
code: "INVALID_PARAMS",
|
|
16381
|
-
message: "recordingId is required"
|
|
16382
|
-
});
|
|
16383
|
-
return;
|
|
16384
|
-
}
|
|
16385
|
-
if (!recording || !recording.oss_audio_url || !recording.created_at) {
|
|
16386
|
-
respond(false, null, {
|
|
16387
|
-
code: "INVALID_PARAMS",
|
|
16388
|
-
message: "recording with oss_audio_url and created_at is required"
|
|
16389
|
-
});
|
|
16390
|
-
return;
|
|
16391
|
-
}
|
|
16392
|
-
const asrError = asr ? validateAsrConfig(asr) : undefined;
|
|
16393
|
-
if (asrError) {
|
|
16394
|
-
respond(false, null, {
|
|
16395
|
-
code: "INVALID_PARAMS",
|
|
16396
|
-
message: asrError
|
|
16397
|
-
});
|
|
16398
|
-
return;
|
|
16399
|
-
}
|
|
16400
|
-
const result = await handleRecordingSync(recordingId, recording, recordingStorage, asr, logger, { notifyStatus: notifyRecordingStatus });
|
|
16401
|
-
respond(result.ok, result, result.ok ? undefined : {
|
|
16402
|
-
code: "SYNC_FAILED",
|
|
16403
|
-
message: result.error ?? "Unknown error"
|
|
16404
|
-
});
|
|
16405
|
-
});
|
|
16406
15693
|
registerGatewayMethod("recordings.result.write", async ({ params, respond }) => {
|
|
16407
15694
|
const recordingStorage = getRecordingStorage();
|
|
16408
15695
|
if (!recordingStorage) {
|
|
@@ -16570,69 +15857,6 @@ function registerRecordingInterfaces(deps) {
|
|
|
16570
15857
|
message: `Recording not found: ${recordingId}`
|
|
16571
15858
|
});
|
|
16572
15859
|
});
|
|
16573
|
-
registerGatewayMethod("recordings.retranscribe", async ({ params, respond }) => {
|
|
16574
|
-
const recordingStorage = getRecordingStorage();
|
|
16575
|
-
if (!recordingStorage) {
|
|
16576
|
-
respond(false, null, {
|
|
16577
|
-
code: "NOT_READY",
|
|
16578
|
-
message: "Recording storage service not ready"
|
|
16579
|
-
});
|
|
16580
|
-
return;
|
|
16581
|
-
}
|
|
16582
|
-
const { recordingId, asr } = params;
|
|
16583
|
-
if (!recordingId) {
|
|
16584
|
-
respond(false, null, {
|
|
16585
|
-
code: "INVALID_PARAMS",
|
|
16586
|
-
message: "recordingId is required"
|
|
16587
|
-
});
|
|
16588
|
-
return;
|
|
16589
|
-
}
|
|
16590
|
-
const asrError = validateAsrConfig(asr);
|
|
16591
|
-
if (asrError) {
|
|
16592
|
-
respond(false, null, {
|
|
16593
|
-
code: "ASR_NOT_CONFIGURED",
|
|
16594
|
-
message: asrError
|
|
16595
|
-
});
|
|
16596
|
-
return;
|
|
16597
|
-
}
|
|
16598
|
-
const entry = recordingStorage.findById(recordingId);
|
|
16599
|
-
if (!entry) {
|
|
16600
|
-
respond(false, null, {
|
|
16601
|
-
code: "NOT_FOUND",
|
|
16602
|
-
message: `Recording not found: ${recordingId}`
|
|
16603
|
-
});
|
|
16604
|
-
return;
|
|
16605
|
-
}
|
|
16606
|
-
if (!canStartTranscription(entry.status)) {
|
|
16607
|
-
respond(false, null, {
|
|
16608
|
-
code: "INVALID_STATE",
|
|
16609
|
-
message: `Recording status does not allow retranscribe: ${entry.status}`
|
|
16610
|
-
});
|
|
16611
|
-
return;
|
|
16612
|
-
}
|
|
16613
|
-
triggerTranscription(recordingId, recordingStorage, asr, logger, {
|
|
16614
|
-
notifyStatus: notifyRecordingStatus
|
|
16615
|
-
}).catch((err2) => {
|
|
16616
|
-
logger.error(`[recordings.retranscribe] 重试转写失败: ${recordingId}, ${err2?.message ?? err2}`);
|
|
16617
|
-
});
|
|
16618
|
-
respond(true, { ok: true, recordingId, message: "转写已重新触发" });
|
|
16619
|
-
});
|
|
16620
|
-
registerGatewayMethod("recordings.asr.init", async ({ params, respond }) => {
|
|
16621
|
-
const { asr } = params;
|
|
16622
|
-
const asrError = validateAsrConfig(asr);
|
|
16623
|
-
if (asrError) {
|
|
16624
|
-
respond(false, null, {
|
|
16625
|
-
code: "INVALID_PARAMS",
|
|
16626
|
-
message: asrError
|
|
16627
|
-
});
|
|
16628
|
-
return;
|
|
16629
|
-
}
|
|
16630
|
-
const result = await initializeAsr(asr, asrDataDir, logger);
|
|
16631
|
-
respond(result.ok, result, result.ok ? undefined : {
|
|
16632
|
-
code: "ASR_INIT_FAILED",
|
|
16633
|
-
message: result.error ?? "ASR init failed"
|
|
16634
|
-
});
|
|
16635
|
-
});
|
|
16636
15860
|
api.registerHttpRoute({
|
|
16637
15861
|
path: "/recordings",
|
|
16638
15862
|
auth: "gateway",
|
|
@@ -16651,47 +15875,15 @@ function registerRecordingInterfaces(deps) {
|
|
|
16651
15875
|
res.end(JSON.stringify({ ok: false, error: "Service Not Ready" }));
|
|
16652
15876
|
return;
|
|
16653
15877
|
}
|
|
16654
|
-
|
|
16655
|
-
|
|
16656
|
-
|
|
16657
|
-
|
|
16658
|
-
}
|
|
16659
|
-
res.writeHead(400, { "Content-Type": "application/json" });
|
|
16660
|
-
res.end(JSON.stringify({ ok: false, error: "Invalid JSON" }));
|
|
16661
|
-
return;
|
|
16662
|
-
}
|
|
16663
|
-
const recordingId = trimToUndefined2(body.recordingId);
|
|
16664
|
-
if (!recordingId) {
|
|
16665
|
-
res.writeHead(400, { "Content-Type": "application/json" });
|
|
16666
|
-
res.end(JSON.stringify({
|
|
16667
|
-
ok: false,
|
|
16668
|
-
error: "recordingId is required"
|
|
16669
|
-
}));
|
|
16670
|
-
return;
|
|
16671
|
-
}
|
|
16672
|
-
if (!body.recording || !body.recording.oss_audio_url) {
|
|
16673
|
-
res.writeHead(400, { "Content-Type": "application/json" });
|
|
16674
|
-
res.end(JSON.stringify({
|
|
16675
|
-
ok: false,
|
|
16676
|
-
error: "recording with oss_audio_url is required"
|
|
16677
|
-
}));
|
|
16678
|
-
return;
|
|
16679
|
-
}
|
|
16680
|
-
const asrError = body.asr ? validateAsrConfig(body.asr) : undefined;
|
|
16681
|
-
if (asrError) {
|
|
16682
|
-
res.writeHead(400, { "Content-Type": "application/json" });
|
|
16683
|
-
res.end(JSON.stringify({ ok: false, error: asrError }));
|
|
16684
|
-
return;
|
|
16685
|
-
}
|
|
16686
|
-
const result = await handleRecordingSync(recordingId, body.recording, recordingStorage, body.asr, logger, shouldBroadcastStatusOnHttp() ? { notifyStatus: notifyRecordingStatus } : undefined);
|
|
16687
|
-
res.writeHead(result.ok ? 200 : 500, {
|
|
16688
|
-
"Content-Type": "application/json"
|
|
16689
|
-
});
|
|
16690
|
-
res.end(JSON.stringify(result));
|
|
15878
|
+
res.writeHead(410, { "Content-Type": "application/json" });
|
|
15879
|
+
res.end(JSON.stringify({
|
|
15880
|
+
ok: false,
|
|
15881
|
+
error: "POST /recordings 已停用,请改用 gateway 方法 recordings.result.write"
|
|
15882
|
+
}));
|
|
16691
15883
|
}
|
|
16692
15884
|
});
|
|
16693
|
-
logger.info("Gateway 录音方法已注册: recordings.
|
|
16694
|
-
logger.info("HTTP 录音端点已注册: POST /recordings");
|
|
15885
|
+
logger.info("Gateway 录音方法已注册: recordings.result.write / recordings.list / recordings.status / recordings.rename / recordings.delete");
|
|
15886
|
+
logger.info("HTTP 录音端点已注册: POST /recordings (已停用,返回 410)");
|
|
16695
15887
|
}
|
|
16696
15888
|
|
|
16697
15889
|
// src/plugin/images.ts
|
|
@@ -16858,9 +16050,8 @@ var src_default = {
|
|
|
16858
16050
|
let tunnelService = null;
|
|
16859
16051
|
const openclawDir = resolvePluginStateDir(api);
|
|
16860
16052
|
const logger = openclawDir ? new PluginFileLogger(api.logger, openclawDir) : createVersionAwareLogger(api.logger);
|
|
16861
|
-
const asrDataDir = openclawDir ?? resolveStateDir() ?? process.cwd();
|
|
16862
16053
|
ensureNtfBinInstalled({
|
|
16863
|
-
ntfCjsPath:
|
|
16054
|
+
ntfCjsPath: import_node_path31.join(__dirname, "bin", "ntf.cjs"),
|
|
16864
16055
|
stateDir: openclawDir,
|
|
16865
16056
|
logger
|
|
16866
16057
|
});
|
|
@@ -16990,11 +16181,9 @@ var src_default = {
|
|
|
16990
16181
|
registerRecordingInterfaces({
|
|
16991
16182
|
api,
|
|
16992
16183
|
logger,
|
|
16993
|
-
asrDataDir,
|
|
16994
16184
|
getRecordingStorage: () => recordingStorage,
|
|
16995
16185
|
notifyRecordingStatus,
|
|
16996
16186
|
registerGatewayMethod: registerGatewayMethodWithBroadcastCapture,
|
|
16997
|
-
shouldBroadcastStatusOnHttp: () => !!broadcastFn,
|
|
16998
16187
|
tunnelService
|
|
16999
16188
|
});
|
|
17000
16189
|
registerImageInterfaces({
|
|
@@ -17023,5 +16212,5 @@ var src_default = {
|
|
|
17023
16212
|
}
|
|
17024
16213
|
};
|
|
17025
16214
|
|
|
17026
|
-
//# debugId=
|
|
16215
|
+
//# debugId=1C4AEF73472B01C364756E2164756E21
|
|
17027
16216
|
//# sourceMappingURL=index.cjs.map
|