@rivus/gateway 0.16.6 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +674 -21
- package/README.md +25 -0
- package/dist/bootstrap/herdr-feishu.d.ts +37 -0
- package/dist/bootstrap/herdr-feishu.js +856 -0
- package/dist/bootstrap/pi-feishu.d.ts +281 -16
- package/dist/bootstrap/pi-feishu.js +17314 -344
- package/dist/chunks/background-session-authority.js +2 -38
- package/dist/chunks/rivus-daemon-cli.d.ts +1115 -0
- package/dist/chunks/rivus-node-entrypoint.js +700 -819
- package/dist/chunks/{node-rivus-deployment-manifest.js → runtime-cache.js} +1116 -1475
- package/dist/cli.d.ts +2 -1
- package/dist/cli.js +1 -0
- package/dist/index.d.ts +65 -3
- package/dist/index.js +3 -1215
- package/dist/mcp.d.ts +7 -63
- package/dist/mcp.js +3 -250
- package/package.json +21 -13
- package/templates/a-share-briefing-analysis.mjs +3 -1
- package/templates/a-share-briefing-renderer.mjs +15 -5
- package/templates/a-share-index-evidence.mjs +9 -3
- package/templates/a-share-market-briefing.mjs +3 -1
- package/templates/a-share-overseas-evidence.mjs +12 -4
- package/templates/a-share-policy-evidence.mjs +30 -10
- package/templates/a-share-sector-evidence.mjs +3 -1
- package/templates/acp-stdio-proxy.mjs +10 -4
- package/templates/current-weather.mjs +36 -12
- package/templates/herdr-feishu.mjs +30 -0
- package/templates/html-drive-tools.mjs +36 -12
- package/templates/pi-feishu-deployment.bootstrap.ts +1 -3
- package/templates/pi-feishu.bootstrap.ts +1 -242
- package/templates/rivus-agents.plugin.mjs +3 -1
- package/dist/chunks/background-session-control-input.js +0 -45
- package/dist/chunks/background-session-service.d.ts +0 -390
- package/dist/chunks/index.d.ts +0 -4703
- package/dist/chunks/service.js +0 -12112
- package/templates/langfuse-drive-e2e.mjs +0 -175
|
@@ -6,10 +6,12 @@ import {
|
|
|
6
6
|
createAcpStdioAgentLoop,
|
|
7
7
|
createJsonAcpSessionStore,
|
|
8
8
|
serveAcpAgentOnStdio
|
|
9
|
-
} from "@rivus/
|
|
9
|
+
} from "@rivus/runtime/acp";
|
|
10
10
|
|
|
11
11
|
const command = process.env.RIVUS_ACP_SERVER_COMMAND?.trim();
|
|
12
|
-
if (!command)
|
|
12
|
+
if (!command) {
|
|
13
|
+
throw new Error("RIVUS_ACP_SERVER_COMMAND is required and must resolve to an explicit executable");
|
|
14
|
+
}
|
|
13
15
|
|
|
14
16
|
const workingDirectory = process.env.RIVUS_ACP_WORKING_DIRECTORY?.trim() || process.cwd();
|
|
15
17
|
const sessionStorePath = process.env.RIVUS_ACP_SESSION_STORE?.trim();
|
|
@@ -37,7 +39,9 @@ try {
|
|
|
37
39
|
}
|
|
38
40
|
|
|
39
41
|
function parseArguments(value) {
|
|
40
|
-
if (!value?.trim())
|
|
42
|
+
if (!value?.trim()) {
|
|
43
|
+
return [];
|
|
44
|
+
}
|
|
41
45
|
const parsed = JSON.parse(value);
|
|
42
46
|
if (!Array.isArray(parsed) || parsed.some((item) => typeof item !== "string")) {
|
|
43
47
|
throw new Error("RIVUS_ACP_SERVER_ARGUMENTS must be a JSON array of strings");
|
|
@@ -52,7 +56,9 @@ function selectEnvironment(value) {
|
|
|
52
56
|
.map((item) => item.trim())
|
|
53
57
|
.filter(Boolean) ?? []) {
|
|
54
58
|
const environmentValue = process.env[key];
|
|
55
|
-
if (environmentValue !== undefined)
|
|
59
|
+
if (environmentValue !== undefined) {
|
|
60
|
+
selected[key] = environmentValue;
|
|
61
|
+
}
|
|
56
62
|
}
|
|
57
63
|
return selected;
|
|
58
64
|
}
|
|
@@ -85,12 +85,16 @@ async function fetchJson(url, operation) {
|
|
|
85
85
|
}
|
|
86
86
|
|
|
87
87
|
function readNumber(value, field) {
|
|
88
|
-
if (typeof value !== "number" || !Number.isFinite(value))
|
|
88
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
89
|
+
throw new Error(`current-weather missing ${field}`);
|
|
90
|
+
}
|
|
89
91
|
return value;
|
|
90
92
|
}
|
|
91
93
|
|
|
92
94
|
function readString(value, field) {
|
|
93
|
-
if (typeof value !== "string" || !value)
|
|
95
|
+
if (typeof value !== "string" || !value) {
|
|
96
|
+
throw new Error(`current-weather missing ${field}`);
|
|
97
|
+
}
|
|
94
98
|
return value;
|
|
95
99
|
}
|
|
96
100
|
|
|
@@ -103,15 +107,35 @@ function readFirstNumber(value, field) {
|
|
|
103
107
|
}
|
|
104
108
|
|
|
105
109
|
function describeWeatherCode(code) {
|
|
106
|
-
if (code === 0)
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
if (code ===
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
if (code
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
if (code
|
|
110
|
+
if (code === 0) {
|
|
111
|
+
return "晴";
|
|
112
|
+
}
|
|
113
|
+
if (code === 1 || code === 2) {
|
|
114
|
+
return "晴间多云";
|
|
115
|
+
}
|
|
116
|
+
if (code === 3) {
|
|
117
|
+
return "阴";
|
|
118
|
+
}
|
|
119
|
+
if (code === 45 || code === 48) {
|
|
120
|
+
return "有雾";
|
|
121
|
+
}
|
|
122
|
+
if (code >= 51 && code <= 57) {
|
|
123
|
+
return "毛毛雨";
|
|
124
|
+
}
|
|
125
|
+
if (code >= 61 && code <= 67) {
|
|
126
|
+
return "雨";
|
|
127
|
+
}
|
|
128
|
+
if (code >= 71 && code <= 77) {
|
|
129
|
+
return "雪";
|
|
130
|
+
}
|
|
131
|
+
if (code >= 80 && code <= 82) {
|
|
132
|
+
return "阵雨";
|
|
133
|
+
}
|
|
134
|
+
if (code >= 85 && code <= 86) {
|
|
135
|
+
return "阵雪";
|
|
136
|
+
}
|
|
137
|
+
if (code >= 95 && code <= 99) {
|
|
138
|
+
return "雷暴";
|
|
139
|
+
}
|
|
116
140
|
return "未知";
|
|
117
141
|
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { readFile } from "node:fs/promises";
|
|
4
|
+
import { checkHerdrFeishuBridge, startHerdrFeishuBridge } from "@rivus/gateway/bootstrap/herdr-feishu";
|
|
5
|
+
|
|
6
|
+
if (process.argv.slice(2).some((arg) => arg !== "--check")) {
|
|
7
|
+
throw new Error("Usage: herdr-feishu.mjs [--check]");
|
|
8
|
+
}
|
|
9
|
+
const path = process.env.RIVUS_HERDR_CONFIG;
|
|
10
|
+
if (!path) {
|
|
11
|
+
throw new Error("Set RIVUS_HERDR_CONFIG to a private bridge configuration file");
|
|
12
|
+
}
|
|
13
|
+
const config = JSON.parse(await readFile(path, "utf8"));
|
|
14
|
+
const options = {
|
|
15
|
+
...config,
|
|
16
|
+
appId: process.env.FEISHU_APP_ID,
|
|
17
|
+
appSecret: process.env.FEISHU_APP_SECRET,
|
|
18
|
+
onObservation: (event) => process.stdout.write(`${JSON.stringify(event)}\n`)
|
|
19
|
+
};
|
|
20
|
+
if (process.argv.includes("--check")) {
|
|
21
|
+
process.stdout.write(`${JSON.stringify(await checkHerdrFeishuBridge(options), null, 2)}\n`);
|
|
22
|
+
} else {
|
|
23
|
+
const bridge = await startHerdrFeishuBridge(options);
|
|
24
|
+
process.stdout.write("Herdr bridge started; verify receipt and reply before treating the transport as ready.\n");
|
|
25
|
+
for (const signal of ["SIGINT", "SIGTERM"]) {
|
|
26
|
+
process.once(signal, async () => {
|
|
27
|
+
await bridge.stop();
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -25,7 +25,9 @@ export function createHtmlArtifactWriter(options = {}) {
|
|
|
25
25
|
try {
|
|
26
26
|
await writeFile(filePath, html, { encoding: "utf8", flag: "wx" });
|
|
27
27
|
} catch (error) {
|
|
28
|
-
if (!isFileAlreadyPresent(error) || (await readFile(filePath, "utf8")) !== html)
|
|
28
|
+
if (!isFileAlreadyPresent(error) || (await readFile(filePath, "utf8")) !== html) {
|
|
29
|
+
throw error;
|
|
30
|
+
}
|
|
29
31
|
}
|
|
30
32
|
return Object.freeze({
|
|
31
33
|
artifactId,
|
|
@@ -85,15 +87,21 @@ export function createLarkDriveHtmlUploader(options = {}) {
|
|
|
85
87
|
|
|
86
88
|
async function readRegularFileNoFollow(filePath, artifactId) {
|
|
87
89
|
const linkMetadata = await lstat(filePath);
|
|
88
|
-
if (linkMetadata.isSymbolicLink())
|
|
90
|
+
if (linkMetadata.isSymbolicLink()) {
|
|
91
|
+
throw new Error(`HTML artifact cannot be a symbolic link: ${artifactId}`);
|
|
92
|
+
}
|
|
89
93
|
if (typeof constants.O_NOFOLLOW !== "number") {
|
|
90
94
|
throw new Error("HTML artifact upload requires filesystem no-follow support");
|
|
91
95
|
}
|
|
92
96
|
const handle = await open(filePath, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
93
97
|
try {
|
|
94
98
|
const metadata = await handle.stat();
|
|
95
|
-
if (!metadata.isFile())
|
|
96
|
-
|
|
99
|
+
if (!metadata.isFile()) {
|
|
100
|
+
throw new Error(`HTML artifact is not a regular file: ${artifactId}`);
|
|
101
|
+
}
|
|
102
|
+
if (metadata.size > 256 * 1024) {
|
|
103
|
+
throw new Error(`HTML artifact exceeds the 256 KiB limit: ${artifactId}`);
|
|
104
|
+
}
|
|
97
105
|
return await handle.readFile();
|
|
98
106
|
} finally {
|
|
99
107
|
await handle.close();
|
|
@@ -138,7 +146,9 @@ function readCompleteHtml(input) {
|
|
|
138
146
|
if (!/^<!doctype html>/i.test(html) || !/<html(?:\s|>)/i.test(html) || !/<\/html>\s*$/i.test(html)) {
|
|
139
147
|
throw new Error("write-html-artifact requires a complete HTML document");
|
|
140
148
|
}
|
|
141
|
-
if (Buffer.byteLength(html) > 256 * 1024)
|
|
149
|
+
if (Buffer.byteLength(html) > 256 * 1024) {
|
|
150
|
+
throw new Error("HTML artifact exceeds the 256 KiB limit");
|
|
151
|
+
}
|
|
142
152
|
return html;
|
|
143
153
|
}
|
|
144
154
|
|
|
@@ -165,18 +175,24 @@ function readUploadResult(stdout, requestedName) {
|
|
|
165
175
|
throw new Error("lark-cli upload returned invalid JSON");
|
|
166
176
|
}
|
|
167
177
|
const result = isRecord(parsed?.data) ? parsed.data : parsed;
|
|
168
|
-
if (!isRecord(result))
|
|
178
|
+
if (!isRecord(result)) {
|
|
179
|
+
throw new Error("lark-cli upload returned an invalid result");
|
|
180
|
+
}
|
|
169
181
|
const fileToken = readFirstString(result, ["file_token", "fileToken", "token"]);
|
|
170
182
|
const url = readFirstString(result, ["url", "web_url", "file_url"]);
|
|
171
183
|
const name = readFirstString(result, ["name", "file_name"]) ?? requestedName;
|
|
172
|
-
if (!fileToken || !url)
|
|
184
|
+
if (!fileToken || !url) {
|
|
185
|
+
throw new Error("lark-cli upload result is missing file token or URL");
|
|
186
|
+
}
|
|
173
187
|
return Object.freeze({ fileToken, name, url });
|
|
174
188
|
}
|
|
175
189
|
|
|
176
190
|
function readFirstString(value, keys) {
|
|
177
191
|
for (const key of keys) {
|
|
178
192
|
const candidate = value[key];
|
|
179
|
-
if (typeof candidate === "string" && candidate.trim())
|
|
193
|
+
if (typeof candidate === "string" && candidate.trim()) {
|
|
194
|
+
return candidate.trim();
|
|
195
|
+
}
|
|
180
196
|
}
|
|
181
197
|
return undefined;
|
|
182
198
|
}
|
|
@@ -191,7 +207,9 @@ function resolveArtifactRoot(cwd, artifactRoot = DEFAULT_ARTIFACT_ROOT) {
|
|
|
191
207
|
}
|
|
192
208
|
|
|
193
209
|
function resolveAgentDirectory(artifactRoot, agentId) {
|
|
194
|
-
if (typeof agentId !== "string" || !agentId.trim())
|
|
210
|
+
if (typeof agentId !== "string" || !agentId.trim()) {
|
|
211
|
+
throw new Error("HTML artifact tool requires an agent id");
|
|
212
|
+
}
|
|
195
213
|
return resolve(artifactRoot, createHash("sha256").update(agentId).digest("hex").slice(0, 16));
|
|
196
214
|
}
|
|
197
215
|
|
|
@@ -218,7 +236,9 @@ async function resolveTrustedDirectory(cwd, directory, create) {
|
|
|
218
236
|
if (metadata.isSymbolicLink()) {
|
|
219
237
|
throw new Error("HTML artifact directory cannot contain symbolic links");
|
|
220
238
|
}
|
|
221
|
-
if (!metadata.isDirectory())
|
|
239
|
+
if (!metadata.isDirectory()) {
|
|
240
|
+
throw new Error("HTML artifact path component is not a directory");
|
|
241
|
+
}
|
|
222
242
|
}
|
|
223
243
|
const [workspacePath, directoryPath] = await Promise.all([realpath(lexicalWorkspace), realpath(lexicalDirectory)]);
|
|
224
244
|
const resolvedRelativePath = relative(workspacePath, directoryPath);
|
|
@@ -232,12 +252,16 @@ async function readOrCreateDirectory(directory, create) {
|
|
|
232
252
|
try {
|
|
233
253
|
return await lstat(directory);
|
|
234
254
|
} catch (error) {
|
|
235
|
-
if (!isMissingFile(error) || !create)
|
|
255
|
+
if (!isMissingFile(error) || !create) {
|
|
256
|
+
throw error;
|
|
257
|
+
}
|
|
236
258
|
}
|
|
237
259
|
try {
|
|
238
260
|
await mkdir(directory);
|
|
239
261
|
} catch (error) {
|
|
240
|
-
if (!isFileAlreadyPresent(error))
|
|
262
|
+
if (!isFileAlreadyPresent(error)) {
|
|
263
|
+
throw error;
|
|
264
|
+
}
|
|
241
265
|
}
|
|
242
266
|
return lstat(directory);
|
|
243
267
|
}
|
|
@@ -1,3 +1 @@
|
|
|
1
|
-
|
|
2
|
-
// the implementation remains in the Gateway package canonical owner.
|
|
3
|
-
export { createRivusDeploymentAdapters } from "@rivus/agent/bootstrap/pi-feishu";
|
|
1
|
+
export { createRivusDeploymentAdapters } from "@rivus/gateway/bootstrap/pi-feishu";
|
|
@@ -1,242 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { homedir } from "node:os";
|
|
3
|
-
import { Effect } from "effect";
|
|
4
|
-
import * as Lark from "@larksuiteoapi/node-sdk";
|
|
5
|
-
import { createAgentSession, ModelRuntime, SessionManager } from "@earendil-works/pi-coding-agent";
|
|
6
|
-
import {
|
|
7
|
-
createJsonFetchRequest,
|
|
8
|
-
createJsonFileFeishuCardTargetRegistry,
|
|
9
|
-
createJsonlAgentEventLog,
|
|
10
|
-
createLazyFeishuWebSocketEventDispatcher,
|
|
11
|
-
mergePiProviderBaseUrlOverride,
|
|
12
|
-
createRivusDaemonStatusHttpServer,
|
|
13
|
-
createPiAgentLoop,
|
|
14
|
-
createPiSessionRegistry,
|
|
15
|
-
createSystemClock,
|
|
16
|
-
createUuidRunIds,
|
|
17
|
-
restoreConfiguredRivusDaemonBootstrap,
|
|
18
|
-
type ConfiguredRivusDaemonBootstrapRequest,
|
|
19
|
-
type ConfiguredRivusDaemonBootstrapResponse,
|
|
20
|
-
type FeishuWebSocketClient,
|
|
21
|
-
type RivusDaemonBootstrapContext,
|
|
22
|
-
type RivusDaemonFeishuReplayRunner,
|
|
23
|
-
type RivusDaemonProcess,
|
|
24
|
-
type RivusDaemonPromptRunner,
|
|
25
|
-
type RivusDaemonStatusReporter,
|
|
26
|
-
type FeishuReceiveMessagePayload,
|
|
27
|
-
type FeishuReceiveMessageReplayOptions
|
|
28
|
-
} from "@rivus/agent";
|
|
29
|
-
import {
|
|
30
|
-
createPiSkillReadTools,
|
|
31
|
-
createPiSessionResources,
|
|
32
|
-
resolvePiSessionToolNames,
|
|
33
|
-
validatePiSkillCommand
|
|
34
|
-
} from "@rivus/agent/pi";
|
|
35
|
-
|
|
36
|
-
type PiSessionOptions = NonNullable<Parameters<typeof createAgentSession>[0]>;
|
|
37
|
-
|
|
38
|
-
const STATE_DIR = "./.rivus";
|
|
39
|
-
const CARD_TARGETS_FILE = `${STATE_DIR}/feishu-card-targets.json`;
|
|
40
|
-
const EVENT_LOG_FILE = `${STATE_DIR}/agent-events.jsonl`;
|
|
41
|
-
const PI_AUTH_FILE = `${STATE_DIR}/pi-auth.json`;
|
|
42
|
-
const PI_MODELS_FILE = `${STATE_DIR}/pi-models.json`;
|
|
43
|
-
const PI_AGENT_DIR = `${STATE_DIR}/pi-agent`;
|
|
44
|
-
|
|
45
|
-
export async function createRivusDaemonProcess(
|
|
46
|
-
context: RivusDaemonBootstrapContext
|
|
47
|
-
): Promise<RivusDaemonProcess & RivusDaemonPromptRunner & RivusDaemonStatusReporter & RivusDaemonFeishuReplayRunner> {
|
|
48
|
-
await mkdir(PI_AGENT_DIR, { recursive: true });
|
|
49
|
-
|
|
50
|
-
const piSessionOptions = await createPiSessionOptions(context);
|
|
51
|
-
const sessionRegistry = createPiSessionRegistry({
|
|
52
|
-
createSession: async () => {
|
|
53
|
-
const cwd = process.cwd();
|
|
54
|
-
const resources = await createPiSessionResources({
|
|
55
|
-
agentDir: PI_AGENT_DIR,
|
|
56
|
-
cwd,
|
|
57
|
-
homeDirectory: homedir()
|
|
58
|
-
});
|
|
59
|
-
const customTools = createPiSkillReadTools({
|
|
60
|
-
cwd,
|
|
61
|
-
runtimeToolIds: [],
|
|
62
|
-
skillPaths: resources.skillNames.size > 0 ? resources.skillPaths : []
|
|
63
|
-
});
|
|
64
|
-
const result = await createAgentSession(
|
|
65
|
-
resources.withSessionOptions({
|
|
66
|
-
...piSessionOptions,
|
|
67
|
-
customTools,
|
|
68
|
-
excludeTools: [],
|
|
69
|
-
sessionManager: SessionManager.create(cwd),
|
|
70
|
-
tools: [...resolvePiSessionToolNames([], customTools)]
|
|
71
|
-
})
|
|
72
|
-
);
|
|
73
|
-
return {
|
|
74
|
-
dispose: () => result.session.dispose(),
|
|
75
|
-
preparePrompt: (input) => {
|
|
76
|
-
validatePiSkillCommand(input.text, resources.skillNames);
|
|
77
|
-
return input.text;
|
|
78
|
-
},
|
|
79
|
-
session: result.session
|
|
80
|
-
};
|
|
81
|
-
}
|
|
82
|
-
});
|
|
83
|
-
const piLoop = createPiAgentLoop({
|
|
84
|
-
supportsSteering: true,
|
|
85
|
-
disposeSessionAfterRun: false,
|
|
86
|
-
resolveSession: (input) => sessionRegistry.resolve(input)
|
|
87
|
-
});
|
|
88
|
-
const statusPort = readOptionalPort(context.env.RIVUS_STATUS_PORT);
|
|
89
|
-
const request = createJsonFetchRequest();
|
|
90
|
-
|
|
91
|
-
const bootstrap = await Effect.runPromise(
|
|
92
|
-
restoreConfiguredRivusDaemonBootstrap({
|
|
93
|
-
cardTargets: createJsonFileFeishuCardTargetRegistry({
|
|
94
|
-
filePath: CARD_TARGETS_FILE
|
|
95
|
-
}),
|
|
96
|
-
clock: createSystemClock(),
|
|
97
|
-
config: context.config,
|
|
98
|
-
...(statusPort === undefined
|
|
99
|
-
? {}
|
|
100
|
-
: {
|
|
101
|
-
createStatusTransport: (statusReporter: RivusDaemonStatusReporter) =>
|
|
102
|
-
createRivusDaemonStatusHttpServer({
|
|
103
|
-
port: statusPort,
|
|
104
|
-
statusReporter
|
|
105
|
-
})
|
|
106
|
-
}),
|
|
107
|
-
eventDispatcher: createLazyFeishuWebSocketEventDispatcher(() => new Lark.EventDispatcher({})),
|
|
108
|
-
eventLog: createJsonlAgentEventLog({
|
|
109
|
-
filePath: EVENT_LOG_FILE
|
|
110
|
-
}),
|
|
111
|
-
loop: piLoop,
|
|
112
|
-
request: (input: ConfiguredRivusDaemonBootstrapRequest) =>
|
|
113
|
-
request(input).pipe(Effect.map((response) => response as ConfiguredRivusDaemonBootstrapResponse)),
|
|
114
|
-
runIds: createUuidRunIds(),
|
|
115
|
-
sleep: (ms) => Effect.promise(() => new Promise<void>((resolve) => setTimeout(resolve, ms))),
|
|
116
|
-
websocketClient: createLazyFeishuWebSocketClient({
|
|
117
|
-
appId: context.config.feishu.appId,
|
|
118
|
-
appSecret: context.config.feishu.appSecret
|
|
119
|
-
})
|
|
120
|
-
})
|
|
121
|
-
);
|
|
122
|
-
|
|
123
|
-
return {
|
|
124
|
-
promptText: (command) => bootstrap.promptText(command),
|
|
125
|
-
replayReceiveMessage: (payload: FeishuReceiveMessagePayload, options?: FeishuReceiveMessageReplayOptions) =>
|
|
126
|
-
bootstrap.runtime.replayReceiveMessage(payload, options),
|
|
127
|
-
running: () => bootstrap.process.running(),
|
|
128
|
-
start: () => bootstrap.process.start(),
|
|
129
|
-
status: () => bootstrap.status(),
|
|
130
|
-
stop: () =>
|
|
131
|
-
Effect.gen(function* () {
|
|
132
|
-
yield* bootstrap.process.stop();
|
|
133
|
-
yield* Effect.tryPromise({
|
|
134
|
-
try: () => sessionRegistry.disposeAll(),
|
|
135
|
-
catch: (error) => error
|
|
136
|
-
});
|
|
137
|
-
})
|
|
138
|
-
};
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
function createLazyFeishuWebSocketClient(options: {
|
|
142
|
-
readonly appId: string;
|
|
143
|
-
readonly appSecret: string;
|
|
144
|
-
}): FeishuWebSocketClient {
|
|
145
|
-
let client:
|
|
146
|
-
| {
|
|
147
|
-
close(): void;
|
|
148
|
-
start(options: Parameters<FeishuWebSocketClient["start"]>[0]): Promise<void> | void;
|
|
149
|
-
}
|
|
150
|
-
| undefined;
|
|
151
|
-
|
|
152
|
-
return {
|
|
153
|
-
start: (startOptions) => {
|
|
154
|
-
client ??= new Lark.WSClient({
|
|
155
|
-
appId: options.appId,
|
|
156
|
-
appSecret: options.appSecret
|
|
157
|
-
});
|
|
158
|
-
return client.start(startOptions);
|
|
159
|
-
},
|
|
160
|
-
close: () => {
|
|
161
|
-
client?.close();
|
|
162
|
-
client = undefined;
|
|
163
|
-
}
|
|
164
|
-
};
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
async function createPiSessionOptions(context: RivusDaemonBootstrapContext): Promise<PiSessionOptions> {
|
|
168
|
-
const provider = readProviderFromModel(context.config.pi.model);
|
|
169
|
-
|
|
170
|
-
if (context.config.pi.baseUrl) {
|
|
171
|
-
if (!provider) {
|
|
172
|
-
throw new Error("PI_BASE_URL requires PI_MODEL in provider/model form, for example zai/glm-5.2");
|
|
173
|
-
}
|
|
174
|
-
await writeProviderBaseUrlOverride(provider, context.config.pi.baseUrl);
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
const modelRuntime = await ModelRuntime.create({
|
|
178
|
-
allowModelNetwork: false,
|
|
179
|
-
authPath: PI_AUTH_FILE,
|
|
180
|
-
modelsPath: context.config.pi.baseUrl ? PI_MODELS_FILE : null
|
|
181
|
-
});
|
|
182
|
-
if (context.config.pi.apiKey) {
|
|
183
|
-
if (!provider) {
|
|
184
|
-
throw new Error("PI_API_KEY requires PI_MODEL in provider/model form, for example zai/glm-5.2");
|
|
185
|
-
}
|
|
186
|
-
await modelRuntime.setRuntimeApiKey(provider, context.config.pi.apiKey);
|
|
187
|
-
}
|
|
188
|
-
const model = context.config.pi.model ? resolveConfiguredPiModel(modelRuntime, context.config.pi.model) : undefined;
|
|
189
|
-
return {
|
|
190
|
-
cwd: process.cwd(),
|
|
191
|
-
modelRuntime,
|
|
192
|
-
...(model ? { model } : {}),
|
|
193
|
-
...(context.config.pi.thinkingLevel ? { thinkingLevel: context.config.pi.thinkingLevel } : {})
|
|
194
|
-
};
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
function resolveConfiguredPiModel(modelRuntime: ModelRuntime, modelReference: string): PiSessionOptions["model"] {
|
|
198
|
-
const provider = readProviderFromModel(modelReference);
|
|
199
|
-
const modelId = readModelIdFromModel(modelReference);
|
|
200
|
-
if (!provider || !modelId) {
|
|
201
|
-
throw new Error("PI_MODEL must use provider/model form, for example zai/glm-5.2");
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
const model = modelRuntime.getModel(provider, modelId);
|
|
205
|
-
if (!model) {
|
|
206
|
-
throw new Error(`PI_MODEL ${modelReference} was not found in the Pi model runtime`);
|
|
207
|
-
}
|
|
208
|
-
return model as PiSessionOptions["model"];
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
function readProviderFromModel(model: string | undefined): string | undefined {
|
|
212
|
-
const slashIndex = model?.indexOf("/") ?? -1;
|
|
213
|
-
if (!model || slashIndex <= 0) {
|
|
214
|
-
return undefined;
|
|
215
|
-
}
|
|
216
|
-
return model.slice(0, slashIndex);
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
function readModelIdFromModel(model: string | undefined): string | undefined {
|
|
220
|
-
const slashIndex = model?.indexOf("/") ?? -1;
|
|
221
|
-
if (!model || slashIndex < 0 || slashIndex === model.length - 1) {
|
|
222
|
-
return undefined;
|
|
223
|
-
}
|
|
224
|
-
return model.slice(slashIndex + 1);
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
function readOptionalPort(value: string | undefined): number | undefined {
|
|
228
|
-
const trimmed = value?.trim();
|
|
229
|
-
if (!trimmed) {
|
|
230
|
-
return undefined;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
const port = Number(trimmed);
|
|
234
|
-
if (!Number.isInteger(port) || port < 0 || port > 65_535) {
|
|
235
|
-
throw new Error("RIVUS_STATUS_PORT must be an integer between 0 and 65535");
|
|
236
|
-
}
|
|
237
|
-
return port;
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
async function writeProviderBaseUrlOverride(provider: string, baseUrl: string): Promise<void> {
|
|
241
|
-
await mergePiProviderBaseUrlOverride({ baseUrl, filePath: PI_MODELS_FILE, provider });
|
|
242
|
-
}
|
|
1
|
+
export { createRivusDaemonProcess } from "@rivus/gateway/bootstrap/pi-feishu";
|
|
@@ -281,7 +281,9 @@ async function saveNote(input, context) {
|
|
|
281
281
|
try {
|
|
282
282
|
await writeFile(filePath, content, { encoding: "utf8", flag: "wx" });
|
|
283
283
|
} catch (error) {
|
|
284
|
-
if (!(error instanceof Error) || !("code" in error) || error.code !== "EEXIST")
|
|
284
|
+
if (!(error instanceof Error) || !("code" in error) || error.code !== "EEXIST") {
|
|
285
|
+
throw error;
|
|
286
|
+
}
|
|
285
287
|
if ((await readFile(filePath, "utf8")) !== content) {
|
|
286
288
|
throw new Error("operation id is already bound to different note content");
|
|
287
289
|
}
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
//#region src/core/application/background-session/control/background-session-control-input.ts
|
|
2
|
-
function readBackgroundSessionObject(input, allowed, error) {
|
|
3
|
-
if (input === null || typeof input !== "object" || Array.isArray(input)) throw error("background tool input must be an object");
|
|
4
|
-
const record = input;
|
|
5
|
-
const unknown = Object.keys(record).find((key) => !allowed.includes(key));
|
|
6
|
-
if (unknown) throw error(`field is not allowed: ${unknown}`);
|
|
7
|
-
return record;
|
|
8
|
-
}
|
|
9
|
-
function readBackgroundSessionString(value, name, error) {
|
|
10
|
-
if (typeof value !== "string" || value.trim() === "") throw error(`${name} must be a non-empty string`);
|
|
11
|
-
return value;
|
|
12
|
-
}
|
|
13
|
-
function readBackgroundSessionInteger(value, name, error) {
|
|
14
|
-
if (!Number.isSafeInteger(value) || value < 1) throw error(`${name} must be a positive integer`);
|
|
15
|
-
return value;
|
|
16
|
-
}
|
|
17
|
-
function readBackgroundSessionPhase(value, error) {
|
|
18
|
-
const phases = [
|
|
19
|
-
"queued",
|
|
20
|
-
"running",
|
|
21
|
-
"waiting",
|
|
22
|
-
"input-required",
|
|
23
|
-
"stopping",
|
|
24
|
-
"stopped",
|
|
25
|
-
"completed",
|
|
26
|
-
"failed",
|
|
27
|
-
"reconciliation-required"
|
|
28
|
-
];
|
|
29
|
-
if (typeof value !== "string" || !phases.includes(value)) throw error(`phase must be one of ${phases.join(", ")}`);
|
|
30
|
-
return value;
|
|
31
|
-
}
|
|
32
|
-
function readBackgroundSessionWaitInput(input, error) {
|
|
33
|
-
const { delayMs, reason, until } = readBackgroundSessionObject(input, [
|
|
34
|
-
"delayMs",
|
|
35
|
-
"reason",
|
|
36
|
-
"until"
|
|
37
|
-
], error);
|
|
38
|
-
const args = {};
|
|
39
|
-
if (delayMs !== void 0) args.delayMs = readBackgroundSessionInteger(delayMs, "delayMs", error);
|
|
40
|
-
if (reason !== void 0) args.reason = readBackgroundSessionString(reason, "reason", error);
|
|
41
|
-
if (until !== void 0) args.until = readBackgroundSessionString(until, "until", error);
|
|
42
|
-
return args;
|
|
43
|
-
}
|
|
44
|
-
//#endregion
|
|
45
|
-
export { readBackgroundSessionWaitInput as a, readBackgroundSessionString as i, readBackgroundSessionObject as n, readBackgroundSessionPhase as r, readBackgroundSessionInteger as t };
|