sophhub 0.2.0 → 0.2.2
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/package.json +1 -1
- package/skills/compact-context/skill.json +20 -0
- package/skills/compact-context/src/SKILL.md +133 -0
- package/skills/compact-context/src/scripts/check.sh +381 -0
- package/skills/compact-context/src/scripts/set-keep-recent.mjs +1337 -0
- package/skills/compact-context/src/scripts/setup.sh +96 -0
- package/skills/feishu-notes-assistant-universal/skill.json +20 -0
- package/skills/feishu-notes-assistant-universal/src/README.md +55 -0
- package/skills/feishu-notes-assistant-universal/src/SKILL.md +159 -0
- package/skills/feishu-notes-assistant-universal/src/bin/linux-amd64/lark-cli-openclaw +0 -0
- package/skills/feishu-notes-assistant-universal/src/bin/linux-arm64/lark-cli-openclaw +0 -0
- package/skills/feishu-notes-assistant-universal/src/scripts/_resolve_lark_cli.py +58 -0
- package/skills/feishu-notes-assistant-universal/src/scripts/openclaw_meeting_minutes.py +462 -0
- package/skills/feishu-notes-assistant-universal/src/scripts/openclaw_notes_crud.py +547 -0
- package/skills/feishu-notes-assistant-universal/src/scripts/openclaw_notes_crud_test.py +181 -0
- package/skills/feishu-notes-assistant-universal/src/scripts/run_meeting_minutes.py +80 -0
- package/skills/feishu-notes-assistant-universal/src/scripts/run_meeting_minutes.sh +5 -0
- package/skills/feishu-notes-assistant-universal/src/scripts/run_note_crud.py +32 -0
- package/skills/feishu-notes-assistant-universal/src/scripts/run_note_crud.sh +5 -0
- package/skills/image-classify/skill.json +5 -5
- package/skills/image-classify/src/SKILL.md +60 -67
- package/skills/image-classify/src/scripts/face_search.py +400 -15
- package/skills/image-classify/src/scripts/send_dm_message.py +332 -0
- package/skills/md2pdf-converter/skill.json +20 -0
- package/skills/md2pdf-converter/src/SKILL.md +244 -0
- package/skills/md2pdf-converter/src/_meta.json +6 -0
- package/skills/md2pdf-converter/src/scripts/generate_emoji_mapping.py +74 -0
- package/skills/md2pdf-converter/src/scripts/md2pdf-local.sh +291 -0
- package/skills/sophnet-bot-client/skill.json +20 -0
- package/skills/sophnet-bot-client/src/SKILL.md +255 -0
- package/skills/sophnet-bot-client/src/pyproject.toml +13 -0
- package/skills/sophnet-bot-client/src/scripts/__init__.py +0 -0
- package/skills/sophnet-bot-client/src/scripts/bot_client_proxy.py +165 -0
- package/skills/sophnet-bot-client/src/scripts/bot_client_safe.sh +29 -0
- package/skills/sophnet-bot-client/src/scripts/bot_client_setup.py +502 -0
- package/skills/sophnet-bot-client/src/tests/__init__.py +0 -0
- package/skills/sophnet-bot-client/src/tests/test_bot_client_proxy.py +255 -0
- package/skills/sophnet-bot-client/src/tests/test_bot_client_setup.py +679 -0
- package/skills/sophnet-bot-client/src/uv.lock +8 -0
- package/skills/sophnet-docx/skill.json +20 -0
- package/skills/sophnet-docx/src/SKILL.md +463 -0
- package/skills/sophnet-docx/src/package-lock.json +208 -0
- package/skills/sophnet-docx/src/package.json +16 -0
- package/skills/sophnet-docx/src/pyproject.toml +11 -0
- package/skills/sophnet-docx/src/scripts/__init__.py +1 -0
- package/skills/sophnet-docx/src/scripts/accept_changes.py +135 -0
- package/skills/sophnet-docx/src/scripts/comment.py +318 -0
- package/skills/sophnet-docx/src/scripts/ensure_uv_env.sh +68 -0
- package/skills/sophnet-docx/src/scripts/office/helpers/__init__.py +0 -0
- package/skills/sophnet-docx/src/scripts/office/helpers/merge_runs.py +199 -0
- package/skills/sophnet-docx/src/scripts/office/helpers/simplify_redlines.py +197 -0
- package/skills/sophnet-docx/src/scripts/office/pack.py +159 -0
- package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -0
- package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -0
- package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -0
- package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -0
- package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -0
- package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -0
- package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -0
- package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -0
- package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -0
- package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -0
- package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -0
- package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -0
- package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -0
- package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -0
- package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -0
- package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -0
- package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -0
- package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -0
- package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -0
- package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -0
- package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -0
- package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -0
- package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -0
- package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -0
- package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -0
- package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -0
- package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -0
- package/skills/sophnet-docx/src/scripts/office/schemas/ecma/fouth-edition/opc-contentTypes.xsd +42 -0
- package/skills/sophnet-docx/src/scripts/office/schemas/ecma/fouth-edition/opc-coreProperties.xsd +50 -0
- package/skills/sophnet-docx/src/scripts/office/schemas/ecma/fouth-edition/opc-digSig.xsd +49 -0
- package/skills/sophnet-docx/src/scripts/office/schemas/ecma/fouth-edition/opc-relationships.xsd +33 -0
- package/skills/sophnet-docx/src/scripts/office/schemas/mce/mc.xsd +75 -0
- package/skills/sophnet-docx/src/scripts/office/schemas/microsoft/wml-2010.xsd +560 -0
- package/skills/sophnet-docx/src/scripts/office/schemas/microsoft/wml-2012.xsd +67 -0
- package/skills/sophnet-docx/src/scripts/office/schemas/microsoft/wml-2018.xsd +14 -0
- package/skills/sophnet-docx/src/scripts/office/schemas/microsoft/wml-cex-2018.xsd +20 -0
- package/skills/sophnet-docx/src/scripts/office/schemas/microsoft/wml-cid-2016.xsd +13 -0
- package/skills/sophnet-docx/src/scripts/office/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -0
- package/skills/sophnet-docx/src/scripts/office/schemas/microsoft/wml-symex-2015.xsd +8 -0
- package/skills/sophnet-docx/src/scripts/office/soffice.py +183 -0
- package/skills/sophnet-docx/src/scripts/office/unpack.py +132 -0
- package/skills/sophnet-docx/src/scripts/office/validate.py +111 -0
- package/skills/sophnet-docx/src/scripts/office/validators/__init__.py +15 -0
- package/skills/sophnet-docx/src/scripts/office/validators/base.py +847 -0
- package/skills/sophnet-docx/src/scripts/office/validators/docx.py +446 -0
- package/skills/sophnet-docx/src/scripts/office/validators/pptx.py +275 -0
- package/skills/sophnet-docx/src/scripts/office/validators/redlining.py +247 -0
- package/skills/sophnet-docx/src/scripts/templates/comments.xml +3 -0
- package/skills/sophnet-docx/src/scripts/templates/commentsExtended.xml +3 -0
- package/skills/sophnet-docx/src/scripts/templates/commentsExtensible.xml +3 -0
- package/skills/sophnet-docx/src/scripts/templates/commentsIds.xml +3 -0
- package/skills/sophnet-docx/src/scripts/templates/people.xml +3 -0
- package/skills/sophnet-docx/src/scripts/upload_file.sh +96 -0
- package/skills/sophnet-docx/src/uv.lock +320 -0
- package/skills/sophnet-pdf/skill.json +20 -0
- package/skills/sophnet-pdf/src/SKILL.md +413 -0
- package/skills/sophnet-pdf/src/forms.md +297 -0
- package/skills/sophnet-pdf/src/pyproject.toml +14 -0
- package/skills/sophnet-pdf/src/reference.md +612 -0
- package/skills/sophnet-pdf/src/scripts/check_bounding_boxes.py +65 -0
- package/skills/sophnet-pdf/src/scripts/check_fillable_fields.py +11 -0
- package/skills/sophnet-pdf/src/scripts/convert_pdf_to_images.py +33 -0
- package/skills/sophnet-pdf/src/scripts/create_validation_image.py +37 -0
- package/skills/sophnet-pdf/src/scripts/enhance_tutorial.py +558 -0
- package/skills/sophnet-pdf/src/scripts/ensure_uv_env.sh +68 -0
- package/skills/sophnet-pdf/src/scripts/extract_form_field_info.py +122 -0
- package/skills/sophnet-pdf/src/scripts/extract_form_structure.py +115 -0
- package/skills/sophnet-pdf/src/scripts/extract_pdf_content.py +35 -0
- package/skills/sophnet-pdf/src/scripts/fill_fillable_fields.py +98 -0
- package/skills/sophnet-pdf/src/scripts/fill_pdf_form_with_annotations.py +107 -0
- package/skills/sophnet-pdf/src/scripts/upload_file.sh +88 -0
- package/skills/sophnet-pdf/src/uv.lock +537 -0
- package/skills/sophnet-xlsx/skill.json +20 -0
- package/skills/sophnet-xlsx/src/SKILL.md +399 -0
- package/skills/sophnet-xlsx/src/pyproject.toml +11 -0
- package/skills/sophnet-xlsx/src/scripts/ensure_uv_env.sh +68 -0
- package/skills/sophnet-xlsx/src/scripts/office/helpers/__init__.py +0 -0
- package/skills/sophnet-xlsx/src/scripts/office/helpers/merge_runs.py +199 -0
- package/skills/sophnet-xlsx/src/scripts/office/helpers/simplify_redlines.py +197 -0
- package/skills/sophnet-xlsx/src/scripts/office/pack.py +159 -0
- package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -0
- package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -0
- package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -0
- package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -0
- package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -0
- package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -0
- package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -0
- package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -0
- package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -0
- package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -0
- package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -0
- package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -0
- package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -0
- package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -0
- package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -0
- package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -0
- package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -0
- package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -0
- package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -0
- package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -0
- package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -0
- package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -0
- package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -0
- package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -0
- package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -0
- package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -0
- package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -0
- package/skills/sophnet-xlsx/src/scripts/office/schemas/ecma/fouth-edition/opc-contentTypes.xsd +42 -0
- package/skills/sophnet-xlsx/src/scripts/office/schemas/ecma/fouth-edition/opc-coreProperties.xsd +50 -0
- package/skills/sophnet-xlsx/src/scripts/office/schemas/ecma/fouth-edition/opc-digSig.xsd +49 -0
- package/skills/sophnet-xlsx/src/scripts/office/schemas/ecma/fouth-edition/opc-relationships.xsd +33 -0
- package/skills/sophnet-xlsx/src/scripts/office/schemas/mce/mc.xsd +75 -0
- package/skills/sophnet-xlsx/src/scripts/office/schemas/microsoft/wml-2010.xsd +560 -0
- package/skills/sophnet-xlsx/src/scripts/office/schemas/microsoft/wml-2012.xsd +67 -0
- package/skills/sophnet-xlsx/src/scripts/office/schemas/microsoft/wml-2018.xsd +14 -0
- package/skills/sophnet-xlsx/src/scripts/office/schemas/microsoft/wml-cex-2018.xsd +20 -0
- package/skills/sophnet-xlsx/src/scripts/office/schemas/microsoft/wml-cid-2016.xsd +13 -0
- package/skills/sophnet-xlsx/src/scripts/office/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -0
- package/skills/sophnet-xlsx/src/scripts/office/schemas/microsoft/wml-symex-2015.xsd +8 -0
- package/skills/sophnet-xlsx/src/scripts/office/soffice.py +183 -0
- package/skills/sophnet-xlsx/src/scripts/office/unpack.py +132 -0
- package/skills/sophnet-xlsx/src/scripts/office/validate.py +111 -0
- package/skills/sophnet-xlsx/src/scripts/office/validators/__init__.py +15 -0
- package/skills/sophnet-xlsx/src/scripts/office/validators/base.py +847 -0
- package/skills/sophnet-xlsx/src/scripts/office/validators/docx.py +446 -0
- package/skills/sophnet-xlsx/src/scripts/office/validators/pptx.py +275 -0
- package/skills/sophnet-xlsx/src/scripts/office/validators/redlining.py +247 -0
- package/skills/sophnet-xlsx/src/scripts/recalc.py +184 -0
- package/skills/sophnet-xlsx/src/scripts/upload_file.sh +96 -0
- package/skills/sophnet-xlsx/src/uv.lock +319 -0
- package/skills/wechat-article-publisher/skill.json +20 -0
- package/skills/wechat-article-publisher/src/SKILL.md +60 -0
- package/skills/wechat-article-publisher/src/config.json +7 -0
- package/skills/wechat-article-publisher/src/pyproject.toml +12 -0
- package/skills/wechat-article-publisher/src/scripts/publish_wechat.py +825 -0
|
@@ -0,0 +1,1337 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Align keepRecentTokens with OpenClaw native session/store accounting.
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* node set-keep-recent.mjs --turns 5 [--workspace /path/to/ws] [--session /path/to/file.jsonl]
|
|
7
|
+
* node set-keep-recent.mjs --pct 30 [--workspace /path/to/ws]
|
|
8
|
+
* node set-keep-recent.mjs --tokens 20000 [--workspace /path/to/ws]
|
|
9
|
+
* node set-keep-recent.mjs --restore [--workspace /path/to/ws]
|
|
10
|
+
* node set-keep-recent.mjs --info [--workspace /path/to/ws]
|
|
11
|
+
*
|
|
12
|
+
* Extra selectors / diagnostics:
|
|
13
|
+
* --session-key <agent:...>
|
|
14
|
+
* --session-id <uuid>
|
|
15
|
+
* --state-dir <path>
|
|
16
|
+
* --debug
|
|
17
|
+
* --require-current-session
|
|
18
|
+
* --allow-latest-updated
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import fs from "node:fs";
|
|
22
|
+
import os from "node:os";
|
|
23
|
+
import path from "node:path";
|
|
24
|
+
import { spawnSync } from "node:child_process";
|
|
25
|
+
import { fileURLToPath } from "node:url";
|
|
26
|
+
|
|
27
|
+
export const BACKUP_KEY = "__compactContextSkillBackup";
|
|
28
|
+
export const LAST_RUN_KEY = "__compactContextSkillLastRun";
|
|
29
|
+
export const LAST_RESTORE_KEY = "__compactContextSkillLastRestore";
|
|
30
|
+
export const BACKUP_VERSION = 2;
|
|
31
|
+
export const INTERACTION_TURN_ALLOWANCE = 2;
|
|
32
|
+
export const DISPLAY_LABEL = "message_estimate";
|
|
33
|
+
export const PROMPT_TRUTH_SOURCE = "next_real_usage";
|
|
34
|
+
const DEFAULT_CONTEXT_TOKENS = 200000;
|
|
35
|
+
const OPENCLAW_JSON_ARGS = ["sessions", "--json"];
|
|
36
|
+
const ANSI_PATTERN = /\u001b\[[0-9;?]*[ -/]*[@-~]/g;
|
|
37
|
+
const SESSION_ROLES = new Set(["user", "human"]);
|
|
38
|
+
const TURN_KEEP_TOKENS_SAFETY_RATIO = 1.10;
|
|
39
|
+
const TURN_KEEP_TOKENS_SAFETY_BUFFER = 256;
|
|
40
|
+
const SESSION_REQUIRED_ERROR = "无法定位当前对话线程:缺少 session-key/session-id,拒绝执行局部压缩设置。";
|
|
41
|
+
|
|
42
|
+
function getArg(args, name) {
|
|
43
|
+
const index = args.indexOf(name);
|
|
44
|
+
return index >= 0 ? args[index + 1] : null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function hasArg(args, name) {
|
|
48
|
+
return args.includes(name);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function parseArgs(argv) {
|
|
52
|
+
const keepTurnsValue = getArg(argv, "--turns");
|
|
53
|
+
const keepPctValue = getArg(argv, "--pct");
|
|
54
|
+
const keepTokensValue = getArg(argv, "--tokens");
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
keepTurns: keepTurnsValue !== null ? parseInt(keepTurnsValue, 10) : null,
|
|
58
|
+
keepPct: keepPctValue !== null ? parseFloat(keepPctValue) : null,
|
|
59
|
+
keepTokens: keepTokensValue !== null ? parseInt(keepTokensValue, 10) : null,
|
|
60
|
+
restore: hasArg(argv, "--restore"),
|
|
61
|
+
infoMode: hasArg(argv, "--info"),
|
|
62
|
+
debug: hasArg(argv, "--debug"),
|
|
63
|
+
workspace: getArg(argv, "--workspace") ?? process.cwd(),
|
|
64
|
+
sessionFile: getArg(argv, "--session"),
|
|
65
|
+
sessionKey: getArg(argv, "--session-key"),
|
|
66
|
+
sessionId: getArg(argv, "--session-id"),
|
|
67
|
+
stateDir: getArg(argv, "--state-dir"),
|
|
68
|
+
requireCurrentSession: hasArg(argv, "--require-current-session"),
|
|
69
|
+
allowLatestUpdated: hasArg(argv, "--allow-latest-updated"),
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function stripAnsi(text) {
|
|
74
|
+
return String(text ?? "").replace(ANSI_PATTERN, "");
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function toPositiveNumber(value) {
|
|
78
|
+
const parsed = typeof value === "number" ? value : Number(value);
|
|
79
|
+
if (!Number.isFinite(parsed) || parsed <= 0) return null;
|
|
80
|
+
return parsed;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function toFiniteNumber(value) {
|
|
84
|
+
const parsed = typeof value === "number" ? value : Number(value);
|
|
85
|
+
if (!Number.isFinite(parsed)) return null;
|
|
86
|
+
return parsed;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function uniq(values) {
|
|
90
|
+
return [...new Set(values.filter(Boolean))];
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function parseAgentIdFromSessionKey(sessionKey) {
|
|
94
|
+
if (!sessionKey || typeof sessionKey !== "string") return null;
|
|
95
|
+
const parts = sessionKey.split(":");
|
|
96
|
+
if (parts[0] !== "agent" || parts.length < 2) return null;
|
|
97
|
+
return parts[1] || null;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function formatDebugValue(value) {
|
|
101
|
+
if (value === null || value === undefined || value === "") return "none";
|
|
102
|
+
return String(value).replace(/\s+/g, "_");
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function printDebug(debug, entries) {
|
|
106
|
+
if (!debug) return;
|
|
107
|
+
for (const [key, value] of Object.entries(entries)) {
|
|
108
|
+
if (value === undefined) continue;
|
|
109
|
+
console.log(`debug ${key}=${formatDebugValue(value)}`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function readJsonFile(filePath) {
|
|
114
|
+
if (!filePath || !fs.existsSync(filePath)) return null;
|
|
115
|
+
try {
|
|
116
|
+
return JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
117
|
+
} catch {
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function resolveConfigPath() {
|
|
123
|
+
return process.env.OPENCLAW_CONFIG_PATH || path.join(os.homedir(), ".openclaw", "openclaw.json");
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function readOpenClawConfig() {
|
|
127
|
+
return readJsonFile(resolveConfigPath()) ?? {};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function getCandidateStateDirs(overrideStateDir = null) {
|
|
131
|
+
return uniq([
|
|
132
|
+
overrideStateDir,
|
|
133
|
+
process.env.OPENCLAW_STATE_DIR,
|
|
134
|
+
process.env.CLAWDBOT_STATE_DIR,
|
|
135
|
+
path.join(os.homedir(), ".openclaw"),
|
|
136
|
+
path.join(os.homedir(), ".clawdbot"),
|
|
137
|
+
path.join(os.homedir(), ".moltbot"),
|
|
138
|
+
]);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function extractFirstJsonValue(text) {
|
|
142
|
+
const source = String(text ?? "");
|
|
143
|
+
for (let start = 0; start < source.length; start += 1) {
|
|
144
|
+
const startChar = source[start];
|
|
145
|
+
if (startChar !== "{" && startChar !== "[") continue;
|
|
146
|
+
|
|
147
|
+
const stack = [];
|
|
148
|
+
let inString = false;
|
|
149
|
+
let escaped = false;
|
|
150
|
+
|
|
151
|
+
for (let index = start; index < source.length; index += 1) {
|
|
152
|
+
const char = source[index];
|
|
153
|
+
|
|
154
|
+
if (inString) {
|
|
155
|
+
if (escaped) {
|
|
156
|
+
escaped = false;
|
|
157
|
+
} else if (char === "\\") {
|
|
158
|
+
escaped = true;
|
|
159
|
+
} else if (char === "\"") {
|
|
160
|
+
inString = false;
|
|
161
|
+
}
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (char === "\"") {
|
|
166
|
+
inString = true;
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (char === "{" || char === "[") {
|
|
171
|
+
stack.push(char === "{" ? "}" : "]");
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (char === "}" || char === "]") {
|
|
176
|
+
if (stack.length === 0) break;
|
|
177
|
+
const expected = stack.pop();
|
|
178
|
+
if (expected !== char) break;
|
|
179
|
+
if (stack.length === 0) {
|
|
180
|
+
const candidate = source.slice(start, index + 1);
|
|
181
|
+
try {
|
|
182
|
+
JSON.parse(candidate);
|
|
183
|
+
return candidate;
|
|
184
|
+
} catch {
|
|
185
|
+
break;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export function parseSessionsCommandOutput(outputText) {
|
|
196
|
+
const source = String(outputText ?? "");
|
|
197
|
+
for (let start = 0; start < source.length; start += 1) {
|
|
198
|
+
const startChar = source[start];
|
|
199
|
+
if (startChar !== "{" && startChar !== "[") continue;
|
|
200
|
+
const jsonText = extractFirstJsonValue(source.slice(start));
|
|
201
|
+
if (!jsonText) continue;
|
|
202
|
+
|
|
203
|
+
try {
|
|
204
|
+
const parsed = JSON.parse(jsonText);
|
|
205
|
+
if (!parsed || typeof parsed !== "object") continue;
|
|
206
|
+
if (!Array.isArray(parsed?.sessions) && typeof parsed?.path !== "string") continue;
|
|
207
|
+
return {
|
|
208
|
+
path: typeof parsed?.path === "string" ? parsed.path : null,
|
|
209
|
+
sessions: Array.isArray(parsed?.sessions) ? parsed.sessions : [],
|
|
210
|
+
};
|
|
211
|
+
} catch {
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function deriveStateDirFromStorePath(storePath) {
|
|
220
|
+
if (!storePath) return null;
|
|
221
|
+
const marker = `${path.sep}agents${path.sep}`;
|
|
222
|
+
const markerIndex = storePath.indexOf(marker);
|
|
223
|
+
if (markerIndex === -1) return null;
|
|
224
|
+
return storePath.slice(0, markerIndex);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export function normalizeSessionRow(row, meta = {}) {
|
|
228
|
+
const key = row?.key ?? meta.key ?? null;
|
|
229
|
+
const agentId = row?.agentId ?? parseAgentIdFromSessionKey(key) ?? meta.agentId ?? null;
|
|
230
|
+
|
|
231
|
+
return {
|
|
232
|
+
key,
|
|
233
|
+
sessionId: row?.sessionId ?? row?.session_id ?? null,
|
|
234
|
+
updatedAt: toFiniteNumber(row?.updatedAt ?? row?.updated_at ?? null),
|
|
235
|
+
totalTokens: toFiniteNumber(row?.totalTokens ?? row?.total_tokens ?? null),
|
|
236
|
+
totalTokensFresh: typeof row?.totalTokensFresh === "boolean" ? row.totalTokensFresh : true,
|
|
237
|
+
inputTokens: toFiniteNumber(row?.inputTokens ?? row?.input_tokens ?? null),
|
|
238
|
+
outputTokens: toFiniteNumber(row?.outputTokens ?? row?.output_tokens ?? null),
|
|
239
|
+
contextTokens: toPositiveNumber(row?.contextTokens ?? row?.context_tokens ?? null),
|
|
240
|
+
model: row?.model ?? null,
|
|
241
|
+
modelProvider: row?.modelProvider ?? row?.provider ?? null,
|
|
242
|
+
sessionFile: row?.sessionFile ?? row?.session_file ?? null,
|
|
243
|
+
stateDir: meta.stateDir ?? null,
|
|
244
|
+
storeFile: meta.storeFile ?? null,
|
|
245
|
+
agentId,
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function runOpenClawSessionsJson(workspace) {
|
|
250
|
+
const result = spawnSync("openclaw", OPENCLAW_JSON_ARGS, {
|
|
251
|
+
cwd: workspace,
|
|
252
|
+
encoding: "utf8",
|
|
253
|
+
env: process.env,
|
|
254
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
const combined = [result.stdout, result.stderr].filter(Boolean).join("\n");
|
|
258
|
+
const parsed = parseSessionsCommandOutput(combined);
|
|
259
|
+
if (!parsed) {
|
|
260
|
+
return {
|
|
261
|
+
available: false,
|
|
262
|
+
sessions: [],
|
|
263
|
+
stateDir: null,
|
|
264
|
+
error: result.error?.message ?? `unable_to_parse_openclaw_sessions_output`,
|
|
265
|
+
rawOutput: combined,
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const stateDir = deriveStateDirFromStorePath(parsed.path);
|
|
270
|
+
return {
|
|
271
|
+
available: true,
|
|
272
|
+
sessions: parsed.sessions.map((session) =>
|
|
273
|
+
normalizeSessionRow(session, { stateDir }),
|
|
274
|
+
),
|
|
275
|
+
stateDir,
|
|
276
|
+
error: result.status === 0 ? null : result.error?.message ?? `openclaw_sessions_exit_${result.status}`,
|
|
277
|
+
rawOutput: combined,
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function loadSessionsFromStoreFiles(stateDirOverride = null) {
|
|
282
|
+
const rows = [];
|
|
283
|
+
|
|
284
|
+
for (const stateDir of getCandidateStateDirs(stateDirOverride)) {
|
|
285
|
+
const agentsDir = path.join(stateDir, "agents");
|
|
286
|
+
if (!fs.existsSync(agentsDir)) continue;
|
|
287
|
+
|
|
288
|
+
for (const agentId of fs.readdirSync(agentsDir, { withFileTypes: true })) {
|
|
289
|
+
if (!agentId.isDirectory()) continue;
|
|
290
|
+
const storeFile = path.join(agentsDir, agentId.name, "sessions", "sessions.json");
|
|
291
|
+
const store = readJsonFile(storeFile);
|
|
292
|
+
if (!store || typeof store !== "object") continue;
|
|
293
|
+
|
|
294
|
+
for (const [key, entry] of Object.entries(store)) {
|
|
295
|
+
if (key === "global" || key === "unknown") continue;
|
|
296
|
+
if (!entry || typeof entry !== "object") continue;
|
|
297
|
+
rows.push(normalizeSessionRow(entry, {
|
|
298
|
+
key,
|
|
299
|
+
agentId: agentId.name,
|
|
300
|
+
stateDir,
|
|
301
|
+
storeFile,
|
|
302
|
+
}));
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
return rows;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function buildSessionSelectors(options = {}) {
|
|
311
|
+
return [
|
|
312
|
+
{ value: options.sessionKey, type: "key", resolvedBy: "session-key" },
|
|
313
|
+
{ value: options.sessionId, type: "id", resolvedBy: "session-id" },
|
|
314
|
+
{ value: process.env.OPENCLAW_TASK_SESSION_KEY, type: "key", resolvedBy: "env-session-key" },
|
|
315
|
+
{ value: process.env.OPENCLAW_TASK_SESSION_ID, type: "id", resolvedBy: "env-session-id" },
|
|
316
|
+
].filter((selector) => selector.value);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function hasCliSessionSelector(options = {}) {
|
|
320
|
+
return Boolean(options.sessionKey || options.sessionId);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function sessionSelectorValue(options = {}) {
|
|
324
|
+
if (options.sessionKey) return `session-key=${options.sessionKey}`;
|
|
325
|
+
if (options.sessionId) return `session-id=${options.sessionId}`;
|
|
326
|
+
if (process.env.OPENCLAW_TASK_SESSION_KEY) return `env-session-key=${process.env.OPENCLAW_TASK_SESSION_KEY}`;
|
|
327
|
+
if (process.env.OPENCLAW_TASK_SESSION_ID) return `env-session-id=${process.env.OPENCLAW_TASK_SESSION_ID}`;
|
|
328
|
+
return "unknown";
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
export function chooseSessionRow(rows, options = {}) {
|
|
332
|
+
const selectors = buildSessionSelectors(options);
|
|
333
|
+
|
|
334
|
+
for (const selector of selectors) {
|
|
335
|
+
const candidates = rows.filter((row) =>
|
|
336
|
+
selector.type === "key" ? row.key === selector.value : row.sessionId === selector.value,
|
|
337
|
+
);
|
|
338
|
+
if (candidates.length > 0) {
|
|
339
|
+
const row = [...candidates].sort((left, right) => (right.updatedAt ?? 0) - (left.updatedAt ?? 0))[0];
|
|
340
|
+
return { row, resolvedBy: selector.resolvedBy, missing: false };
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
if (selectors.length > 0) {
|
|
345
|
+
const lastSelector = selectors[selectors.length - 1];
|
|
346
|
+
return {
|
|
347
|
+
row: null,
|
|
348
|
+
resolvedBy: `${lastSelector.resolvedBy}-missing`,
|
|
349
|
+
missing: true,
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
if (rows.length === 0 || options.allowLatestUpdated === false) return null;
|
|
354
|
+
const latest = [...rows].sort((left, right) => (right.updatedAt ?? 0) - (left.updatedAt ?? 0))[0];
|
|
355
|
+
return { row: latest, resolvedBy: "latest-updated", missing: false };
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
function modelMatches(modelId, candidateModel) {
|
|
359
|
+
if (!modelId || !candidateModel) return false;
|
|
360
|
+
const normalizedModelId = String(modelId).toLowerCase();
|
|
361
|
+
const normalizedCandidate = String(candidateModel).toLowerCase();
|
|
362
|
+
if (normalizedModelId === normalizedCandidate) return true;
|
|
363
|
+
const bareModelId = normalizedModelId.includes("/") ? normalizedModelId.split("/").pop() : normalizedModelId;
|
|
364
|
+
const bareCandidate = normalizedCandidate.includes("/") ? normalizedCandidate.split("/").pop() : normalizedCandidate;
|
|
365
|
+
return bareModelId === bareCandidate;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
function lookupModelContextWindow(cfg, provider, model) {
|
|
369
|
+
const providerMap = cfg?.models?.providers ?? {};
|
|
370
|
+
const providerKeys = provider ? [provider, provider.toLowerCase()] : Object.keys(providerMap);
|
|
371
|
+
|
|
372
|
+
for (const providerKey of providerKeys) {
|
|
373
|
+
const providerConfig = providerMap?.[providerKey];
|
|
374
|
+
const models = Array.isArray(providerConfig?.models) ? providerConfig.models : [];
|
|
375
|
+
for (const entry of models) {
|
|
376
|
+
if (!modelMatches(entry?.id, model) && !modelMatches(`${providerKey}/${entry?.id}`, model)) {
|
|
377
|
+
continue;
|
|
378
|
+
}
|
|
379
|
+
const contextWindow = toPositiveNumber(entry?.contextWindow ?? entry?.contextTokens ?? null);
|
|
380
|
+
if (contextWindow) return contextWindow;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
return null;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
export function resolveContextTokens({ cfg, provider, model, contextTokensOverride = null }) {
|
|
388
|
+
const explicitOverride = toPositiveNumber(contextTokensOverride);
|
|
389
|
+
if (explicitOverride) return explicitOverride;
|
|
390
|
+
|
|
391
|
+
const configuredDefault = toPositiveNumber(cfg?.agents?.defaults?.contextTokens ?? null);
|
|
392
|
+
const modelContextWindow = lookupModelContextWindow(cfg, provider, model);
|
|
393
|
+
if (modelContextWindow) return modelContextWindow;
|
|
394
|
+
if (configuredDefault) return configuredDefault;
|
|
395
|
+
|
|
396
|
+
const envContextWindow = toPositiveNumber(process.env.CONTEXT_WINDOW);
|
|
397
|
+
if (envContextWindow) return envContextWindow;
|
|
398
|
+
|
|
399
|
+
return DEFAULT_CONTEXT_TOKENS;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
export function resolveSessionFile(options, snapshot = null) {
|
|
403
|
+
if (options?.sessionFile && fs.existsSync(options.sessionFile)) {
|
|
404
|
+
return options.sessionFile;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
const sessionId = snapshot?.sessionId ?? options?.sessionId ?? process.env.OPENCLAW_TASK_SESSION_ID ?? null;
|
|
408
|
+
const agentId = snapshot?.agentId ?? parseAgentIdFromSessionKey(snapshot?.key ?? options?.sessionKey ?? process.env.OPENCLAW_TASK_SESSION_KEY ?? null) ?? "main";
|
|
409
|
+
const candidateStateDirs = uniq([
|
|
410
|
+
snapshot?.stateDir,
|
|
411
|
+
options?.stateDir,
|
|
412
|
+
...getCandidateStateDirs(options?.stateDir),
|
|
413
|
+
]);
|
|
414
|
+
|
|
415
|
+
if (snapshot?.sessionFile && fs.existsSync(snapshot.sessionFile)) {
|
|
416
|
+
return snapshot.sessionFile;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
if (!sessionId) return null;
|
|
420
|
+
|
|
421
|
+
for (const stateDir of candidateStateDirs) {
|
|
422
|
+
const sessionsDir = path.join(stateDir, "agents", agentId, "sessions");
|
|
423
|
+
if (!fs.existsSync(sessionsDir)) continue;
|
|
424
|
+
|
|
425
|
+
const exactPath = path.join(sessionsDir, `${sessionId}.jsonl`);
|
|
426
|
+
if (fs.existsSync(exactPath)) return exactPath;
|
|
427
|
+
|
|
428
|
+
const topicCandidates = fs.readdirSync(sessionsDir)
|
|
429
|
+
.filter((entry) => entry.startsWith(`${sessionId}-`) && entry.endsWith(".jsonl"))
|
|
430
|
+
.map((entry) => path.join(sessionsDir, entry))
|
|
431
|
+
.sort((left, right) => fs.statSync(right).mtimeMs - fs.statSync(left).mtimeMs);
|
|
432
|
+
|
|
433
|
+
if (topicCandidates.length > 0) return topicCandidates[0];
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
for (const stateDir of candidateStateDirs) {
|
|
437
|
+
const agentsDir = path.join(stateDir, "agents");
|
|
438
|
+
if (!fs.existsSync(agentsDir)) continue;
|
|
439
|
+
|
|
440
|
+
for (const agentDir of fs.readdirSync(agentsDir, { withFileTypes: true })) {
|
|
441
|
+
if (!agentDir.isDirectory()) continue;
|
|
442
|
+
const agentSessionsDir = path.join(agentsDir, agentDir.name, "sessions");
|
|
443
|
+
if (!fs.existsSync(agentSessionsDir)) continue;
|
|
444
|
+
const exactPath = path.join(agentSessionsDir, `${sessionId}.jsonl`);
|
|
445
|
+
if (fs.existsSync(exactPath)) return exactPath;
|
|
446
|
+
const topicCandidates = fs.readdirSync(agentSessionsDir)
|
|
447
|
+
.filter((entry) => entry.startsWith(`${sessionId}-`) && entry.endsWith(".jsonl"))
|
|
448
|
+
.map((entry) => path.join(agentSessionsDir, entry))
|
|
449
|
+
.sort((left, right) => fs.statSync(right).mtimeMs - fs.statSync(left).mtimeMs);
|
|
450
|
+
if (topicCandidates.length > 0) return topicCandidates[0];
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
return null;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
function resolveSessionSnapshot(options) {
|
|
458
|
+
const config = readOpenClawConfig();
|
|
459
|
+
const explicitSessionFile = options.sessionFile && fs.existsSync(options.sessionFile) ? options.sessionFile : null;
|
|
460
|
+
const selectors = buildSessionSelectors(options);
|
|
461
|
+
const requireBoundSession = options.requireCurrentSession === true;
|
|
462
|
+
const allowLatestUpdated = options.allowLatestUpdated === true;
|
|
463
|
+
|
|
464
|
+
if (requireBoundSession && !explicitSessionFile && selectors.length === 0) {
|
|
465
|
+
throw new Error(SESSION_REQUIRED_ERROR);
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
if (explicitSessionFile && !options.sessionKey && !options.sessionId) {
|
|
469
|
+
return {
|
|
470
|
+
key: null,
|
|
471
|
+
sessionId: null,
|
|
472
|
+
updatedAt: null,
|
|
473
|
+
totalTokens: null,
|
|
474
|
+
totalTokensFresh: false,
|
|
475
|
+
inputTokens: null,
|
|
476
|
+
outputTokens: null,
|
|
477
|
+
contextTokens: resolveContextTokens({ cfg: config, provider: null, model: null, contextTokensOverride: null }),
|
|
478
|
+
model: null,
|
|
479
|
+
modelProvider: null,
|
|
480
|
+
sessionFile: explicitSessionFile,
|
|
481
|
+
stateDir: options.stateDir ?? getCandidateStateDirs(options.stateDir)[0] ?? null,
|
|
482
|
+
storeFile: null,
|
|
483
|
+
agentId: "main",
|
|
484
|
+
resolvedBy: "session-file",
|
|
485
|
+
config,
|
|
486
|
+
nativeAvailable: false,
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
const nativeResult = runOpenClawSessionsJson(options.workspace);
|
|
491
|
+
let selectorMiss = null;
|
|
492
|
+
|
|
493
|
+
if (nativeResult.sessions.length > 0) {
|
|
494
|
+
const selection = chooseSessionRow(nativeResult.sessions, { ...options, allowLatestUpdated });
|
|
495
|
+
if (selection?.row) {
|
|
496
|
+
const row = selection.row;
|
|
497
|
+
return {
|
|
498
|
+
...row,
|
|
499
|
+
config,
|
|
500
|
+
contextTokens: resolveContextTokens({
|
|
501
|
+
cfg: config,
|
|
502
|
+
provider: row.modelProvider,
|
|
503
|
+
model: row.model,
|
|
504
|
+
contextTokensOverride: row.contextTokens,
|
|
505
|
+
}),
|
|
506
|
+
resolvedBy: selection.resolvedBy,
|
|
507
|
+
nativeAvailable: true,
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
if (selection?.missing) selectorMiss = selection.resolvedBy;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
const storeRows = loadSessionsFromStoreFiles(options.stateDir);
|
|
514
|
+
const storeSelection = chooseSessionRow(storeRows, { ...options, allowLatestUpdated });
|
|
515
|
+
if (storeSelection?.row) {
|
|
516
|
+
const row = storeSelection.row;
|
|
517
|
+
return {
|
|
518
|
+
...row,
|
|
519
|
+
config,
|
|
520
|
+
contextTokens: resolveContextTokens({
|
|
521
|
+
cfg: config,
|
|
522
|
+
provider: row.modelProvider,
|
|
523
|
+
model: row.model,
|
|
524
|
+
contextTokensOverride: row.contextTokens,
|
|
525
|
+
}),
|
|
526
|
+
resolvedBy: `store-${storeSelection.resolvedBy}`,
|
|
527
|
+
nativeAvailable: true,
|
|
528
|
+
};
|
|
529
|
+
}
|
|
530
|
+
if (storeSelection?.missing) selectorMiss = selectorMiss ?? storeSelection.resolvedBy;
|
|
531
|
+
|
|
532
|
+
if (explicitSessionFile) {
|
|
533
|
+
return {
|
|
534
|
+
key: options.sessionKey ?? process.env.OPENCLAW_TASK_SESSION_KEY ?? null,
|
|
535
|
+
sessionId: options.sessionId ?? process.env.OPENCLAW_TASK_SESSION_ID ?? null,
|
|
536
|
+
updatedAt: null,
|
|
537
|
+
totalTokens: null,
|
|
538
|
+
totalTokensFresh: false,
|
|
539
|
+
inputTokens: null,
|
|
540
|
+
outputTokens: null,
|
|
541
|
+
contextTokens: resolveContextTokens({ cfg: config, provider: null, model: null, contextTokensOverride: null }),
|
|
542
|
+
model: null,
|
|
543
|
+
modelProvider: null,
|
|
544
|
+
sessionFile: explicitSessionFile,
|
|
545
|
+
stateDir: options.stateDir ?? getCandidateStateDirs(options.stateDir)[0] ?? null,
|
|
546
|
+
storeFile: null,
|
|
547
|
+
agentId: parseAgentIdFromSessionKey(options.sessionKey ?? process.env.OPENCLAW_TASK_SESSION_KEY ?? null) ?? "main",
|
|
548
|
+
resolvedBy: selectorMiss ?? "session-file",
|
|
549
|
+
config,
|
|
550
|
+
nativeAvailable: false,
|
|
551
|
+
};
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
if (hasCliSessionSelector(options)) {
|
|
555
|
+
throw new Error(`未找到指定 session:${sessionSelectorValue(options)}`);
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
if (requireBoundSession) {
|
|
559
|
+
throw new Error(SESSION_REQUIRED_ERROR);
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
return {
|
|
563
|
+
key: options.sessionKey ?? process.env.OPENCLAW_TASK_SESSION_KEY ?? null,
|
|
564
|
+
sessionId: options.sessionId ?? process.env.OPENCLAW_TASK_SESSION_ID ?? null,
|
|
565
|
+
updatedAt: null,
|
|
566
|
+
totalTokens: null,
|
|
567
|
+
totalTokensFresh: false,
|
|
568
|
+
inputTokens: null,
|
|
569
|
+
outputTokens: null,
|
|
570
|
+
contextTokens: resolveContextTokens({ cfg: config, provider: null, model: null, contextTokensOverride: null }),
|
|
571
|
+
model: null,
|
|
572
|
+
modelProvider: null,
|
|
573
|
+
sessionFile: null,
|
|
574
|
+
stateDir: options.stateDir ?? getCandidateStateDirs(options.stateDir)[0] ?? null,
|
|
575
|
+
storeFile: null,
|
|
576
|
+
agentId: parseAgentIdFromSessionKey(options.sessionKey ?? process.env.OPENCLAW_TASK_SESSION_KEY ?? null) ?? "main",
|
|
577
|
+
resolvedBy: selectorMiss ?? "none",
|
|
578
|
+
config,
|
|
579
|
+
nativeAvailable: false,
|
|
580
|
+
};
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
function normalizeText(text) {
|
|
584
|
+
return stripAnsi(String(text ?? "")).replace(/\r\n/g, "\n").trim();
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
function safeStringify(value) {
|
|
588
|
+
try {
|
|
589
|
+
return JSON.stringify(value);
|
|
590
|
+
} catch {
|
|
591
|
+
return "";
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
function extractTextParts(content, allowedTypes = new Set(["text"])) {
|
|
596
|
+
if (typeof content === "string") {
|
|
597
|
+
const normalized = normalizeText(content);
|
|
598
|
+
return normalized ? [normalized] : [];
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
if (!Array.isArray(content)) return [];
|
|
602
|
+
|
|
603
|
+
const parts = [];
|
|
604
|
+
for (const item of content) {
|
|
605
|
+
if (typeof item === "string") {
|
|
606
|
+
const normalized = normalizeText(item);
|
|
607
|
+
if (normalized) parts.push(normalized);
|
|
608
|
+
continue;
|
|
609
|
+
}
|
|
610
|
+
if (!item || typeof item !== "object") continue;
|
|
611
|
+
if (!allowedTypes.has(item.type)) continue;
|
|
612
|
+
if (item.type === "text" && typeof item.text === "string") {
|
|
613
|
+
const normalized = normalizeText(item.text);
|
|
614
|
+
if (normalized) parts.push(normalized);
|
|
615
|
+
continue;
|
|
616
|
+
}
|
|
617
|
+
if (item.type === "thinking") {
|
|
618
|
+
const thinkingText = typeof item.thinking === "string" ? item.thinking : item.text;
|
|
619
|
+
const normalized = normalizeText(thinkingText);
|
|
620
|
+
if (normalized) parts.push(normalized);
|
|
621
|
+
continue;
|
|
622
|
+
}
|
|
623
|
+
if (item.type === "toolCall") {
|
|
624
|
+
const name = normalizeText(item.name);
|
|
625
|
+
if (name) parts.push(name);
|
|
626
|
+
const serializedArguments = normalizeText(safeStringify(item.arguments));
|
|
627
|
+
if (serializedArguments) parts.push(serializedArguments);
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
return parts;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
function messageTextForEstimate(entry) {
|
|
634
|
+
const message = entry?.message;
|
|
635
|
+
if (!message || typeof message !== "object") return "";
|
|
636
|
+
|
|
637
|
+
if (message.role === "toolResult") {
|
|
638
|
+
const aggregated = normalizeText(entry?.details?.aggregated);
|
|
639
|
+
if (aggregated) return aggregated;
|
|
640
|
+
return extractTextParts(message.content).join("\n");
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
if (message.role === "assistant") {
|
|
644
|
+
return extractTextParts(message.content, new Set(["text", "thinking", "toolCall"])).join("\n");
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
return extractTextParts(message.content, new Set(["text"])).join("\n");
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
function estimateTextTokens(text) {
|
|
651
|
+
if (!text) return 0;
|
|
652
|
+
return Math.ceil(text.length / 4);
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
function messageObservedTotalTokens(entry) {
|
|
656
|
+
const message = entry?.message;
|
|
657
|
+
if (!message || message.role !== "assistant") return null;
|
|
658
|
+
return toPositiveNumber(
|
|
659
|
+
message?.usage?.totalTokens
|
|
660
|
+
?? message?.usage?.total_tokens
|
|
661
|
+
?? entry?.usage?.totalTokens
|
|
662
|
+
?? entry?.usage?.total_tokens
|
|
663
|
+
?? null,
|
|
664
|
+
);
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
export function parseTranscriptText(text) {
|
|
668
|
+
const items = [];
|
|
669
|
+
const source = String(text ?? "").trim();
|
|
670
|
+
if (!source) return items;
|
|
671
|
+
|
|
672
|
+
for (const line of source.split("\n")) {
|
|
673
|
+
if (!line.trim()) continue;
|
|
674
|
+
let entry;
|
|
675
|
+
try {
|
|
676
|
+
entry = JSON.parse(line);
|
|
677
|
+
} catch {
|
|
678
|
+
continue;
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
if ((entry.type !== "message" && entry.type !== "custom_message") || !entry.message) {
|
|
682
|
+
continue;
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
const textForEstimate = messageTextForEstimate(entry);
|
|
686
|
+
items.push({
|
|
687
|
+
role: entry.message.role ?? null,
|
|
688
|
+
text: textForEstimate,
|
|
689
|
+
estimatedTokens: estimateTextTokens(textForEstimate),
|
|
690
|
+
observedTotalTokens: messageObservedTotalTokens(entry),
|
|
691
|
+
});
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
return items;
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
export function summarizeTranscript(items) {
|
|
698
|
+
let latestAssistantUsageTotalTokens = null;
|
|
699
|
+
for (const item of items) {
|
|
700
|
+
if (item.role !== "assistant") continue;
|
|
701
|
+
const observedTotalTokens = toPositiveNumber(item.observedTotalTokens);
|
|
702
|
+
if (observedTotalTokens !== null) latestAssistantUsageTotalTokens = observedTotalTokens;
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
return {
|
|
706
|
+
totalTurns: items.filter((item) => SESSION_ROLES.has(item.role)).length,
|
|
707
|
+
transcriptTotalEstimate: items.reduce((sum, item) => sum + item.estimatedTokens, 0),
|
|
708
|
+
latestAssistantUsageTotalTokens,
|
|
709
|
+
};
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
export function estimateTailTokens(items, keepTurns) {
|
|
713
|
+
if (!Number.isFinite(keepTurns) || keepTurns <= 0) return 0;
|
|
714
|
+
|
|
715
|
+
let turnCount = 0;
|
|
716
|
+
let tokenCount = 0;
|
|
717
|
+
|
|
718
|
+
for (let index = items.length - 1; index >= 0; index -= 1) {
|
|
719
|
+
const item = items[index];
|
|
720
|
+
tokenCount += item.estimatedTokens;
|
|
721
|
+
if (SESSION_ROLES.has(item.role)) {
|
|
722
|
+
turnCount += 1;
|
|
723
|
+
if (turnCount >= keepTurns) break;
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
return tokenCount;
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
function loadTranscriptStats(options, snapshot) {
|
|
731
|
+
const sessionFile = resolveSessionFile(options, snapshot);
|
|
732
|
+
if (!sessionFile || !fs.existsSync(sessionFile)) {
|
|
733
|
+
if (options.requireTranscript === true) {
|
|
734
|
+
if (options.sessionFile) {
|
|
735
|
+
throw new Error(`未找到指定 transcript:${options.sessionFile}`);
|
|
736
|
+
}
|
|
737
|
+
if (snapshot?.sessionId || snapshot?.key) {
|
|
738
|
+
throw new Error("已定位当前 session,但找不到对应 transcript 文件。");
|
|
739
|
+
}
|
|
740
|
+
throw new Error("无法读取当前对话 transcript,拒绝继续。");
|
|
741
|
+
}
|
|
742
|
+
return {
|
|
743
|
+
sessionFile: null,
|
|
744
|
+
items: [],
|
|
745
|
+
totalTurns: 0,
|
|
746
|
+
transcriptTotalEstimate: 0,
|
|
747
|
+
latestAssistantUsageTotalTokens: null,
|
|
748
|
+
tailEstimate: 0,
|
|
749
|
+
};
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
let transcriptText;
|
|
753
|
+
try {
|
|
754
|
+
transcriptText = fs.readFileSync(sessionFile, "utf8");
|
|
755
|
+
} catch (error) {
|
|
756
|
+
throw new Error(`读取 transcript 失败:${error instanceof Error ? error.message : String(error)}`);
|
|
757
|
+
}
|
|
758
|
+
const items = parseTranscriptText(transcriptText);
|
|
759
|
+
const summary = summarizeTranscript(items);
|
|
760
|
+
return {
|
|
761
|
+
sessionFile,
|
|
762
|
+
items,
|
|
763
|
+
totalTurns: summary.totalTurns,
|
|
764
|
+
transcriptTotalEstimate: summary.transcriptTotalEstimate,
|
|
765
|
+
latestAssistantUsageTotalTokens: summary.latestAssistantUsageTotalTokens,
|
|
766
|
+
tailEstimate: 0,
|
|
767
|
+
};
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
function valueOrUnknown(value) {
|
|
771
|
+
return value === null || value === undefined ? "unknown" : String(value);
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
function toNonNegativeNumber(value) {
|
|
775
|
+
const parsed = typeof value === "number" ? value : Number(value);
|
|
776
|
+
if (!Number.isFinite(parsed) || parsed < 0) return null;
|
|
777
|
+
return parsed;
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
function sourceDisplayName(source) {
|
|
781
|
+
if (source === "native") return "native";
|
|
782
|
+
if (source === "assistant-usage") return "assistant_usage_fallback";
|
|
783
|
+
if (source === "transcript-estimate") return "transcript_estimate_fallback";
|
|
784
|
+
return "unknown";
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
function resolveObservedTotalTokens(snapshot, transcriptStats) {
|
|
788
|
+
const nativeTotalTokens = snapshot?.totalTokensFresh !== false ? toFiniteNumber(snapshot?.totalTokens) : null;
|
|
789
|
+
if (nativeTotalTokens !== null) {
|
|
790
|
+
return {
|
|
791
|
+
observedTotalTokens: nativeTotalTokens,
|
|
792
|
+
observedTotalSource: "native",
|
|
793
|
+
isNative: true,
|
|
794
|
+
};
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
const latestAssistantUsageTotalTokens = toPositiveNumber(transcriptStats?.latestAssistantUsageTotalTokens);
|
|
798
|
+
if (latestAssistantUsageTotalTokens !== null) {
|
|
799
|
+
return {
|
|
800
|
+
observedTotalTokens: latestAssistantUsageTotalTokens,
|
|
801
|
+
observedTotalSource: "assistant-usage",
|
|
802
|
+
isNative: false,
|
|
803
|
+
};
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
const transcriptTotalEstimate = transcriptStats?.transcriptTotalEstimate > 0
|
|
807
|
+
? transcriptStats.transcriptTotalEstimate
|
|
808
|
+
: null;
|
|
809
|
+
return {
|
|
810
|
+
observedTotalTokens: transcriptTotalEstimate,
|
|
811
|
+
observedTotalSource: "transcript-estimate",
|
|
812
|
+
isNative: false,
|
|
813
|
+
};
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
export function buildInfoResult(snapshot, transcriptStats) {
|
|
817
|
+
const observedTotal = resolveObservedTotalTokens(snapshot, transcriptStats);
|
|
818
|
+
const totalTokens = observedTotal.observedTotalTokens;
|
|
819
|
+
const contextTokens = toPositiveNumber(snapshot?.contextTokens);
|
|
820
|
+
const usedPct = totalTokens !== null && contextTokens ? Math.round(totalTokens / contextTokens * 100) : null;
|
|
821
|
+
const source = observedTotal.observedTotalSource;
|
|
822
|
+
const scaleRatio = observedTotal.observedTotalTokens !== null
|
|
823
|
+
&& transcriptStats?.transcriptTotalEstimate
|
|
824
|
+
&& observedTotal.observedTotalSource !== "transcript-estimate"
|
|
825
|
+
? observedTotal.observedTotalTokens / transcriptStats.transcriptTotalEstimate
|
|
826
|
+
: null;
|
|
827
|
+
|
|
828
|
+
return {
|
|
829
|
+
turns: transcriptStats?.totalTurns ?? 0,
|
|
830
|
+
totalTokens,
|
|
831
|
+
contextTokens,
|
|
832
|
+
usedPct,
|
|
833
|
+
source,
|
|
834
|
+
sourceLabel: sourceDisplayName(source),
|
|
835
|
+
resolvedBy: snapshot?.resolvedBy ?? "none",
|
|
836
|
+
nativeTotalTokens: observedTotal.isNative ? observedTotal.observedTotalTokens : null,
|
|
837
|
+
observedTotalSource: observedTotal.observedTotalSource,
|
|
838
|
+
transcriptTotalEstimate: transcriptStats?.transcriptTotalEstimate ?? 0,
|
|
839
|
+
scaleRatio,
|
|
840
|
+
estimationMode: observedTotal.observedTotalSource,
|
|
841
|
+
messageEstimateBefore: transcriptStats?.transcriptTotalEstimate ?? 0,
|
|
842
|
+
messageEstimateAfter: null,
|
|
843
|
+
displayLabel: DISPLAY_LABEL,
|
|
844
|
+
promptTruthSource: PROMPT_TRUTH_SOURCE,
|
|
845
|
+
};
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
export function calculateTurnKeepTokens({
|
|
849
|
+
requestedTurns,
|
|
850
|
+
transcriptStats,
|
|
851
|
+
observedTotalTokens,
|
|
852
|
+
observedTotalSource,
|
|
853
|
+
}) {
|
|
854
|
+
const requestedKeepTurns = Math.max(0, requestedTurns);
|
|
855
|
+
const allowanceAdjustedTurns = requestedKeepTurns + INTERACTION_TURN_ALLOWANCE;
|
|
856
|
+
const totalTurns = transcriptStats.totalTurns || requestedKeepTurns;
|
|
857
|
+
const effectiveKeepTurns = Math.max(0, Math.min(allowanceAdjustedTurns, totalTurns));
|
|
858
|
+
const tailEstimate = estimateTailTokens(transcriptStats.items, effectiveKeepTurns);
|
|
859
|
+
const transcriptTotalEstimate = transcriptStats.transcriptTotalEstimate;
|
|
860
|
+
|
|
861
|
+
let baseKeepTokens = tailEstimate;
|
|
862
|
+
let source = "estimated";
|
|
863
|
+
let scaleRatio = null;
|
|
864
|
+
const displayTotalTurns = totalTurns > INTERACTION_TURN_ALLOWANCE
|
|
865
|
+
? totalTurns - INTERACTION_TURN_ALLOWANCE
|
|
866
|
+
: totalTurns;
|
|
867
|
+
const displayKeepTurns = Math.max(0, Math.min(requestedKeepTurns, displayTotalTurns));
|
|
868
|
+
const displayCompressTurns = Math.max(0, displayTotalTurns - displayKeepTurns);
|
|
869
|
+
|
|
870
|
+
if (observedTotalTokens !== null && transcriptTotalEstimate > 0 && observedTotalSource !== "transcript-estimate") {
|
|
871
|
+
scaleRatio = observedTotalTokens / transcriptTotalEstimate;
|
|
872
|
+
baseKeepTokens = Math.round(tailEstimate * scaleRatio);
|
|
873
|
+
source = "calibrated";
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
const keepTokens = Math.max(
|
|
877
|
+
baseKeepTokens,
|
|
878
|
+
Math.ceil(baseKeepTokens * TURN_KEEP_TOKENS_SAFETY_RATIO),
|
|
879
|
+
baseKeepTokens + TURN_KEEP_TOKENS_SAFETY_BUFFER,
|
|
880
|
+
);
|
|
881
|
+
|
|
882
|
+
return {
|
|
883
|
+
requestedTurns: requestedKeepTurns,
|
|
884
|
+
effectiveKeepTurns,
|
|
885
|
+
compressTurns: Math.max(0, totalTurns - effectiveKeepTurns),
|
|
886
|
+
displayKeepTurns,
|
|
887
|
+
displayCompressTurns,
|
|
888
|
+
keepTokens: Math.max(0, keepTokens),
|
|
889
|
+
baseKeepTokens,
|
|
890
|
+
tailEstimate,
|
|
891
|
+
transcriptTotalEstimate,
|
|
892
|
+
scaleRatio,
|
|
893
|
+
source,
|
|
894
|
+
};
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
function calculatePctKeepTokens({
|
|
898
|
+
keepPct,
|
|
899
|
+
transcriptStats,
|
|
900
|
+
observedTotalTokens,
|
|
901
|
+
observedTotalSource,
|
|
902
|
+
}) {
|
|
903
|
+
const baseTotal = observedTotalTokens;
|
|
904
|
+
if (baseTotal === null || baseTotal === 0) {
|
|
905
|
+
throw new Error("无法根据百分比计算 keepRecentTokens:缺少可用的总 token 数据。");
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
return {
|
|
909
|
+
keepTokens: Math.ceil(baseTotal * keepPct / 100),
|
|
910
|
+
source: observedTotalSource === "native" ? "native_pct" : "fallback_pct",
|
|
911
|
+
scaleRatio: observedTotalTokens !== null
|
|
912
|
+
&& transcriptStats.transcriptTotalEstimate > 0
|
|
913
|
+
&& observedTotalSource !== "transcript-estimate"
|
|
914
|
+
? observedTotalTokens / transcriptStats.transcriptTotalEstimate
|
|
915
|
+
: null,
|
|
916
|
+
transcriptTotalEstimate: transcriptStats.transcriptTotalEstimate,
|
|
917
|
+
};
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
function readSettings(settingsFile) {
|
|
921
|
+
return readJsonFile(settingsFile) ?? {};
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
function writeSettings(settingsFile, data) {
|
|
925
|
+
fs.mkdirSync(path.dirname(settingsFile), { recursive: true });
|
|
926
|
+
fs.writeFileSync(settingsFile, JSON.stringify(data, null, 2));
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
function sessionDetailsFromRecord(record = {}) {
|
|
930
|
+
return {
|
|
931
|
+
sessionKey: record?.sessionKey ?? record?.key ?? null,
|
|
932
|
+
sessionId: record?.sessionId ?? null,
|
|
933
|
+
};
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
function sessionMatches(expected = {}, actual = {}) {
|
|
937
|
+
const expectedSessionId = expected?.sessionId ?? null;
|
|
938
|
+
const expectedSessionKey = expected?.sessionKey ?? null;
|
|
939
|
+
const actualSessionId = actual?.sessionId ?? null;
|
|
940
|
+
const actualSessionKey = actual?.sessionKey ?? null;
|
|
941
|
+
|
|
942
|
+
let compared = false;
|
|
943
|
+
|
|
944
|
+
if (expectedSessionId && actualSessionId) {
|
|
945
|
+
compared = true;
|
|
946
|
+
if (expectedSessionId !== actualSessionId) return false;
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
if (expectedSessionKey && actualSessionKey) {
|
|
950
|
+
compared = true;
|
|
951
|
+
if (expectedSessionKey !== actualSessionKey) return false;
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
if (compared) return true;
|
|
955
|
+
if (expectedSessionId || expectedSessionKey) return false;
|
|
956
|
+
return true;
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
function buildAuditSnapshot(metadata = {}, appliedKeepRecentTokens = null) {
|
|
960
|
+
const totalTokensBefore = toFiniteNumber(metadata.totalTokensBefore);
|
|
961
|
+
const contextTokensBefore = toPositiveNumber(metadata.contextTokensBefore);
|
|
962
|
+
const usedPctBefore = totalTokensBefore !== null && contextTokensBefore
|
|
963
|
+
? Math.round(totalTokensBefore / contextTokensBefore * 100)
|
|
964
|
+
: null;
|
|
965
|
+
|
|
966
|
+
return {
|
|
967
|
+
version: BACKUP_VERSION,
|
|
968
|
+
sessionKey: metadata.sessionKey ?? null,
|
|
969
|
+
sessionId: metadata.sessionId ?? null,
|
|
970
|
+
resolvedBy: metadata.resolvedBy ?? null,
|
|
971
|
+
source: metadata.source ?? null,
|
|
972
|
+
requestedTurns: metadata.requestedTurns ?? null,
|
|
973
|
+
effectiveKeepTurns: metadata.effectiveKeepTurns ?? null,
|
|
974
|
+
displayKeepTurns: metadata.displayKeepTurns ?? null,
|
|
975
|
+
displayCompressTurns: metadata.displayCompressTurns ?? null,
|
|
976
|
+
keepRecentTokens: appliedKeepRecentTokens,
|
|
977
|
+
totalTokensBefore,
|
|
978
|
+
contextTokensBefore,
|
|
979
|
+
usedPctBefore,
|
|
980
|
+
transcriptTotalEstimateBefore: toFiniteNumber(metadata.transcriptTotalEstimateBefore),
|
|
981
|
+
nativeTotalTokensBefore: toFiniteNumber(metadata.nativeTotalTokensBefore),
|
|
982
|
+
messageEstimateBefore: toNonNegativeNumber(metadata.messageEstimateBefore),
|
|
983
|
+
messageEstimateAfter: toNonNegativeNumber(metadata.messageEstimateAfter),
|
|
984
|
+
displayLabel: metadata.displayLabel ?? DISPLAY_LABEL,
|
|
985
|
+
promptTruthSource: metadata.promptTruthSource ?? PROMPT_TRUTH_SOURCE,
|
|
986
|
+
createdAt: metadata.createdAt ?? new Date().toISOString(),
|
|
987
|
+
};
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
function createBackup(currentKeepRecentTokens, metadata, appliedKeepRecentTokens) {
|
|
991
|
+
const audit = buildAuditSnapshot(metadata, appliedKeepRecentTokens);
|
|
992
|
+
return {
|
|
993
|
+
version: BACKUP_VERSION,
|
|
994
|
+
previousKeepRecentTokens: typeof currentKeepRecentTokens === "number" ? currentKeepRecentTokens : null,
|
|
995
|
+
appliedKeepRecentTokens,
|
|
996
|
+
requestedTurns: audit.requestedTurns,
|
|
997
|
+
effectiveKeepTurns: audit.effectiveKeepTurns,
|
|
998
|
+
sessionKey: audit.sessionKey,
|
|
999
|
+
sessionId: audit.sessionId,
|
|
1000
|
+
source: audit.source,
|
|
1001
|
+
audit,
|
|
1002
|
+
updatedAt: new Date().toISOString(),
|
|
1003
|
+
};
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
export function applyKeepRecentTokensSetting(settingsFile, targetKeepTokens, metadata = {}) {
|
|
1007
|
+
const current = readSettings(settingsFile);
|
|
1008
|
+
const compaction = { ...(current.compaction ?? {}) };
|
|
1009
|
+
const existingBackup = compaction[BACKUP_KEY];
|
|
1010
|
+
const currentKeepRecentTokens = typeof compaction.keepRecentTokens === "number" ? compaction.keepRecentTokens : null;
|
|
1011
|
+
const audit = buildAuditSnapshot(metadata, targetKeepTokens);
|
|
1012
|
+
|
|
1013
|
+
const canReuseBackup = Boolean(
|
|
1014
|
+
existingBackup
|
|
1015
|
+
&& typeof existingBackup === "object"
|
|
1016
|
+
&& currentKeepRecentTokens === existingBackup.appliedKeepRecentTokens
|
|
1017
|
+
);
|
|
1018
|
+
|
|
1019
|
+
compaction[BACKUP_KEY] = canReuseBackup
|
|
1020
|
+
? {
|
|
1021
|
+
...existingBackup,
|
|
1022
|
+
appliedKeepRecentTokens: targetKeepTokens,
|
|
1023
|
+
requestedTurns: metadata.requestedTurns ?? existingBackup.requestedTurns ?? null,
|
|
1024
|
+
effectiveKeepTurns: metadata.effectiveKeepTurns ?? existingBackup.effectiveKeepTurns ?? null,
|
|
1025
|
+
sessionKey: metadata.sessionKey ?? existingBackup.sessionKey ?? null,
|
|
1026
|
+
sessionId: metadata.sessionId ?? existingBackup.sessionId ?? null,
|
|
1027
|
+
source: metadata.source ?? existingBackup.source ?? null,
|
|
1028
|
+
audit,
|
|
1029
|
+
updatedAt: new Date().toISOString(),
|
|
1030
|
+
}
|
|
1031
|
+
: createBackup(currentKeepRecentTokens, metadata, targetKeepTokens);
|
|
1032
|
+
|
|
1033
|
+
compaction.keepRecentTokens = targetKeepTokens;
|
|
1034
|
+
compaction[LAST_RUN_KEY] = audit;
|
|
1035
|
+
current.compaction = compaction;
|
|
1036
|
+
writeSettings(settingsFile, current);
|
|
1037
|
+
|
|
1038
|
+
return {
|
|
1039
|
+
backup: compaction[BACKUP_KEY],
|
|
1040
|
+
appliedKeepRecentTokens: targetKeepTokens,
|
|
1041
|
+
reusedBackup: canReuseBackup,
|
|
1042
|
+
};
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
function recordLastRestore(compaction, restoreResult) {
|
|
1046
|
+
compaction[LAST_RESTORE_KEY] = {
|
|
1047
|
+
version: BACKUP_VERSION,
|
|
1048
|
+
sessionKey: restoreResult.sessionKey ?? null,
|
|
1049
|
+
sessionId: restoreResult.sessionId ?? null,
|
|
1050
|
+
restoreStatus: restoreResult.status,
|
|
1051
|
+
restoredKeepRecentTokens: restoreResult.restoredValue ?? null,
|
|
1052
|
+
restoredAt: new Date().toISOString(),
|
|
1053
|
+
};
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
export function restoreKeepRecentTokensSetting(settingsFile, expectedSession = null) {
|
|
1057
|
+
if (!fs.existsSync(settingsFile)) {
|
|
1058
|
+
return { status: "no_backup", restoredValue: null };
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
const current = readSettings(settingsFile);
|
|
1062
|
+
const compaction = current.compaction ?? {};
|
|
1063
|
+
const backup = compaction[BACKUP_KEY];
|
|
1064
|
+
|
|
1065
|
+
if (!backup || typeof backup !== "object") {
|
|
1066
|
+
return { status: "no_backup", restoredValue: null };
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
if (expectedSession && !sessionMatches(expectedSession, sessionDetailsFromRecord(backup))) {
|
|
1070
|
+
recordLastRestore(compaction, {
|
|
1071
|
+
status: "session_mismatch",
|
|
1072
|
+
restoredValue: current.compaction?.keepRecentTokens ?? null,
|
|
1073
|
+
...sessionDetailsFromRecord(backup),
|
|
1074
|
+
});
|
|
1075
|
+
current.compaction = compaction;
|
|
1076
|
+
writeSettings(settingsFile, current);
|
|
1077
|
+
return { status: "session_mismatch", restoredValue: current.compaction?.keepRecentTokens ?? null };
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
const currentKeepRecentTokens = typeof compaction.keepRecentTokens === "number" ? compaction.keepRecentTokens : null;
|
|
1081
|
+
if (currentKeepRecentTokens !== backup.appliedKeepRecentTokens) {
|
|
1082
|
+
recordLastRestore(compaction, {
|
|
1083
|
+
status: "manual_change",
|
|
1084
|
+
restoredValue: currentKeepRecentTokens,
|
|
1085
|
+
...sessionDetailsFromRecord(backup),
|
|
1086
|
+
});
|
|
1087
|
+
current.compaction = compaction;
|
|
1088
|
+
writeSettings(settingsFile, current);
|
|
1089
|
+
return { status: "manual_change", restoredValue: currentKeepRecentTokens };
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
if (typeof backup.previousKeepRecentTokens === "number") {
|
|
1093
|
+
compaction.keepRecentTokens = backup.previousKeepRecentTokens;
|
|
1094
|
+
} else {
|
|
1095
|
+
delete compaction.keepRecentTokens;
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
delete compaction[BACKUP_KEY];
|
|
1099
|
+
recordLastRestore(compaction, {
|
|
1100
|
+
status: "restored",
|
|
1101
|
+
restoredValue: typeof backup.previousKeepRecentTokens === "number" ? backup.previousKeepRecentTokens : null,
|
|
1102
|
+
...sessionDetailsFromRecord(backup),
|
|
1103
|
+
});
|
|
1104
|
+
current.compaction = compaction;
|
|
1105
|
+
writeSettings(settingsFile, current);
|
|
1106
|
+
return {
|
|
1107
|
+
status: "restored",
|
|
1108
|
+
restoredValue: typeof backup.previousKeepRecentTokens === "number" ? backup.previousKeepRecentTokens : null,
|
|
1109
|
+
};
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
function validateOptions(options) {
|
|
1113
|
+
const modes = [
|
|
1114
|
+
options.infoMode,
|
|
1115
|
+
options.restore,
|
|
1116
|
+
options.keepTurns !== null,
|
|
1117
|
+
options.keepPct !== null,
|
|
1118
|
+
options.keepTokens !== null,
|
|
1119
|
+
].filter(Boolean).length;
|
|
1120
|
+
|
|
1121
|
+
if (modes === 0) {
|
|
1122
|
+
throw new Error(
|
|
1123
|
+
"Usage:\n" +
|
|
1124
|
+
" set-keep-recent.mjs --turns N [--workspace <path>] [--session <path>]\n" +
|
|
1125
|
+
" set-keep-recent.mjs --pct N [--workspace <path>]\n" +
|
|
1126
|
+
" set-keep-recent.mjs --tokens N [--workspace <path>]\n" +
|
|
1127
|
+
" set-keep-recent.mjs --restore [--workspace <path>]\n" +
|
|
1128
|
+
" set-keep-recent.mjs --info [--workspace <path>]\n" +
|
|
1129
|
+
" Optional selectors: --session-key <key> --session-id <id> --state-dir <dir> --debug\n" +
|
|
1130
|
+
" Safety flags: --require-current-session --allow-latest-updated",
|
|
1131
|
+
);
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
if (modes > 1) {
|
|
1135
|
+
throw new Error("一次只能使用一种模式:--info、--restore、--turns、--pct、--tokens 互斥。");
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
if (options.keepTurns !== null && (!Number.isInteger(options.keepTurns) || options.keepTurns < 0)) {
|
|
1139
|
+
throw new Error("--turns 必须是大于等于 0 的整数。");
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
if (options.keepPct !== null && (!Number.isFinite(options.keepPct) || options.keepPct < 0 || options.keepPct > 100)) {
|
|
1143
|
+
throw new Error("--pct 必须在 0 到 100 之间。");
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
if (options.keepTokens !== null && (!Number.isInteger(options.keepTokens) || options.keepTokens < 0)) {
|
|
1147
|
+
throw new Error("--tokens 必须是大于等于 0 的整数。");
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
export function run(options) {
|
|
1152
|
+
validateOptions(options);
|
|
1153
|
+
|
|
1154
|
+
const settingsFile = path.join(options.workspace, ".pi", "settings.json");
|
|
1155
|
+
const snapshot = resolveSessionSnapshot(options);
|
|
1156
|
+
const transcriptStats = loadTranscriptStats({
|
|
1157
|
+
...options,
|
|
1158
|
+
requireTranscript: options.infoMode || options.keepTurns !== null || options.keepPct !== null,
|
|
1159
|
+
}, snapshot);
|
|
1160
|
+
const observedTotal = resolveObservedTotalTokens(snapshot, transcriptStats);
|
|
1161
|
+
const nativeTotalTokens = observedTotal.isNative ? observedTotal.observedTotalTokens : null;
|
|
1162
|
+
const settings = readSettings(settingsFile);
|
|
1163
|
+
const lastRun = settings?.compaction?.[LAST_RUN_KEY] ?? null;
|
|
1164
|
+
const snapshotStatus = !lastRun
|
|
1165
|
+
? "none"
|
|
1166
|
+
: sessionMatches(sessionDetailsFromRecord(snapshot), sessionDetailsFromRecord(lastRun))
|
|
1167
|
+
? "matches-current-session"
|
|
1168
|
+
: "session-mismatch";
|
|
1169
|
+
const baseDebug = {
|
|
1170
|
+
resolved_session_key: snapshot.key,
|
|
1171
|
+
resolved_session_id: snapshot.sessionId,
|
|
1172
|
+
resolved_by: snapshot.resolvedBy,
|
|
1173
|
+
resolved_session_file: transcriptStats.sessionFile,
|
|
1174
|
+
source: observedTotal.observedTotalSource,
|
|
1175
|
+
native_total_tokens: nativeTotalTokens,
|
|
1176
|
+
native_context_tokens: snapshot.contextTokens,
|
|
1177
|
+
transcript_total_estimate: transcriptStats.transcriptTotalEstimate,
|
|
1178
|
+
assistant_usage_total_tokens: transcriptStats.latestAssistantUsageTotalTokens,
|
|
1179
|
+
observed_total_tokens: observedTotal.observedTotalTokens,
|
|
1180
|
+
observed_total_source: observedTotal.observedTotalSource,
|
|
1181
|
+
snapshot_status: snapshotStatus,
|
|
1182
|
+
message_estimate_before: transcriptStats.transcriptTotalEstimate,
|
|
1183
|
+
prompt_truth_source: PROMPT_TRUTH_SOURCE,
|
|
1184
|
+
};
|
|
1185
|
+
|
|
1186
|
+
if (options.infoMode) {
|
|
1187
|
+
const info = buildInfoResult(snapshot, transcriptStats);
|
|
1188
|
+
console.log(
|
|
1189
|
+
`turns=${info.turns} total_tokens=${valueOrUnknown(info.totalTokens)} context_tokens=${valueOrUnknown(info.contextTokens)} used_pct=${valueOrUnknown(info.usedPct)} source=${info.source} estimation_mode=${info.estimationMode} source_label=${info.sourceLabel} message_estimate_before=${valueOrUnknown(info.messageEstimateBefore)} message_estimate_after=${valueOrUnknown(info.messageEstimateAfter)} display_label=${info.displayLabel} prompt_truth_source=${info.promptTruthSource} resolved_by=${info.resolvedBy} snapshot_status=${snapshotStatus}`,
|
|
1190
|
+
);
|
|
1191
|
+
printDebug(options.debug, {
|
|
1192
|
+
...baseDebug,
|
|
1193
|
+
source: info.source,
|
|
1194
|
+
scale_ratio: info.scaleRatio,
|
|
1195
|
+
});
|
|
1196
|
+
return 0;
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
if (options.restore) {
|
|
1200
|
+
const restoreResult = restoreKeepRecentTokensSetting(settingsFile, sessionDetailsFromRecord(snapshot));
|
|
1201
|
+
const statusLabel = restoreResult.status === "restored"
|
|
1202
|
+
? "✔ 已还原"
|
|
1203
|
+
: restoreResult.status === "session_mismatch"
|
|
1204
|
+
? "✘ 当前 session 与备份不一致,拒绝还原"
|
|
1205
|
+
: restoreResult.status === "manual_change"
|
|
1206
|
+
? "✔ 检测到手动修改,跳过还原"
|
|
1207
|
+
: "✔ 无需还原";
|
|
1208
|
+
console.log(`${statusLabel} restore_status=${restoreResult.status} snapshot_status=${snapshotStatus}`);
|
|
1209
|
+
printDebug(options.debug, {
|
|
1210
|
+
...baseDebug,
|
|
1211
|
+
restore_status: restoreResult.status,
|
|
1212
|
+
restored_value: restoreResult.restoredValue,
|
|
1213
|
+
settings_file: settingsFile,
|
|
1214
|
+
});
|
|
1215
|
+
return 0;
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
if (options.keepTokens !== null) {
|
|
1219
|
+
const applyResult = applyKeepRecentTokensSetting(settingsFile, options.keepTokens, {
|
|
1220
|
+
requestedTurns: null,
|
|
1221
|
+
effectiveKeepTurns: null,
|
|
1222
|
+
displayKeepTurns: null,
|
|
1223
|
+
displayCompressTurns: null,
|
|
1224
|
+
sessionKey: snapshot.key,
|
|
1225
|
+
sessionId: snapshot.sessionId,
|
|
1226
|
+
source: "explicit",
|
|
1227
|
+
resolvedBy: snapshot.resolvedBy,
|
|
1228
|
+
totalTokensBefore: observedTotal.observedTotalTokens,
|
|
1229
|
+
contextTokensBefore: snapshot.contextTokens,
|
|
1230
|
+
transcriptTotalEstimateBefore: transcriptStats.transcriptTotalEstimate,
|
|
1231
|
+
nativeTotalTokensBefore: nativeTotalTokens,
|
|
1232
|
+
messageEstimateBefore: transcriptStats.transcriptTotalEstimate,
|
|
1233
|
+
messageEstimateAfter: null,
|
|
1234
|
+
displayLabel: DISPLAY_LABEL,
|
|
1235
|
+
promptTruthSource: PROMPT_TRUTH_SOURCE,
|
|
1236
|
+
});
|
|
1237
|
+
console.log(`keep_tokens=${options.keepTokens} source=explicit estimation_mode=explicit message_estimate_before=${valueOrUnknown(transcriptStats.transcriptTotalEstimate)} message_estimate_after=unknown display_label=${DISPLAY_LABEL} prompt_truth_source=${PROMPT_TRUTH_SOURCE} snapshot_status=${applyResult.reusedBackup ? "updated" : "created"}`);
|
|
1238
|
+
printDebug(options.debug, {
|
|
1239
|
+
...baseDebug,
|
|
1240
|
+
scale_ratio: null,
|
|
1241
|
+
settings_file: settingsFile,
|
|
1242
|
+
});
|
|
1243
|
+
return 0;
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
if (options.keepPct !== null) {
|
|
1247
|
+
const pctResult = calculatePctKeepTokens({
|
|
1248
|
+
keepPct: options.keepPct,
|
|
1249
|
+
transcriptStats,
|
|
1250
|
+
observedTotalTokens: observedTotal.observedTotalTokens,
|
|
1251
|
+
observedTotalSource: observedTotal.observedTotalSource,
|
|
1252
|
+
});
|
|
1253
|
+
const applyResult = applyKeepRecentTokensSetting(settingsFile, pctResult.keepTokens, {
|
|
1254
|
+
requestedTurns: null,
|
|
1255
|
+
effectiveKeepTurns: null,
|
|
1256
|
+
displayKeepTurns: null,
|
|
1257
|
+
displayCompressTurns: null,
|
|
1258
|
+
sessionKey: snapshot.key,
|
|
1259
|
+
sessionId: snapshot.sessionId,
|
|
1260
|
+
source: observedTotal.observedTotalSource,
|
|
1261
|
+
resolvedBy: snapshot.resolvedBy,
|
|
1262
|
+
totalTokensBefore: observedTotal.observedTotalTokens,
|
|
1263
|
+
contextTokensBefore: snapshot.contextTokens,
|
|
1264
|
+
transcriptTotalEstimateBefore: transcriptStats.transcriptTotalEstimate,
|
|
1265
|
+
nativeTotalTokensBefore: nativeTotalTokens,
|
|
1266
|
+
messageEstimateBefore: transcriptStats.transcriptTotalEstimate,
|
|
1267
|
+
messageEstimateAfter: null,
|
|
1268
|
+
displayLabel: DISPLAY_LABEL,
|
|
1269
|
+
promptTruthSource: PROMPT_TRUTH_SOURCE,
|
|
1270
|
+
});
|
|
1271
|
+
console.log(`keep_tokens=${pctResult.keepTokens} source=${observedTotal.observedTotalSource} estimation_mode=${pctResult.source} message_estimate_before=${valueOrUnknown(transcriptStats.transcriptTotalEstimate)} message_estimate_after=unknown display_label=${DISPLAY_LABEL} prompt_truth_source=${PROMPT_TRUTH_SOURCE} snapshot_status=${applyResult.reusedBackup ? "updated" : "created"}`);
|
|
1272
|
+
printDebug(options.debug, {
|
|
1273
|
+
...baseDebug,
|
|
1274
|
+
scale_ratio: pctResult.scaleRatio,
|
|
1275
|
+
settings_file: settingsFile,
|
|
1276
|
+
});
|
|
1277
|
+
return 0;
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
const turnResult = calculateTurnKeepTokens({
|
|
1281
|
+
requestedTurns: options.keepTurns,
|
|
1282
|
+
transcriptStats,
|
|
1283
|
+
observedTotalTokens: observedTotal.observedTotalTokens,
|
|
1284
|
+
observedTotalSource: observedTotal.observedTotalSource,
|
|
1285
|
+
});
|
|
1286
|
+
|
|
1287
|
+
const applyResult = applyKeepRecentTokensSetting(settingsFile, turnResult.keepTokens, {
|
|
1288
|
+
requestedTurns: turnResult.requestedTurns,
|
|
1289
|
+
effectiveKeepTurns: turnResult.effectiveKeepTurns,
|
|
1290
|
+
displayKeepTurns: turnResult.displayKeepTurns,
|
|
1291
|
+
displayCompressTurns: turnResult.displayCompressTurns,
|
|
1292
|
+
sessionKey: snapshot.key,
|
|
1293
|
+
sessionId: snapshot.sessionId,
|
|
1294
|
+
source: observedTotal.observedTotalSource,
|
|
1295
|
+
resolvedBy: snapshot.resolvedBy,
|
|
1296
|
+
totalTokensBefore: observedTotal.observedTotalTokens,
|
|
1297
|
+
contextTokensBefore: snapshot.contextTokens,
|
|
1298
|
+
transcriptTotalEstimateBefore: transcriptStats.transcriptTotalEstimate,
|
|
1299
|
+
nativeTotalTokensBefore: nativeTotalTokens,
|
|
1300
|
+
messageEstimateBefore: transcriptStats.transcriptTotalEstimate,
|
|
1301
|
+
messageEstimateAfter: turnResult.baseKeepTokens,
|
|
1302
|
+
displayLabel: DISPLAY_LABEL,
|
|
1303
|
+
promptTruthSource: PROMPT_TRUTH_SOURCE,
|
|
1304
|
+
});
|
|
1305
|
+
|
|
1306
|
+
console.log(
|
|
1307
|
+
`keep_turns=${turnResult.displayKeepTurns} compress_turns=${turnResult.displayCompressTurns} display_keep_turns=${turnResult.displayKeepTurns} display_compress_turns=${turnResult.displayCompressTurns} keep_tokens=${turnResult.keepTokens} source=${observedTotal.observedTotalSource} estimation_mode=${turnResult.source} message_estimate_before=${valueOrUnknown(transcriptStats.transcriptTotalEstimate)} message_estimate_after=${valueOrUnknown(turnResult.baseKeepTokens)} display_label=${DISPLAY_LABEL} prompt_truth_source=${PROMPT_TRUTH_SOURCE} snapshot_status=${applyResult.reusedBackup ? "updated" : "created"}`,
|
|
1308
|
+
);
|
|
1309
|
+
printDebug(options.debug, {
|
|
1310
|
+
...baseDebug,
|
|
1311
|
+
requested_turns: turnResult.requestedTurns,
|
|
1312
|
+
effective_keep_turns: turnResult.effectiveKeepTurns,
|
|
1313
|
+
tail_estimate: turnResult.tailEstimate,
|
|
1314
|
+
message_estimate_after: turnResult.baseKeepTokens,
|
|
1315
|
+
scale_ratio: turnResult.scaleRatio,
|
|
1316
|
+
settings_file: settingsFile,
|
|
1317
|
+
});
|
|
1318
|
+
return 0;
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
function main() {
|
|
1322
|
+
const options = parseArgs(process.argv.slice(2));
|
|
1323
|
+
try {
|
|
1324
|
+
const exitCode = run(options);
|
|
1325
|
+
process.exit(exitCode);
|
|
1326
|
+
} catch (error) {
|
|
1327
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
1328
|
+
process.exit(1);
|
|
1329
|
+
}
|
|
1330
|
+
}
|
|
1331
|
+
|
|
1332
|
+
const isDirectRun = process.argv[1]
|
|
1333
|
+
&& path.resolve(process.argv[1]) === fileURLToPath(import.meta.url);
|
|
1334
|
+
|
|
1335
|
+
if (isDirectRun) {
|
|
1336
|
+
main();
|
|
1337
|
+
}
|