



























11import fs from "node:fs/promises";
22import path from "node:path";
33import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";
4+import {
5+asDateTimestampMs,
6+resolveExpiresAtMsFromDurationMs,
7+} from "openclaw/plugin-sdk/number-runtime";
48import { replaceFileAtomic } from "openclaw/plugin-sdk/security-runtime";
59import {
610normalizeLowercaseStringOrEmpty,
@@ -294,14 +298,38 @@ function lacksAdminToMutatePhoneControl(params: {
294298return senderIsOwner !== true;
295299}
296300301+function resolveArmExpiryStatus(state: ArmStateFile, nowRaw = Date.now()): string {
302+if (state.expiresAtMs == null) {
303+return "manual disarm required";
304+}
305+const now = asDateTimestampMs(nowRaw);
306+if (now === undefined) {
307+return "expiry unavailable";
308+}
309+const expiresAt = asDateTimestampMs(state.expiresAtMs);
310+if (expiresAt === undefined || expiresAt <= now) {
311+return "expired";
312+}
313+return `expires in ${formatDuration(expiresAt - now)}`;
314+}
315+316+function isArmStateExpired(state: ArmStateFile, nowRaw = Date.now()): boolean {
317+if (state.expiresAtMs == null) {
318+return false;
319+}
320+const now = asDateTimestampMs(nowRaw);
321+if (now === undefined) {
322+return false;
323+}
324+const expiresAt = asDateTimestampMs(state.expiresAtMs);
325+return expiresAt === undefined || expiresAt <= now;
326+}
327+297328function formatStatus(state: ArmStateFile | null): string {
298329if (!state) {
299330return "Phone control: disarmed.";
300331}
301-const until =
302-state.expiresAtMs == null
303- ? "manual disarm required"
304- : `expires in ${formatDuration(Math.max(0, state.expiresAtMs - Date.now()))}`;
332+const until = resolveArmExpiryStatus(state);
305333const cmds = uniqSorted(
306334state.version === 1
307335 ? state.removedFromDeny
@@ -329,7 +357,7 @@ export default definePluginEntry({
329357if (!state || state.expiresAtMs == null) {
330358return;
331359}
332-if (Date.now() < state.expiresAtMs) {
360+if (!isArmStateExpired(state)) {
333361return;
334362}
335363await disarmNow({
@@ -430,7 +458,14 @@ export default definePluginEntry({
430458if (durationMs === null) {
431459return { text: "Invalid duration. Use values like 30s, 10m, 2h, or 1d." };
432460}
433-const expiresAtMs = Date.now() + durationMs;
461+const armedAtMs = asDateTimestampMs(Date.now());
462+const expiresAtMs =
463+armedAtMs === undefined
464+ ? undefined
465+ : resolveExpiresAtMsFromDurationMs(durationMs, { nowMs: armedAtMs });
466+if (armedAtMs === undefined || expiresAtMs === undefined) {
467+return { text: "Invalid duration. Use values like 30s, 10m, 2h, or 1d." };
468+}
434469435470const commands = resolveCommandsForGroup(group);
436471const cfg = api.runtime.config.current() as OpenClawConfig;
@@ -461,7 +496,7 @@ export default definePluginEntry({
461496462497await writeArmState(statePath, {
463498version: STATE_VERSION,
464-armedAtMs: Date.now(),
499+ armedAtMs,
465500 expiresAtMs,
466501 group,
467502armedCommands: uniqSorted(commands),
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。