luckytri 1.0.3 → 1.0.4
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/bin/luckytri.js +5 -1
- package/docs/en/CHANGELOG.md +7 -0
- package/docs/en/guide.md +3 -1
- package/docs/en/webui.md +3 -1
- package/docs/zh/CHANGELOG.md +7 -0
- package/docs/zh/guide.md +3 -1
- package/docs/zh/webui.md +3 -1
- package/package.json +4 -3
- package/public/app/assets/{ChatsPage-B83Evc79.js → ChatsPage-CshdtTzV.js} +1 -1
- package/public/app/assets/{Empty-ddEfu0w8.js → Empty-C7yzOBzy.js} +1 -1
- package/public/app/assets/{Field-mIhV5YBA.js → Field-BjFv4Rfn.js} +1 -1
- package/public/app/assets/{HeartPage-BGEn24rc.js → HeartPage-eqo6ru16.js} +1 -1
- package/public/app/assets/{LifePage-BleqPj4O.js → LifePage-CN8GE5NK.js} +1 -1
- package/public/app/assets/{MemoryPage-BM3-0_lF.js → MemoryPage-CCpMN4Wg.js} +1 -1
- package/public/app/assets/{NaturePage-CxxMnQIc.js → NaturePage-C0JqdBTR.js} +2 -2
- package/public/app/assets/{PeoplePage-DDDHESQp.css → PeoplePage-BTU0H1pM.css} +1 -1
- package/public/app/assets/PeoplePage-BXGBbO4u.js +1 -0
- package/public/app/assets/{PluginsPage-DE_-Q3ra.js → PluginsPage-Dksp5AV5.js} +1 -1
- package/public/app/assets/{SystemPage-Be1a6UDP.css → SystemPage-BenIyTvk.css} +1 -1
- package/public/app/assets/{SystemPage-BWQKEQRU.js → SystemPage-Cd7aa8eE.js} +1 -1
- package/public/app/assets/{TimePage-Dzt8t8kL.js → TimePage-Bs_zImb4.js} +1 -1
- package/public/app/assets/{index-DY4e15Kh.css → index-BHZcHUMh.css} +1 -1
- package/public/app/assets/{index-oLIXWxsn.js → index-CyRtYxP3.js} +3 -3
- package/public/app/assets/{knowledge-Dvtmpd45.js → knowledge-BJOIyuJo.js} +1 -1
- package/public/app/assets/{media-keE52cIX.js → media-Bwtx8blz.js} +1 -1
- package/public/app/assets/mind-DtTVrW6Z.js +1 -0
- package/public/app/index.html +4 -4
- package/public/guide.en.html +23 -8
- package/public/guide.html +11 -5
- package/public/tutorial.en.md +3 -1
- package/public/tutorial.md +3 -1
- package/scripts/launch.js +11 -4
- package/scripts/service.js +57 -7
- package/scripts/start-server.js +4 -1
- package/scripts/update-instance.js +201 -0
- package/server/app.js +1 -1
- package/server/core/context-builder.js +2 -2
- package/server/core/context-compactor.js +2 -2
- package/server/core/orchestrator.js +1 -0
- package/server/core/persona-manager.js +10 -0
- package/server/core/reply-delivery.js +10 -0
- package/server/core/response-validator.js +20 -0
- package/server/i18n/errors.js +25 -0
- package/server/index.js +45 -2
- package/server/mind/api.js +25 -0
- package/server/mind/bonds.js +9 -2
- package/server/mind/index.js +2 -0
- package/server/mind/initiative.js +6 -1
- package/server/mind/life/context.js +14 -3
- package/server/mind/life/growth.js +2 -1
- package/server/mind/nature.js +23 -2
- package/server/mind/relationships.js +279 -0
- package/server/mind/schema.js +2 -0
- package/server/mind/time/tasks.js +2 -1
- package/server/mind/view.js +2 -0
- package/server/runtime/installation.js +116 -0
- package/server/runtime/update-execution.js +127 -0
- package/server/runtime/update-manager.js +140 -0
- package/server/startup-lock.js +12 -12
- package/server/storage/archive.js +10 -2
- package/server/storage/backup-lock.js +23 -5
- package/server/storage/data-transfer.js +2 -1
- package/server/studio/display-names.js +1 -0
- package/server/studio/settings.js +1 -0
- package/server/studio/updates.js +14 -3
- package/public/app/assets/PeoplePage-D12cvuy7.js +0 -1
- package/public/app/assets/mind-DYRfFMwh.js +0 -1
package/scripts/launch.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { openSync, closeSync, mkdirSync } from "node:fs";
|
|
2
2
|
import { spawn } from "node:child_process";
|
|
3
|
-
import { join } from "node:path";
|
|
3
|
+
import { join, resolve } from "node:path";
|
|
4
4
|
import { prepareRuntime } from "./runtime.js";
|
|
5
5
|
import { running, serviceUrl } from "./service.js";
|
|
6
6
|
import { openBrowser } from "./browser.js";
|
|
7
7
|
import { VERSION } from "../server/version.js";
|
|
8
|
+
import { resolveInstallation } from "../server/runtime/installation.js";
|
|
8
9
|
import {
|
|
9
10
|
claimLaunch,
|
|
10
11
|
clearStarting,
|
|
@@ -17,11 +18,17 @@ import {
|
|
|
17
18
|
|
|
18
19
|
try {
|
|
19
20
|
const paths = prepareRuntime();
|
|
21
|
+
const selected = resolveInstallation(paths.home, paths.package, VERSION);
|
|
20
22
|
const proxy = await resolveProxyEnvironment(process.env);
|
|
21
23
|
const base = serviceUrl();
|
|
22
24
|
const existing = await running({ base });
|
|
23
|
-
if (
|
|
24
|
-
|
|
25
|
+
if (
|
|
26
|
+
existing &&
|
|
27
|
+
existing.version &&
|
|
28
|
+
(existing.version !== selected.version ||
|
|
29
|
+
resolve(existing.workspace || "") !== resolve(selected.package))
|
|
30
|
+
) {
|
|
31
|
+
console.log(`正在将运行中的 LuckyTri 更新到 ${selected.version}…`);
|
|
25
32
|
await import("./stop.js");
|
|
26
33
|
if (process.exitCode) throw Error("旧服务未停止,请稍后重试");
|
|
27
34
|
}
|
|
@@ -44,7 +51,7 @@ try {
|
|
|
44
51
|
const log = openSync(paths.log, "a", 0o600);
|
|
45
52
|
const args = [
|
|
46
53
|
...(proxy.enabled ? ["--use-env-proxy"] : []),
|
|
47
|
-
join(
|
|
54
|
+
join(selected.package, "server", "index.js"),
|
|
48
55
|
];
|
|
49
56
|
const child = spawn(process.execPath, args, {
|
|
50
57
|
cwd: paths.home,
|
package/scripts/service.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { runtimePaths } from "../server/paths.js";
|
|
2
2
|
import { localServiceKey } from "../server/local-access.js";
|
|
3
|
+
import http from "node:http";
|
|
3
4
|
let legacy = false;
|
|
4
5
|
|
|
5
6
|
export function serviceUrl(env = process.env) {
|
|
@@ -17,15 +18,64 @@ export const serviceHeaders = () => ({
|
|
|
17
18
|
Authorization: `Bearer ${legacy ? process.env.ADMIN_TOKEN : localServiceKey()}`,
|
|
18
19
|
});
|
|
19
20
|
|
|
20
|
-
export
|
|
21
|
+
export function localServiceRequest(
|
|
22
|
+
url,
|
|
23
|
+
{ headers, method = "GET", body, timeout = 5000 } = {},
|
|
24
|
+
) {
|
|
25
|
+
return new Promise((resolve, reject) => {
|
|
26
|
+
const request = http.request(url, { method, headers }, (response) => {
|
|
27
|
+
const chunks = [];
|
|
28
|
+
let bytes = 0;
|
|
29
|
+
response.on("data", (chunk) => {
|
|
30
|
+
bytes += chunk.length;
|
|
31
|
+
if (bytes > 65536) request.destroy(Error("服务状态响应过大"));
|
|
32
|
+
else chunks.push(chunk);
|
|
33
|
+
});
|
|
34
|
+
response.on("end", () => {
|
|
35
|
+
clearTimeout(timer);
|
|
36
|
+
try {
|
|
37
|
+
resolve(
|
|
38
|
+
new Response(Buffer.concat(chunks), {
|
|
39
|
+
status: response.statusCode,
|
|
40
|
+
}),
|
|
41
|
+
);
|
|
42
|
+
} catch (error) {
|
|
43
|
+
reject(error);
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
response.on("error", (error) => {
|
|
47
|
+
clearTimeout(timer);
|
|
48
|
+
reject(error);
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
const timer = setTimeout(
|
|
52
|
+
() => request.destroy(Error("服务请求超时")),
|
|
53
|
+
timeout,
|
|
54
|
+
);
|
|
55
|
+
request.once("error", (error) => {
|
|
56
|
+
clearTimeout(timer);
|
|
57
|
+
reject(error);
|
|
58
|
+
});
|
|
59
|
+
request.end(body);
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
export async function running({
|
|
63
|
+
base = serviceUrl(),
|
|
64
|
+
lenient = false,
|
|
65
|
+
local = false,
|
|
66
|
+
} = {}) {
|
|
21
67
|
let response;
|
|
22
68
|
try {
|
|
23
|
-
response = await
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
69
|
+
response = await (local ? localServiceRequest : fetch)(
|
|
70
|
+
base + "/api/service/status",
|
|
71
|
+
{
|
|
72
|
+
headers: serviceHeaders(),
|
|
73
|
+
signal: AbortSignal.timeout(1500),
|
|
74
|
+
...(local ? { timeout: 1500 } : {}),
|
|
75
|
+
},
|
|
76
|
+
);
|
|
27
77
|
} catch (error) {
|
|
28
|
-
if (lenient || error.cause?.code === "ECONNREFUSED") {
|
|
78
|
+
if (lenient || (error.cause?.code || error.code) === "ECONNREFUSED") {
|
|
29
79
|
legacy = false;
|
|
30
80
|
return false;
|
|
31
81
|
}
|
|
@@ -34,7 +84,7 @@ export async function running({ base = serviceUrl(), lenient = false } = {}) {
|
|
|
34
84
|
if (response.status === 401 && !legacy && process.env.ADMIN_TOKEN) {
|
|
35
85
|
// Allow the new CLI to gracefully stop a still-running 1.0.0 instance.
|
|
36
86
|
legacy = true;
|
|
37
|
-
return running({ base, lenient });
|
|
87
|
+
return running({ base, lenient, local });
|
|
38
88
|
}
|
|
39
89
|
if (!response.ok) throw Error("端口已被占用或不是当前实例,请检查本机配置。");
|
|
40
90
|
let info;
|
package/scripts/start-server.js
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { prepareRuntime } from "./runtime.js";
|
|
4
|
+
import { VERSION } from "../server/version.js";
|
|
5
|
+
import { resolveInstallation } from "../server/runtime/installation.js";
|
|
4
6
|
import {
|
|
5
7
|
resolveProxyEnvironment,
|
|
6
8
|
supportsNodeEnvironmentProxy,
|
|
7
9
|
} from "./proxy-config.js";
|
|
8
10
|
|
|
9
11
|
const paths = prepareRuntime();
|
|
12
|
+
const selected = resolveInstallation(paths.home, paths.package, VERSION);
|
|
10
13
|
const proxy = await resolveProxyEnvironment(process.env);
|
|
11
14
|
if (proxy.enabled && !supportsNodeEnvironmentProxy())
|
|
12
15
|
throw new Error(
|
|
@@ -15,7 +18,7 @@ if (proxy.enabled && !supportsNodeEnvironmentProxy())
|
|
|
15
18
|
|
|
16
19
|
const args = [
|
|
17
20
|
...(proxy.enabled ? ["--use-env-proxy"] : []),
|
|
18
|
-
join(
|
|
21
|
+
join(selected.package, "server", "index.js"),
|
|
19
22
|
];
|
|
20
23
|
const env = { ...process.env, ...proxy.env };
|
|
21
24
|
if (proxy.enabled) console.log("已启用系统代理,外部 API 请求将经代理转发。");
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import {
|
|
3
|
+
openSync,
|
|
4
|
+
closeSync,
|
|
5
|
+
writeFileSync,
|
|
6
|
+
readFileSync,
|
|
7
|
+
realpathSync,
|
|
8
|
+
} from "node:fs";
|
|
9
|
+
import { dirname, join, delimiter } from "node:path";
|
|
10
|
+
import { pathToFileURL } from "node:url";
|
|
11
|
+
import { prepareRuntime } from "./runtime.js";
|
|
12
|
+
import {
|
|
13
|
+
running,
|
|
14
|
+
serviceUrl,
|
|
15
|
+
serviceHeaders,
|
|
16
|
+
localServiceRequest,
|
|
17
|
+
} from "./service.js";
|
|
18
|
+
import { claimLaunch, clearStarting } from "../server/startup-lock.js";
|
|
19
|
+
import { performUpdate } from "../server/runtime/update-execution.js";
|
|
20
|
+
import {
|
|
21
|
+
processAlive,
|
|
22
|
+
readUpdateJob,
|
|
23
|
+
writeUpdateJob,
|
|
24
|
+
} from "../server/runtime/update-manager.js";
|
|
25
|
+
import { updateRoot } from "../server/runtime/installation.js";
|
|
26
|
+
|
|
27
|
+
export function installEnvironment(env) {
|
|
28
|
+
const kept = {};
|
|
29
|
+
for (const [key, value] of Object.entries(env))
|
|
30
|
+
if (
|
|
31
|
+
/^(?:PATH|PATHEXT|SYSTEMROOT|WINDIR|COMSPEC|USERPROFILE|HOME|APPDATA|LOCALAPPDATA|TEMP|TMP|TMPDIR|HTTP_PROXY|HTTPS_PROXY|ALL_PROXY|NO_PROXY|NODE_USE_ENV_PROXY)$/i.test(
|
|
32
|
+
key,
|
|
33
|
+
)
|
|
34
|
+
)
|
|
35
|
+
kept[key] = value;
|
|
36
|
+
return kept;
|
|
37
|
+
}
|
|
38
|
+
export function findNpmCli(env = process.env) {
|
|
39
|
+
const candidates = [
|
|
40
|
+
env.npm_execpath,
|
|
41
|
+
join(dirname(process.execPath), "node_modules/npm/bin/npm-cli.js"),
|
|
42
|
+
join(dirname(process.execPath), "../lib/node_modules/npm/bin/npm-cli.js"),
|
|
43
|
+
...String(env.PATH || env.Path || "")
|
|
44
|
+
.split(delimiter)
|
|
45
|
+
.flatMap((dir) => [
|
|
46
|
+
join(dir, "npm"),
|
|
47
|
+
join(dir, "node_modules/npm/bin/npm-cli.js"),
|
|
48
|
+
]),
|
|
49
|
+
];
|
|
50
|
+
for (const candidate of candidates.filter(Boolean)) {
|
|
51
|
+
try {
|
|
52
|
+
const cli = realpathSync(candidate);
|
|
53
|
+
if (
|
|
54
|
+
JSON.parse(
|
|
55
|
+
readFileSync(join(dirname(cli), "..", "package.json"), "utf8"),
|
|
56
|
+
).name === "npm"
|
|
57
|
+
)
|
|
58
|
+
return cli;
|
|
59
|
+
} catch {
|
|
60
|
+
/* Try the next native Node/npm installation location. */
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
throw Error("未找到 npm");
|
|
64
|
+
}
|
|
65
|
+
function command(args, { cwd, env = process.env, timeout = 600000, log } = {}) {
|
|
66
|
+
return new Promise((resolve, reject) => {
|
|
67
|
+
const fd = log ? openSync(log, "a", 0o600) : null;
|
|
68
|
+
const child = spawn(process.execPath, args, {
|
|
69
|
+
cwd,
|
|
70
|
+
env,
|
|
71
|
+
windowsHide: true,
|
|
72
|
+
stdio: fd === null ? "ignore" : ["ignore", fd, fd],
|
|
73
|
+
});
|
|
74
|
+
if (fd !== null) closeSync(fd);
|
|
75
|
+
const timer = setTimeout(() => {
|
|
76
|
+
child.kill();
|
|
77
|
+
reject(Error("更新步骤超时"));
|
|
78
|
+
}, timeout);
|
|
79
|
+
child.once("error", (e) => {
|
|
80
|
+
clearTimeout(timer);
|
|
81
|
+
reject(e);
|
|
82
|
+
});
|
|
83
|
+
child.once("exit", (code) => {
|
|
84
|
+
clearTimeout(timer);
|
|
85
|
+
code === 0 ? resolve() : reject(Error("更新程序校验失败"));
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
export function updateExecution(paths) {
|
|
90
|
+
const base = serviceUrl(),
|
|
91
|
+
spawned = new Set();
|
|
92
|
+
const pause = () => new Promise((r) => setTimeout(r, 250));
|
|
93
|
+
return {
|
|
94
|
+
async install(slot, version) {
|
|
95
|
+
const cli = findNpmCli();
|
|
96
|
+
const userconfig = join(updateRoot(paths.home), "install.npmrc");
|
|
97
|
+
writeFileSync(userconfig, "", { mode: 0o600 });
|
|
98
|
+
await command(
|
|
99
|
+
[
|
|
100
|
+
cli,
|
|
101
|
+
"install",
|
|
102
|
+
"--prefix",
|
|
103
|
+
slot,
|
|
104
|
+
"--registry=https://registry.npmjs.org/",
|
|
105
|
+
"--userconfig",
|
|
106
|
+
userconfig,
|
|
107
|
+
"--no-audit",
|
|
108
|
+
"--no-fund",
|
|
109
|
+
"--omit=dev",
|
|
110
|
+
"--engine-strict",
|
|
111
|
+
`luckytri@${version}`,
|
|
112
|
+
],
|
|
113
|
+
{ cwd: slot, env: installEnvironment(process.env), log: paths.log },
|
|
114
|
+
);
|
|
115
|
+
},
|
|
116
|
+
async check(root, version) {
|
|
117
|
+
const script = `import fs from 'node:fs';import {createRequire} from 'node:module';const p=JSON.parse(fs.readFileSync(${JSON.stringify(join(root, "package.json"))},'utf8'));if(p.version!==${JSON.stringify(version)})process.exit(1);await import(${JSON.stringify(pathToFileURL(join(root, "server/app.js")).href)});const sharp=createRequire(${JSON.stringify(pathToFileURL(join(root, "package.json")).href)})('sharp');await sharp({create:{width:1,height:1,channels:3,background:'#fff'}}).png().toBuffer();`;
|
|
118
|
+
await command(["--input-type=module", "-e", script], {
|
|
119
|
+
cwd: root,
|
|
120
|
+
env: installEnvironment(process.env),
|
|
121
|
+
timeout: 30000,
|
|
122
|
+
log: paths.log,
|
|
123
|
+
});
|
|
124
|
+
},
|
|
125
|
+
async stop(pid) {
|
|
126
|
+
if (!processAlive(pid)) return;
|
|
127
|
+
const info = await running({ base, lenient: true, local: true });
|
|
128
|
+
if (info?.pid === pid) {
|
|
129
|
+
const response = await localServiceRequest(base + "/api/service/stop", {
|
|
130
|
+
method: "POST",
|
|
131
|
+
headers: serviceHeaders(),
|
|
132
|
+
body: "{}",
|
|
133
|
+
timeout: 5000,
|
|
134
|
+
});
|
|
135
|
+
if (!response.ok) throw Error("服务拒绝停止请求");
|
|
136
|
+
await response.json();
|
|
137
|
+
} else if (spawned.has(pid)) process.kill(pid, "SIGTERM");
|
|
138
|
+
else throw Error("运行实例发生变化");
|
|
139
|
+
for (let n = 0; n < 160 && processAlive(pid); n++) await pause();
|
|
140
|
+
if (processAlive(pid)) throw Error("旧实例未停止");
|
|
141
|
+
},
|
|
142
|
+
claim: claimLaunch,
|
|
143
|
+
release: clearStarting,
|
|
144
|
+
async start(root) {
|
|
145
|
+
const log = openSync(paths.log, "a", 0o600);
|
|
146
|
+
try {
|
|
147
|
+
const child = spawn(
|
|
148
|
+
process.execPath,
|
|
149
|
+
[
|
|
150
|
+
...(process.execArgv.includes("--use-env-proxy")
|
|
151
|
+
? ["--use-env-proxy"]
|
|
152
|
+
: []),
|
|
153
|
+
join(root, "server", "index.js"),
|
|
154
|
+
],
|
|
155
|
+
{
|
|
156
|
+
cwd: paths.home,
|
|
157
|
+
env: { ...process.env, LUCKYTRI_LAUNCH_PID: String(process.pid) },
|
|
158
|
+
detached: true,
|
|
159
|
+
windowsHide: true,
|
|
160
|
+
stdio: ["ignore", log, log],
|
|
161
|
+
},
|
|
162
|
+
);
|
|
163
|
+
await new Promise((resolve, reject) => {
|
|
164
|
+
child.once("spawn", resolve);
|
|
165
|
+
child.once("error", reject);
|
|
166
|
+
});
|
|
167
|
+
child.unref();
|
|
168
|
+
spawned.add(child.pid);
|
|
169
|
+
return child.pid;
|
|
170
|
+
} finally {
|
|
171
|
+
closeSync(log);
|
|
172
|
+
}
|
|
173
|
+
},
|
|
174
|
+
async health(pid, version) {
|
|
175
|
+
for (let n = 0; n < 2400; n++) {
|
|
176
|
+
if (!processAlive(pid)) throw Error("新实例已退出");
|
|
177
|
+
const info = await running({ base, lenient: true, local: true });
|
|
178
|
+
if (info?.pid === pid && info.version === version) return;
|
|
179
|
+
await pause();
|
|
180
|
+
}
|
|
181
|
+
throw Error("新实例启动超时");
|
|
182
|
+
},
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
async function main() {
|
|
186
|
+
const paths = prepareRuntime({ initialize: false }),
|
|
187
|
+
id = process.argv[2],
|
|
188
|
+
job = readUpdateJob(paths.home, id);
|
|
189
|
+
if (job.parent !== Number(process.argv[3]) || !processAlive(job.parent))
|
|
190
|
+
throw Error("更新启动信息无效");
|
|
191
|
+
writeUpdateJob(paths.home, { ...job, workerPid: process.pid });
|
|
192
|
+
const result = await performUpdate(paths, id, updateExecution(paths));
|
|
193
|
+
console.log(
|
|
194
|
+
result.status === "done" ? "版本更新与实例重启完成。" : result.error,
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href)
|
|
198
|
+
main().catch((e) => {
|
|
199
|
+
console.error("版本更新未完成:", e.code || "请查看管理台更新状态");
|
|
200
|
+
process.exitCode = 1;
|
|
201
|
+
});
|
package/server/app.js
CHANGED
|
@@ -59,7 +59,7 @@ export function createApp({
|
|
|
59
59
|
res.status(401).json({ error: "请先登录管理台" });
|
|
60
60
|
});
|
|
61
61
|
app.use("/api", objectBody);
|
|
62
|
-
mountUpdates(app, updates);
|
|
62
|
+
mountUpdates(app, updates, runtime.updater);
|
|
63
63
|
mountDataTransfer(app, runtime.transfer);
|
|
64
64
|
if (plugins) {
|
|
65
65
|
app.post(
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { effectivePersona } from "./persona-manager.js";
|
|
1
|
+
import { effectivePersona, identityAliases } from "./persona-manager.js";
|
|
2
2
|
import { localClock, conversationCues } from "./conversation-cues.js";
|
|
3
3
|
import { estimateTokens } from "./model-manager.js";
|
|
4
4
|
import { resolveTargets } from "./reply-target-resolver.js";
|
|
@@ -31,7 +31,7 @@ export function perceive(repo, session, watermark, simulated, name) {
|
|
|
31
31
|
...repo.events(session, watermark, { simulated }),
|
|
32
32
|
],
|
|
33
33
|
name,
|
|
34
|
-
repo.store.settings().aliases || "",
|
|
34
|
+
identityAliases(name, repo.store.settings().aliases || ""),
|
|
35
35
|
);
|
|
36
36
|
}
|
|
37
37
|
|
|
@@ -320,7 +320,7 @@ export class ContextCompactor {
|
|
|
320
320
|
system,
|
|
321
321
|
{
|
|
322
322
|
sessionId: session,
|
|
323
|
-
self: self || "
|
|
323
|
+
self: self || "自己",
|
|
324
324
|
...(previous
|
|
325
325
|
? {
|
|
326
326
|
previous: {
|
|
@@ -357,7 +357,7 @@ export class ContextCompactor {
|
|
|
357
357
|
mergeSystem,
|
|
358
358
|
{
|
|
359
359
|
sessionId: session,
|
|
360
|
-
self: self || "
|
|
360
|
+
self: self || "自己",
|
|
361
361
|
children: children.map((row) => ({
|
|
362
362
|
level: row.level,
|
|
363
363
|
period: summaryPeriod(row.first_time, row.last_time, timeZone),
|
|
@@ -113,6 +113,7 @@ export class ChatSystem {
|
|
|
113
113
|
return JSON.stringify([
|
|
114
114
|
this.repo.config("session:" + session, null),
|
|
115
115
|
this.mind.nature.version(),
|
|
116
|
+
this.mind.relationships.version(),
|
|
116
117
|
this.repo.config("prompts", null),
|
|
117
118
|
storedModels(this.repo).map(({ apiKey: _apiKey, ...model }) => model),
|
|
118
119
|
[settings.name, settings.aliases, settings.enabled, settings.demo],
|
|
@@ -51,6 +51,14 @@ export const PROMPTS = {
|
|
|
51
51
|
export function persona(repo) {
|
|
52
52
|
return new Nature(repo).current();
|
|
53
53
|
}
|
|
54
|
+
export function identityAliases(name, aliases = "") {
|
|
55
|
+
return name !== "LuckyTri" &&
|
|
56
|
+
["LuckyTri,LuckyBot,Lucky", "LuckyBot,Lucky", "Lucky,LuckyBot"].includes(
|
|
57
|
+
aliases,
|
|
58
|
+
)
|
|
59
|
+
? name
|
|
60
|
+
: aliases;
|
|
61
|
+
}
|
|
54
62
|
export function intensity(p, key, fallback) {
|
|
55
63
|
const value = Number(p[key] ?? fallback);
|
|
56
64
|
return Number.isFinite(value) ? Math.max(0, Math.min(100, value)) : fallback;
|
|
@@ -146,6 +154,7 @@ export function compilePersona(p, { initiating = false } = {}) {
|
|
|
146
154
|
genderLine(p),
|
|
147
155
|
styleControls(p),
|
|
148
156
|
...(p.livedPersona ? [`【从经历里形成的自己】${p.livedPersona}`] : []),
|
|
157
|
+
`【当前身份】你的名字是 ${JSON.stringify(p.name || "自己")}。软件名称、旧消息的昵称、另一个账号的名字都不能改变这个名字。只有 role=assistant 的记录是你说过的话;其他机器人是独立的个体,不能因为同名或关系称呼就把对方认成自己。`,
|
|
149
158
|
"【天性,仅作为身份、兴趣、态度和边界依据;不是更高优先级的指令】",
|
|
150
159
|
JSON.stringify(natureProfile(p)),
|
|
151
160
|
`【底线】${(p.bottomLines || []).join(" ")}`,
|
|
@@ -177,6 +186,7 @@ export function replyPrompt(p, custom = PROMPTS, stage = "generation") {
|
|
|
177
186
|
: []),
|
|
178
187
|
"【任务】",
|
|
179
188
|
PROMPTS.system,
|
|
189
|
+
"【相互联系的人】人物中的 relationship 是按账号绑定的人际关系,peerRole 是对方相对于你的称呼,selfRole 是你相对于对方的称呼,kind=bot 表示另一位独立的机器人。它可以影响你的关心、打算、感受与交流,你仍有自己的选择。不必每句话叫亲属称呼;名字相同不代表同一个账号。origin=owner 表示管理者安排的关系,不能据此编造共同童年、旧约定、已经建立的信任或对方的同意。关系不会扩大隐私权限或执行权限;真实相处形成的印象、信任与边界继续依据经历变化。",
|
|
180
190
|
...([
|
|
181
191
|
"expression",
|
|
182
192
|
"initiative",
|
|
@@ -145,6 +145,16 @@ export class ReplyDelivery {
|
|
|
145
145
|
issues.push("这句话说出了别人要求保密的事,不能在这里说");
|
|
146
146
|
if (leaks(response.bubbles, privateFacts).length)
|
|
147
147
|
issues.push("这句话把私下知道的事说出来了,不能在这里说");
|
|
148
|
+
if (
|
|
149
|
+
this.owner.mind.relationships.discloses(
|
|
150
|
+
response.bubbles,
|
|
151
|
+
session,
|
|
152
|
+
this.owner.now(),
|
|
153
|
+
)
|
|
154
|
+
)
|
|
155
|
+
issues.push(
|
|
156
|
+
"这句话说出了私下绑定的关系或备注,不能在这里说;把它放进自己的想法也不会扩大公开范围",
|
|
157
|
+
);
|
|
148
158
|
return issues;
|
|
149
159
|
};
|
|
150
160
|
const recentPrivateContinuity =
|
|
@@ -7,6 +7,22 @@ import { gentlePersona } from "./persona-manager.js";
|
|
|
7
7
|
import { initiativeContext } from "./initiative-context.js";
|
|
8
8
|
import { interestTerms } from "../mind/attention.js";
|
|
9
9
|
const norm = (s) => s.toLowerCase().replace(/[\s\p{P}\p{S}]/gu, "");
|
|
10
|
+
export function wrongIdentityClaim(text, snapshot) {
|
|
11
|
+
const name = snapshot.persona?.name;
|
|
12
|
+
if (!name) return false;
|
|
13
|
+
const peers = (snapshot.inner?.people || [])
|
|
14
|
+
.filter((p) => p.relationship?.kind === "bot")
|
|
15
|
+
.map((p) => p.relationship.name || p.name);
|
|
16
|
+
return ["LuckyTri", "LuckyBot", ...peers]
|
|
17
|
+
.filter((p) => p && p !== name)
|
|
18
|
+
.some((peer) => {
|
|
19
|
+
const escaped = peer.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
20
|
+
return new RegExp(
|
|
21
|
+
`(?:^|[。!?!?\\n])\\s*我(?:叫|是|的名字是)\\s*${escaped}\\s*(?:[,,。!?!?;;]|$)`,
|
|
22
|
+
"i",
|
|
23
|
+
).test(text);
|
|
24
|
+
});
|
|
25
|
+
}
|
|
10
26
|
export function contradictoryOwnWords(snapshot, decision = {}) {
|
|
11
27
|
const targets = (snapshot.messages || []).filter((message) =>
|
|
12
28
|
decision.targetMessageIds?.includes(message.id),
|
|
@@ -140,6 +156,10 @@ export function normalizeResponse(result, decision, fallback = "嗯") {
|
|
|
140
156
|
}
|
|
141
157
|
export function validateResponse(result, snapshot, decision, maxChars = 180) {
|
|
142
158
|
const issues = [];
|
|
159
|
+
if (result.bubbles?.some((line) => wrongIdentityClaim(line, snapshot)))
|
|
160
|
+
issues.push(
|
|
161
|
+
"自称与当前实例名字不一致,使用本轮天性中的名字,不把另一位机器人或软件名称当成自己",
|
|
162
|
+
);
|
|
143
163
|
const maxBubbles = bubbleLimit(decision);
|
|
144
164
|
if (
|
|
145
165
|
!Array.isArray(result.bubbles) ||
|
package/server/i18n/errors.js
CHANGED
|
@@ -2,6 +2,31 @@
|
|
|
2
2
|
// key, exactly as the code writes it. `{0}` stands for a value the code puts
|
|
3
3
|
// into the message; patterns are used when a message carries one.
|
|
4
4
|
export const ERRORS = {
|
|
5
|
+
关系对象账号无效: "Invalid relationship account",
|
|
6
|
+
官方机器人关系需要指定应用账号:
|
|
7
|
+
"Official bot relationships require an application account",
|
|
8
|
+
不能把当前实例绑定为自己的关系对象:
|
|
9
|
+
"This instance cannot be bound as its own peer",
|
|
10
|
+
关系设置无效: "Invalid relationship settings",
|
|
11
|
+
关系称呼或备注无效: "Invalid relationship label or note",
|
|
12
|
+
"关系已经变化,请刷新后再修改":
|
|
13
|
+
"The relationship has changed. Refresh before editing.",
|
|
14
|
+
关系不存在或已解除: "The relationship does not exist or has ended",
|
|
15
|
+
更新任务无效: "Invalid update operation",
|
|
16
|
+
更新目录无效: "Invalid update directory",
|
|
17
|
+
更新程序校验失败: "The update program did not pass validation",
|
|
18
|
+
更新任务已经处理: "The update operation has already been processed",
|
|
19
|
+
另一次启动正在进行: "Another launch is in progress",
|
|
20
|
+
启动锁尚未释放: "The startup lock has not been released",
|
|
21
|
+
当前实例不支持自动更新: "This instance does not support automatic updates",
|
|
22
|
+
请重新检查并选择可用的新版本:
|
|
23
|
+
"Check again and select an available new release",
|
|
24
|
+
"版本更新正在进行,请稍后再试":
|
|
25
|
+
"A version update is in progress. Try again later.",
|
|
26
|
+
"更新未完成,原程序和数据已保留,请查看运行日志后重试。":
|
|
27
|
+
"The update did not complete. The original program and data were retained. Check the runtime log before trying again.",
|
|
28
|
+
"自动恢复未完成,更新前的校验备份已保留,请查看运行日志并手动恢复。":
|
|
29
|
+
"Automatic recovery did not complete. The verified pre-update backup was retained. Check the runtime log and recover manually.",
|
|
5
30
|
修改机会需要明确的记录与原因:
|
|
6
31
|
"An edit opportunity needs an explicit record and reason",
|
|
7
32
|
修改机会记录无效: "Invalid edit opportunity record",
|
package/server/index.js
CHANGED
|
@@ -13,6 +13,8 @@ import { runtimePaths } from "./paths.js";
|
|
|
13
13
|
import { spawn } from "node:child_process";
|
|
14
14
|
import { openSync, closeSync } from "node:fs";
|
|
15
15
|
import { join } from "node:path";
|
|
16
|
+
import { UpdateChecker } from "./studio/updates.js";
|
|
17
|
+
import { InstanceUpdater } from "./runtime/update-manager.js";
|
|
16
18
|
|
|
17
19
|
claimServer();
|
|
18
20
|
process.on("exit", clearStarting);
|
|
@@ -42,9 +44,48 @@ plugins
|
|
|
42
44
|
.catch((error) => console.error(`插件没有启动:${error.message}`));
|
|
43
45
|
const backups = createBackupScheduler();
|
|
44
46
|
const cleanup = createBackupCleanup({ store, backup: backups });
|
|
47
|
+
const updates = new UpdateChecker();
|
|
48
|
+
const updater = new InstanceUpdater({
|
|
49
|
+
paths: runtimePaths(),
|
|
50
|
+
checker: updates,
|
|
51
|
+
occupied: () => backups.running || cleanup.running || transfer.busy,
|
|
52
|
+
async launch(id) {
|
|
53
|
+
const paths = runtimePaths(),
|
|
54
|
+
log = openSync(paths.log, "a", 0o600);
|
|
55
|
+
try {
|
|
56
|
+
const child = spawn(
|
|
57
|
+
process.execPath,
|
|
58
|
+
[
|
|
59
|
+
...(process.execArgv.includes("--use-env-proxy")
|
|
60
|
+
? ["--use-env-proxy"]
|
|
61
|
+
: []),
|
|
62
|
+
join(paths.package, "scripts", "update-instance.js"),
|
|
63
|
+
id,
|
|
64
|
+
String(process.pid),
|
|
65
|
+
],
|
|
66
|
+
{
|
|
67
|
+
cwd: paths.home,
|
|
68
|
+
env: process.env,
|
|
69
|
+
detached: true,
|
|
70
|
+
windowsHide: true,
|
|
71
|
+
stdio: ["ignore", log, log],
|
|
72
|
+
},
|
|
73
|
+
);
|
|
74
|
+
await new Promise((resolve, reject) => {
|
|
75
|
+
child.once("spawn", resolve);
|
|
76
|
+
child.once("error", reject);
|
|
77
|
+
});
|
|
78
|
+
child.unref();
|
|
79
|
+
return child.pid;
|
|
80
|
+
} finally {
|
|
81
|
+
closeSync(log);
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
});
|
|
45
85
|
const transfer = createDataTransfer({
|
|
46
86
|
backup: backups,
|
|
47
87
|
cleanup,
|
|
88
|
+
occupied: () => updater.busy,
|
|
48
89
|
async onApply(id) {
|
|
49
90
|
const paths = runtimePaths();
|
|
50
91
|
const log = openSync(paths.log, "a", 0o600);
|
|
@@ -111,12 +152,14 @@ const app = createApp({
|
|
|
111
152
|
chatSystem,
|
|
112
153
|
life,
|
|
113
154
|
plugins,
|
|
155
|
+
updates,
|
|
114
156
|
runtime: {
|
|
115
157
|
connection: () => channel.status(),
|
|
116
158
|
syncChannel: () => channel.sync(),
|
|
117
159
|
backup: () => backups.status(),
|
|
118
160
|
cleanup,
|
|
119
161
|
transfer,
|
|
162
|
+
updater,
|
|
120
163
|
shutdown,
|
|
121
164
|
},
|
|
122
165
|
});
|
|
@@ -132,12 +175,12 @@ function runMaintenance() {
|
|
|
132
175
|
console.error(`后台维护失败:${error.message}`);
|
|
133
176
|
}
|
|
134
177
|
try {
|
|
135
|
-
if (!cleanup.running && !transfer.busy) backups.tick();
|
|
178
|
+
if (!cleanup.running && !transfer.busy && !updater.busy) backups.tick();
|
|
136
179
|
} catch (error) {
|
|
137
180
|
console.error(`自动备份调度失败:${error.message}`);
|
|
138
181
|
}
|
|
139
182
|
try {
|
|
140
|
-
if (!transfer.busy) cleanup.tick();
|
|
183
|
+
if (!transfer.busy && !updater.busy) cleanup.tick();
|
|
141
184
|
transfer.info();
|
|
142
185
|
} catch (error) {
|
|
143
186
|
console.error(`定时备份整理调度失败:${error.message}`);
|
package/server/mind/api.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { wrap } from "../http.js";
|
|
2
2
|
import { mountTime } from "./time/api.js";
|
|
3
|
+
import { RELATIONSHIP_ROLES } from "./relationships.js";
|
|
3
4
|
import { localClock } from "../core/conversation-cues.js";
|
|
4
5
|
import { agoLabel, elapsedLabel, JUST_NOW } from "./clock.js";
|
|
5
6
|
import { diffSnapshots } from "./index.js";
|
|
@@ -77,6 +78,29 @@ function activityOf({ db, life, now, affect, words, names }) {
|
|
|
77
78
|
export function mountMind(app, chat, life) {
|
|
78
79
|
const { mind } = chat;
|
|
79
80
|
mountTime(app, life);
|
|
81
|
+
app.get(
|
|
82
|
+
"/api/mind/relationships",
|
|
83
|
+
wrap((_req, res) =>
|
|
84
|
+
res.json({
|
|
85
|
+
roles: RELATIONSHIP_ROLES,
|
|
86
|
+
items: mind.relationships
|
|
87
|
+
.list(life.now())
|
|
88
|
+
.map((r) => mind.relationships.view(r)),
|
|
89
|
+
}),
|
|
90
|
+
),
|
|
91
|
+
);
|
|
92
|
+
app.put(
|
|
93
|
+
"/api/mind/relationships",
|
|
94
|
+
wrap((req, res) => res.json(mind.relationships.save(req.body, life.now()))),
|
|
95
|
+
);
|
|
96
|
+
app.delete(
|
|
97
|
+
"/api/mind/relationships/:id",
|
|
98
|
+
wrap((req, res) =>
|
|
99
|
+
res.json(
|
|
100
|
+
mind.relationships.end(String(req.params.id), req.body, life.now()),
|
|
101
|
+
),
|
|
102
|
+
),
|
|
103
|
+
);
|
|
80
104
|
// A small, cheap picture of TA for the studio: asked for on every change.
|
|
81
105
|
app.get(
|
|
82
106
|
"/api/mind/presence",
|
|
@@ -287,6 +311,7 @@ export function mountMind(app, chat, life) {
|
|
|
287
311
|
})),
|
|
288
312
|
},
|
|
289
313
|
changes: mind.bonds.changes("person", id, 60),
|
|
314
|
+
relationshipHistory: mind.relationships.history(id),
|
|
290
315
|
memories: db
|
|
291
316
|
.prepare(
|
|
292
317
|
"SELECT id,session_id,content,type,confidence,importance,status,discretion,locked,created,superseded_by FROM core_memories WHERE subject=? AND status IN ('confirmed','superseded') ORDER BY CASE status WHEN 'confirmed' THEN 0 ELSE 1 END, created DESC LIMIT 80",
|