@hirey/hi-mcp-server 0.1.10 → 0.1.12
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/README.md +5 -4
- package/dist/defaultReplyRoute.d.ts +11 -0
- package/dist/defaultReplyRoute.d.ts.map +1 -1
- package/dist/defaultReplyRoute.js +15 -0
- package/dist/receiver-config-material.d.ts +4 -0
- package/dist/receiver-config-material.d.ts.map +1 -0
- package/dist/receiver-config-material.js +47 -0
- package/dist/server.js +108 -19
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -91,15 +91,16 @@ npm install -g @hirey/hi-mcp-server
|
|
|
91
91
|
1. 用宿主把 `hi-mcp-server` 作为 `stdio` MCP server 挂上
|
|
92
92
|
2. 优先执行 `hi_agent_install`,而不是让用户手工走 `register -> connect -> activate`
|
|
93
93
|
3. `hi_agent_install` 会自动完成 register / activate、delivery capability 声明、默认订阅,以及在 OpenClaw 场景下的 receiver 配置
|
|
94
|
-
4. `
|
|
95
|
-
5.
|
|
96
|
-
6.
|
|
94
|
+
4. 当 receiver 的物料配置(例如 hooks token、adapter URL、transport、default reply route)发生变化时,`hi_agent_install` 会先停止当前 profile 记录的 receiver,再写入新配置并按需要重新启动;如果配置没变,则保留现有 runtime cursor,不重置消费进度
|
|
95
|
+
5. `agent_id` 可以省略;省略时由 gateway 正式生成 canonical `ag_...`,不需要用户手写
|
|
96
|
+
6. 完成后执行 `hi_agent_doctor`
|
|
97
|
+
7. 如果要 clean reinstall,先执行 `hi_agent_reset`;`hi_agent_reset` 现在会等待当前 profile 记录的 receiver 真实退出,再删除本地 state / receiver config,避免立刻重装时旧进程把旧配置写回去
|
|
97
98
|
|
|
98
99
|
如果宿主不能直接执行裸命令 `hi-agent-receiver`,而是需要 `node /path/to/dist/server.js`、`npx ...` 或其它显式 argv 前缀,`hi_agent_install` 现在也支持把 receiver 启动入口写成 `receiver_command_argv` 数组;`hi-mcp-server` 会在这组 argv 后自动追加 `run --config <path>`,不再把整段命令误当成单个可执行文件。
|
|
99
100
|
|
|
100
101
|
对 OpenClaw 的普通用户本地 vendor 安装,不要显式传 `receiver_command="hi-agent-receiver"` 或 `receiver_command_argv=["hi-agent-receiver"]`。这两种旧形态都会重新把启动语义写回“按 PATH 找全局命令”。OpenClaw 要么不传 receiver 启动覆盖,让安装流默认落到 `~/.openclaw/vendor/hi/node_modules/.bin/hi-agent-receiver`,要么只传指向该本地 vendor binary 的精确 `receiver_command_argv`。
|
|
101
102
|
|
|
102
|
-
|
|
103
|
+
对 OpenClaw 的普通用户安装,默认就应该把当前聊天绑成 Hi 以后回来的默认线程。因此 `hi_agent_install` 应直接带上 `host_session_key`、`default_reply_channel`、`default_reply_to`、`default_reply_account_id`、`default_reply_thread_id`、`route_missing_policy`。这组字段会同时写进 installation 的 `delivery_capabilities` 和本地 receiver config,避免出现 gateway 已经知道默认 route、但本地 receiver 还沿旧配置投递的漂移。如果拿不到当前聊天的 canonical `host_session_key`,OpenClaw 安装不应宣告成功,而应先把 continuity blocker 说明白。
|
|
103
104
|
|
|
104
105
|
安装后如果要显式验证 continuation route,可以用 `hi_agent_test_delivery`:既可以直接传 `host_session_key` / `reply_*` 做一次显式 route probe,也可以在 installation 已设置 `default_reply_route` 时不传这些字段,让 gateway 自动沿当前 installation 的默认 route 发 probe。
|
|
105
106
|
|
|
@@ -12,4 +12,15 @@ export declare function resolveInstallDefaultReplyDeliveryContext(args: {
|
|
|
12
12
|
defaultReplyAccountId?: unknown;
|
|
13
13
|
defaultReplyThreadId?: unknown;
|
|
14
14
|
}): DefaultReplyDeliveryContext | null;
|
|
15
|
+
export declare function resolveInstallRouteMissingPolicy(args: {
|
|
16
|
+
hostKind: 'openclaw' | 'generic';
|
|
17
|
+
explicitRouteMissingPolicy?: unknown;
|
|
18
|
+
defaultReplyRoute?: Record<string, unknown> | null;
|
|
19
|
+
}): {
|
|
20
|
+
ok: true;
|
|
21
|
+
routeMissingPolicy: string;
|
|
22
|
+
} | {
|
|
23
|
+
ok: false;
|
|
24
|
+
error: 'missing_openclaw_default_reply_route';
|
|
25
|
+
};
|
|
15
26
|
//# sourceMappingURL=defaultReplyRoute.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"defaultReplyRoute.d.ts","sourceRoot":"","sources":["../src/defaultReplyRoute.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,2BAA2B,GAAG;IACxC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IAClB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B,CAAC;AAEF,wBAAgB,yCAAyC,CAAC,IAAI,EAAE;IAC9D,QAAQ,EAAE,UAAU,GAAG,SAAS,CAAC;IACjC,aAAa,EAAE,OAAO,CAAC;IACvB,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,oBAAoB,CAAC,EAAE,OAAO,CAAC;CAChC,GAAG,2BAA2B,GAAG,IAAI,CAyBrC"}
|
|
1
|
+
{"version":3,"file":"defaultReplyRoute.d.ts","sourceRoot":"","sources":["../src/defaultReplyRoute.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,2BAA2B,GAAG;IACxC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IAClB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B,CAAC;AAEF,wBAAgB,yCAAyC,CAAC,IAAI,EAAE;IAC9D,QAAQ,EAAE,UAAU,GAAG,SAAS,CAAC;IACjC,aAAa,EAAE,OAAO,CAAC;IACvB,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,oBAAoB,CAAC,EAAE,OAAO,CAAC;CAChC,GAAG,2BAA2B,GAAG,IAAI,CAyBrC;AAED,wBAAgB,gCAAgC,CAAC,IAAI,EAAE;IACrD,QAAQ,EAAE,UAAU,GAAG,SAAS,CAAC;IACjC,0BAA0B,CAAC,EAAE,OAAO,CAAC;IACrC,iBAAiB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;CACpD,GAAG;IACF,EAAE,EAAE,IAAI,CAAC;IACT,kBAAkB,EAAE,MAAM,CAAC;CAC5B,GAAG;IACF,EAAE,EAAE,KAAK,CAAC;IACV,KAAK,EAAE,sCAAsC,CAAC;CAC/C,CAcA"}
|
|
@@ -26,3 +26,18 @@ export function resolveInstallDefaultReplyDeliveryContext(args) {
|
|
|
26
26
|
}
|
|
27
27
|
return null;
|
|
28
28
|
}
|
|
29
|
+
export function resolveInstallRouteMissingPolicy(args) {
|
|
30
|
+
const explicitRouteMissingPolicy = normalizeText(args.explicitRouteMissingPolicy);
|
|
31
|
+
// OpenClaw 安装默认就要把当前聊天绑成默认续聊入口;否则安装不该继续宣告成功。
|
|
32
|
+
if (args.hostKind === 'openclaw' && !args.defaultReplyRoute) {
|
|
33
|
+
return {
|
|
34
|
+
ok: false,
|
|
35
|
+
error: 'missing_openclaw_default_reply_route',
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
return {
|
|
39
|
+
ok: true,
|
|
40
|
+
routeMissingPolicy: explicitRouteMissingPolicy
|
|
41
|
+
|| (args.defaultReplyRoute ? 'use_explicit_default_route' : ''),
|
|
42
|
+
};
|
|
43
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare function receiverConfigMaterialSnapshot(raw: unknown): Record<string, unknown> | null;
|
|
2
|
+
export declare function receiverConfigMaterialEquals(left: unknown, right: unknown): boolean;
|
|
3
|
+
export declare function applyReceiverRuntimeSnapshot(nextConfig: Record<string, unknown>, existingConfig: unknown): Record<string, unknown>;
|
|
4
|
+
//# sourceMappingURL=receiver-config-material.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"receiver-config-material.d.ts","sourceRoot":"","sources":["../src/receiver-config-material.ts"],"names":[],"mappings":"AAcA,wBAAgB,8BAA8B,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAQ3F;AAED,wBAAgB,4BAA4B,CAAC,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAEnF;AAED,wBAAgB,4BAA4B,CAC1C,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACnC,cAAc,EAAE,OAAO,GACtB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAkBzB"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
function isPlainObject(value) {
|
|
2
|
+
return !!value && typeof value === 'object' && !Array.isArray(value);
|
|
3
|
+
}
|
|
4
|
+
function canonicalizeJson(value) {
|
|
5
|
+
if (Array.isArray(value))
|
|
6
|
+
return value.map((entry) => canonicalizeJson(entry));
|
|
7
|
+
if (!isPlainObject(value))
|
|
8
|
+
return value;
|
|
9
|
+
const out = {};
|
|
10
|
+
for (const key of Object.keys(value).sort()) {
|
|
11
|
+
out[key] = canonicalizeJson(value[key]);
|
|
12
|
+
}
|
|
13
|
+
return out;
|
|
14
|
+
}
|
|
15
|
+
export function receiverConfigMaterialSnapshot(raw) {
|
|
16
|
+
if (!isPlainObject(raw))
|
|
17
|
+
return null;
|
|
18
|
+
const snapshot = JSON.parse(JSON.stringify(raw));
|
|
19
|
+
delete snapshot.runtime;
|
|
20
|
+
delete snapshot._generated_at;
|
|
21
|
+
delete snapshot._generated_by;
|
|
22
|
+
delete snapshot._receiver_config_path;
|
|
23
|
+
return canonicalizeJson(snapshot);
|
|
24
|
+
}
|
|
25
|
+
export function receiverConfigMaterialEquals(left, right) {
|
|
26
|
+
return JSON.stringify(receiverConfigMaterialSnapshot(left)) === JSON.stringify(receiverConfigMaterialSnapshot(right));
|
|
27
|
+
}
|
|
28
|
+
export function applyReceiverRuntimeSnapshot(nextConfig, existingConfig) {
|
|
29
|
+
if (!isPlainObject(existingConfig) || !isPlainObject(existingConfig.runtime))
|
|
30
|
+
return nextConfig;
|
|
31
|
+
const runtime = existingConfig.runtime;
|
|
32
|
+
return {
|
|
33
|
+
...nextConfig,
|
|
34
|
+
// 安装修复时保留已消费 cursor,避免仅因重写物料配置就把 receiver 从头回放。
|
|
35
|
+
runtime: {
|
|
36
|
+
last_consumed_stream_seq: Number.isFinite(Number(runtime.last_consumed_stream_seq))
|
|
37
|
+
? Math.max(0, Number(runtime.last_consumed_stream_seq))
|
|
38
|
+
: 0,
|
|
39
|
+
last_claim_lease_id: typeof runtime.last_claim_lease_id === 'string'
|
|
40
|
+
? runtime.last_claim_lease_id
|
|
41
|
+
: null,
|
|
42
|
+
updated_at: typeof runtime.updated_at === 'string'
|
|
43
|
+
? runtime.updated_at
|
|
44
|
+
: null,
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
}
|
package/dist/server.js
CHANGED
|
@@ -14,8 +14,11 @@ import { createHiAgentClients, exchangeHiAgentClientCredentialsToken, HiAgentGat
|
|
|
14
14
|
import { readState, resolveCanonicalOpenClawStateDir, resolveDefaultStateDir, resolveLegacyStateFiles, resolveStateFile, updateState, normalizeStateProfile, } from './state.js';
|
|
15
15
|
import { looksLikeOpenClawSessionKey, validateOpenClawSessionKey, } from './openclaw-session-key.js';
|
|
16
16
|
import { buildInstallReceiverCommandArgv, } from './receiver-command.js';
|
|
17
|
-
import {
|
|
17
|
+
import { applyReceiverRuntimeSnapshot, receiverConfigMaterialEquals, } from './receiver-config-material.js';
|
|
18
|
+
import { resolveInstallDefaultReplyDeliveryContext, resolveInstallRouteMissingPolicy, } from './defaultReplyRoute.js';
|
|
18
19
|
const CAPABILITY_CACHE_TTL_MS = 30_000;
|
|
20
|
+
const RECEIVER_STOP_TIMEOUT_MS = 3_000;
|
|
21
|
+
const RECEIVER_STOP_POLL_MS = 100;
|
|
19
22
|
const resolvedProfile = normalizeStateProfile(process.env.HI_MCP_PROFILE);
|
|
20
23
|
const config = {
|
|
21
24
|
host: normalizeText(process.env.HI_MCP_HOST) || '127.0.0.1',
|
|
@@ -110,8 +113,8 @@ function controlTools() {
|
|
|
110
113
|
host_adapter_url: { type: 'string', description: 'enable_local_receiver=true 时可选:宿主本地接收入口。OpenClaw 默认 full endpoint 为 http://127.0.0.1:18789/hooks/agent;注意 OpenClaw 配置里的 hooks.path 应保持 /hooks,而不是 /hooks/agent。' },
|
|
111
114
|
host_adapter_bearer_token: { type: 'string', description: 'host_adapter_kind=openclaw_hooks 时必填:本地 hooks bearer token。' },
|
|
112
115
|
openresponses_model: { type: 'string', description: 'host_adapter_kind=openresponses 时必填:receiver 发给本地入口使用的 model。' },
|
|
113
|
-
host_session_key: { type: 'string', description: '
|
|
114
|
-
route_missing_policy: { type: 'string', description: "可选:'use_explicit_default_route'|'fail_closed'
|
|
116
|
+
host_session_key: { type: 'string', description: 'OpenClaw 安装默认应传:把当前 OpenClaw 当前会话设为 default continuation route 的 canonical full session_key。只能使用结构化宿主来源返回的完整 key;不要从 openclaw status / openclaw sessions / TUI footer 这类展示文本里抄值。' },
|
|
117
|
+
route_missing_policy: { type: 'string', description: "可选:'use_explicit_default_route'|'fail_closed'。OpenClaw 安装默认要求当前聊天 ready,因此在提供 host_session_key/default_reply_* 时应使用 use_explicit_default_route;缺失 default route 时安装会直接失败,而不是继续留下 continuity_not_ready。" },
|
|
115
118
|
default_reply_channel: { type: 'string', description: '可选:default continuation route 的 channel。' },
|
|
116
119
|
default_reply_to: { type: 'string', description: '可选:default continuation route 的宿主 target。' },
|
|
117
120
|
default_reply_account_id: { type: 'string', description: '可选:default continuation route 的 account_id。' },
|
|
@@ -724,6 +727,58 @@ function isProcessAlive(pid) {
|
|
|
724
727
|
return false;
|
|
725
728
|
}
|
|
726
729
|
}
|
|
730
|
+
async function waitForProcessExit(pid, timeoutMs = RECEIVER_STOP_TIMEOUT_MS) {
|
|
731
|
+
const deadline = Date.now() + timeoutMs;
|
|
732
|
+
while (Date.now() < deadline) {
|
|
733
|
+
if (!isProcessAlive(pid))
|
|
734
|
+
return true;
|
|
735
|
+
await sleep(RECEIVER_STOP_POLL_MS);
|
|
736
|
+
}
|
|
737
|
+
return !isProcessAlive(pid);
|
|
738
|
+
}
|
|
739
|
+
async function stopTrackedReceiverProcess(pidRaw) {
|
|
740
|
+
const pid = Number(pidRaw || 0);
|
|
741
|
+
if (!Number.isInteger(pid) || pid <= 0) {
|
|
742
|
+
return {
|
|
743
|
+
attempted: false,
|
|
744
|
+
pid: null,
|
|
745
|
+
signal: null,
|
|
746
|
+
exited: true,
|
|
747
|
+
timeout_ms: RECEIVER_STOP_TIMEOUT_MS,
|
|
748
|
+
};
|
|
749
|
+
}
|
|
750
|
+
if (!isProcessAlive(pid)) {
|
|
751
|
+
return {
|
|
752
|
+
attempted: true,
|
|
753
|
+
pid,
|
|
754
|
+
signal: null,
|
|
755
|
+
exited: true,
|
|
756
|
+
timeout_ms: RECEIVER_STOP_TIMEOUT_MS,
|
|
757
|
+
};
|
|
758
|
+
}
|
|
759
|
+
try {
|
|
760
|
+
process.kill(pid, 'SIGTERM');
|
|
761
|
+
}
|
|
762
|
+
catch (error) {
|
|
763
|
+
if (error?.code === 'ESRCH') {
|
|
764
|
+
return {
|
|
765
|
+
attempted: true,
|
|
766
|
+
pid,
|
|
767
|
+
signal: 'SIGTERM',
|
|
768
|
+
exited: true,
|
|
769
|
+
timeout_ms: RECEIVER_STOP_TIMEOUT_MS,
|
|
770
|
+
};
|
|
771
|
+
}
|
|
772
|
+
throw error;
|
|
773
|
+
}
|
|
774
|
+
return {
|
|
775
|
+
attempted: true,
|
|
776
|
+
pid,
|
|
777
|
+
signal: 'SIGTERM',
|
|
778
|
+
exited: await waitForProcessExit(pid),
|
|
779
|
+
timeout_ms: RECEIVER_STOP_TIMEOUT_MS,
|
|
780
|
+
};
|
|
781
|
+
}
|
|
727
782
|
async function startDetachedReceiver(args) {
|
|
728
783
|
const [command, ...prefixArgs] = args.receiverCommandArgv;
|
|
729
784
|
if (!command)
|
|
@@ -1206,8 +1261,19 @@ async function handleInstall(args) {
|
|
|
1206
1261
|
requireOpenClawSessionKey: hostKind === 'openclaw',
|
|
1207
1262
|
hostKind,
|
|
1208
1263
|
});
|
|
1209
|
-
const
|
|
1210
|
-
|
|
1264
|
+
const routeMissingPolicyResolution = resolveInstallRouteMissingPolicy({
|
|
1265
|
+
hostKind,
|
|
1266
|
+
explicitRouteMissingPolicy: args.route_missing_policy,
|
|
1267
|
+
defaultReplyRoute,
|
|
1268
|
+
});
|
|
1269
|
+
if (!routeMissingPolicyResolution.ok) {
|
|
1270
|
+
return fail(routeMissingPolicyResolution.error, {
|
|
1271
|
+
host_kind: hostKind,
|
|
1272
|
+
hint: 'OpenClaw installs must bind the current chat as the default continuation route. Read the canonical full current session key from a structured OpenClaw host source and pass it as host_session_key.',
|
|
1273
|
+
expected_route_missing_policy: 'use_explicit_default_route',
|
|
1274
|
+
});
|
|
1275
|
+
}
|
|
1276
|
+
const routeMissingPolicy = routeMissingPolicyResolution.routeMissingPolicy;
|
|
1211
1277
|
const desiredDeliveryCapabilities = buildInstallationDeliveryDeclaration({
|
|
1212
1278
|
enableLocalReceiver,
|
|
1213
1279
|
receiverTransport,
|
|
@@ -1242,7 +1308,8 @@ async function handleInstall(args) {
|
|
|
1242
1308
|
return fail('missing_host_adapter_url');
|
|
1243
1309
|
}
|
|
1244
1310
|
const receiverConfigPath = resolveReceiverConfigPath(await loadPersistedState());
|
|
1245
|
-
|
|
1311
|
+
let existingReceiverConfig = await readReceiverConfigSnapshot(receiverConfigPath);
|
|
1312
|
+
const desiredReceiverConfig = buildReceiverConfig({
|
|
1246
1313
|
receiverConfigPath,
|
|
1247
1314
|
hostKind,
|
|
1248
1315
|
receiverTransport,
|
|
@@ -1252,16 +1319,37 @@ async function handleInstall(args) {
|
|
|
1252
1319
|
openresponsesModel: normalizeText(args.openresponses_model) || undefined,
|
|
1253
1320
|
defaultReplyRoute,
|
|
1254
1321
|
});
|
|
1255
|
-
|
|
1322
|
+
const receiverConfigChanged = !receiverConfigMaterialEquals(existingReceiverConfig, desiredReceiverConfig);
|
|
1323
|
+
let receiverPid = stateInstallSnapshot(state.runtime).receiver_pid;
|
|
1324
|
+
let receiverStop = null;
|
|
1325
|
+
if (receiverConfigChanged && isProcessAlive(receiverPid)) {
|
|
1326
|
+
// 先停 tracked receiver,避免旧进程继续拿旧 token 投递,并把旧物料配置再写回磁盘。
|
|
1327
|
+
receiverStop = await stopTrackedReceiverProcess(receiverPid);
|
|
1328
|
+
if (!normalizeBooleanFlag(receiverStop?.exited)) {
|
|
1329
|
+
return fail('receiver_stop_timeout', {
|
|
1330
|
+
receiver_config_path: receiverConfigPath,
|
|
1331
|
+
receiver_pid: receiverPid,
|
|
1332
|
+
receiver_stop: receiverStop,
|
|
1333
|
+
});
|
|
1334
|
+
}
|
|
1335
|
+
receiverPid = null;
|
|
1336
|
+
// 停掉 tracked receiver 后再读一次磁盘,尽量保留它退出前最后一次写下的 runtime cursor。
|
|
1337
|
+
existingReceiverConfig = await readReceiverConfigSnapshot(receiverConfigPath);
|
|
1338
|
+
}
|
|
1339
|
+
if (receiverConfigChanged) {
|
|
1340
|
+
const receiverConfig = applyReceiverRuntimeSnapshot(desiredReceiverConfig, existingReceiverConfig);
|
|
1341
|
+
await writeReceiverConfigFile(receiverConfigPath, receiverConfig);
|
|
1342
|
+
}
|
|
1256
1343
|
state = await persistState((current) => ({
|
|
1257
1344
|
...current,
|
|
1258
1345
|
runtime: buildInstallRuntimeState(current.runtime, {
|
|
1259
1346
|
host_kind: hostKind,
|
|
1260
1347
|
receiver_config_path: receiverConfigPath,
|
|
1348
|
+
receiver_pid: isProcessAlive(receiverPid) ? receiverPid : null,
|
|
1261
1349
|
receiver_last_error: null,
|
|
1262
1350
|
}),
|
|
1263
1351
|
}));
|
|
1264
|
-
|
|
1352
|
+
receiverPid = stateInstallSnapshot(state.runtime).receiver_pid;
|
|
1265
1353
|
if (receiverShouldStart && !isProcessAlive(receiverPid)) {
|
|
1266
1354
|
try {
|
|
1267
1355
|
receiverPid = await startDetachedReceiver({
|
|
@@ -1297,12 +1385,14 @@ async function handleInstall(args) {
|
|
|
1297
1385
|
}
|
|
1298
1386
|
receiverPayload = {
|
|
1299
1387
|
config_path: receiverConfigPath,
|
|
1388
|
+
config_changed: receiverConfigChanged,
|
|
1300
1389
|
started: receiverShouldStart,
|
|
1301
1390
|
receiver_pid: receiverPid || null,
|
|
1302
1391
|
receiver_command_argv: receiverCommandArgv,
|
|
1303
1392
|
transport: receiverTransport,
|
|
1304
1393
|
host_adapter_kind: hostAdapterKind,
|
|
1305
1394
|
host_adapter_url: hostAdapterUrl,
|
|
1395
|
+
...(receiverStop ? { receiver_stop: receiverStop } : {}),
|
|
1306
1396
|
};
|
|
1307
1397
|
}
|
|
1308
1398
|
if (receiverShouldStart) {
|
|
@@ -1335,22 +1425,21 @@ async function handleReset(args) {
|
|
|
1335
1425
|
const removeReceiverConfig = args.remove_receiver_config !== false;
|
|
1336
1426
|
const clearState = args.clear_state !== false;
|
|
1337
1427
|
let stopResult = null;
|
|
1338
|
-
if (stopReceiver &&
|
|
1428
|
+
if (stopReceiver && Number.isInteger(installState.receiver_pid) && Number(installState.receiver_pid) > 0) {
|
|
1339
1429
|
try {
|
|
1340
|
-
|
|
1341
|
-
stopResult = {
|
|
1342
|
-
attempted: true,
|
|
1343
|
-
pid: installState.receiver_pid,
|
|
1344
|
-
signalled: true,
|
|
1345
|
-
};
|
|
1430
|
+
stopResult = await stopTrackedReceiverProcess(installState.receiver_pid);
|
|
1346
1431
|
}
|
|
1347
1432
|
catch (error) {
|
|
1348
|
-
|
|
1349
|
-
attempted: true,
|
|
1433
|
+
return fail('receiver_stop_failed', {
|
|
1350
1434
|
pid: installState.receiver_pid,
|
|
1351
|
-
signalled: false,
|
|
1352
1435
|
error: String(error?.message || error || 'receiver_stop_failed'),
|
|
1353
|
-
};
|
|
1436
|
+
});
|
|
1437
|
+
}
|
|
1438
|
+
if (!normalizeBooleanFlag(stopResult?.exited)) {
|
|
1439
|
+
return fail('receiver_stop_timeout', {
|
|
1440
|
+
pid: installState.receiver_pid,
|
|
1441
|
+
receiver_stop: stopResult,
|
|
1442
|
+
});
|
|
1354
1443
|
}
|
|
1355
1444
|
}
|
|
1356
1445
|
const receiverConfigPath = installState.receiver_config_path || resolveReceiverConfigPath(state);
|