pactive 0.0.1-alpha.1 → 0.0.2-alpha.1

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.
@@ -1,28 +1,28 @@
1
1
  ---
2
- name: acti
2
+ name: pactive
3
3
  description: >-
4
- Correct usage of the acti proactive-assistant extension (tools acti_status / acti_schedule /
5
- acti_tell / acti_history and the "/acti" command family). Use when the task involves checking
4
+ Correct usage of the pactive proactive-assistant extension (tools pactive_status / pactive_schedule /
5
+ pactive_tell / pactive_history and the "/pactive" command family). Use when the task involves checking
6
6
  whether the idle trigger fired or why it did not, scheduling work for later (one-shot or
7
- repeating), reading the acti event ledger, or sending a message to another session on the same
7
+ repeating), reading the pactive event ledger, or sending a message to another session on the same
8
8
  machine. Covers the semantics traps (queued is not delivered, idle is disabled by default,
9
9
  human-gated config needs CLI --confirm, the Stop hook blocks at most once per turn) and the
10
10
  "when" syntax (10m / 18:30 / ISO 8601 / every 1h).
11
11
  ---
12
12
 
13
- # acti:怎么正确用它
13
+ # pactive:怎么正确用它
14
14
 
15
- acti 给会话加**主动机制**:空闲判定(到点才动手)、**定时任务**、**跨会话消息**与**事件账本**。
15
+ pactive 给会话加**主动机制**:空闲判定(到点才动手)、**定时任务**、**跨会话消息**与**事件账本**。
16
16
 
17
17
  ## 工具与命令
18
18
 
19
19
  | 入口 | 作用 |
20
20
  |---|---|
21
- | `acti_status` / `/acti status` | 空闲判定与 `reason`、护栏四闸门、定时任务数、最近活动(只读) |
22
- | `acti_schedule` / `/acti schedule <when> <prompt>` | 登记一个定时任务(真正落盘,重启后仍在) |
23
- | `acti_tell <to> <body>` | 给同机另一个会话投递消息(离线时排队,不伪装成功) |
24
- | `acti_history [n]` | 读事件账本尾部:触发 → 投递 → 回应 → 结账 |
25
- | `/acti tasks` | 列出待触发的定时任务 |
21
+ | `pactive_status` / `/pactive status` | 空闲判定与 `reason`、护栏四闸门、定时任务数、最近活动(只读) |
22
+ | `pactive_schedule` / `/pactive schedule <when> <prompt>` | 登记一个定时任务(真正落盘,重启后仍在) |
23
+ | `pactive_tell <to> <body>` | 给同机另一个会话投递消息(离线时排队,不伪装成功) |
24
+ | `pactive_history [n]` | 读事件账本尾部:触发 → 投递 → 回应 → 结账 |
25
+ | `/pactive tasks` | 列出待触发的定时任务 |
26
26
 
27
27
  ## 关键语义(别踩)
28
28
 
@@ -43,5 +43,5 @@ ISO 8601 时间戳(绝对时刻)、`every 1h`(周期)。缺省 `10m`。
43
43
  ## 排障
44
44
 
45
45
  - 状态面板里 `reason=disabled` → 开关没开;`reason=busy` → 宿主还在忙;`reason=threshold` → 还没到阈值。
46
- - 任务登记了但没触发 → 先看 `acti_history` 里有没有 `triggered` 记录;没有说明调度器没跑到。
47
- - 消息一直是 `queued` → 对端不在线;看 `acti_status` 的 peers。
46
+ - 任务登记了但没触发 → 先看 `pactive_history` 里有没有 `triggered` 记录;没有说明调度器没跑到。
47
+ - 消息一直是 `queued` → 对端不在线;看 `pactive_status` 的 peers。
@@ -1,4 +1,4 @@
1
- import { actiError } from './errors.mjs';
1
+ import { pactiveError } from './errors.mjs';
2
2
 
3
3
  export const CONFIG_SCHEMA = {
4
4
  'idle.enabled': { type: 'boolean', default: false, humanGated: true },
@@ -45,7 +45,7 @@ export const config = {
45
45
  function specOf(key) {
46
46
  const spec = Object.prototype.hasOwnProperty.call(CONFIG_SCHEMA, key) ? CONFIG_SCHEMA[key] : null;
47
47
  if (!spec) {
48
- throw actiError('CONFIG_KEY_UNKNOWN', `unknown config key: ${key}`);
48
+ throw pactiveError('CONFIG_KEY_UNKNOWN', `unknown config key: ${key}`);
49
49
  }
50
50
  return spec;
51
51
  }
@@ -1,7 +1,7 @@
1
1
  // Shared error helpers. The contract freezes one public error-code constant.
2
2
  export const NOT_IMPLEMENTED = 'NOT_IMPLEMENTED';
3
3
 
4
- export function actiError(code, message) {
4
+ export function pactiveError(code, message) {
5
5
  const err = new Error(message);
6
6
  err.code = code;
7
7
  return err;
@@ -2,7 +2,7 @@ import crypto from 'node:crypto';
2
2
  import fs from 'node:fs';
3
3
  import path from 'node:path';
4
4
 
5
- import { actiError } from './errors.mjs';
5
+ import { pactiveError } from './errors.mjs';
6
6
 
7
7
  /**
8
8
  * Stable identity of an agent session.
@@ -14,7 +14,7 @@ export function makeIdentity({ name, hostKind, sessionId, cwd } = {}) {
14
14
  const h = typeof hostKind === 'string' ? hostKind.trim() : '';
15
15
  const s = typeof sessionId === 'string' ? sessionId.trim() : '';
16
16
  if (!n || !h || !s) {
17
- throw actiError('IDENTITY_INCOMPLETE', 'name, hostKind and sessionId are required');
17
+ throw pactiveError('IDENTITY_INCOMPLETE', 'name, hostKind and sessionId are required');
18
18
  }
19
19
  const projectHash = hashProject(cwd ?? process.cwd());
20
20
  return {
@@ -1,14 +1,14 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
 
4
- import { actiError } from './errors.mjs';
4
+ import { pactiveError } from './errors.mjs';
5
5
 
6
6
  let tmpSeq = 0;
7
7
 
8
8
  /** Append-only event ledger: one JSON object per line, private file. */
9
9
  export const ledger = {
10
10
  append(file, event) {
11
- if (typeof file !== 'string' || !file) throw actiError('LEDGER_PATH_REQUIRED', 'ledger path is required');
11
+ if (typeof file !== 'string' || !file) throw pactiveError('LEDGER_PATH_REQUIRED', 'ledger path is required');
12
12
  const dir = path.dirname(file);
13
13
  fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
14
14
  try {
package/src/core/lock.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
 
4
- import { actiError } from './errors.mjs';
4
+ import { pactiveError } from './errors.mjs';
5
5
 
6
6
  export const LOCK_STALE_MS = 30000;
7
7
  export const LOCK_TIMEOUT_MS = 10000;
@@ -53,7 +53,7 @@ export async function withLock(locksDir, name, fn, opts = {}) {
53
53
  continue;
54
54
  }
55
55
  if (Date.now() > deadline) {
56
- throw actiError('LOCK_TIMEOUT', `could not acquire lock ${lockFile}`);
56
+ throw pactiveError('LOCK_TIMEOUT', `could not acquire lock ${lockFile}`);
57
57
  }
58
58
  await sleep(20);
59
59
  }
@@ -2,9 +2,9 @@ import fs from 'node:fs';
2
2
  import os from 'node:os';
3
3
  import path from 'node:path';
4
4
 
5
- import { actiError } from './errors.mjs';
5
+ import { pactiveError } from './errors.mjs';
6
6
 
7
- export const DEFAULT_ROOT_DIRNAME = '.acti';
7
+ export const DEFAULT_ROOT_DIRNAME = '.pactive';
8
8
 
9
9
  /**
10
10
  * Resolve the state root and every file location derived from it.
@@ -12,9 +12,9 @@ export const DEFAULT_ROOT_DIRNAME = '.acti';
12
12
  */
13
13
  export function resolvePaths(hostKind, env = process.env) {
14
14
  const kind = typeof hostKind === 'string' ? hostKind.trim() : '';
15
- if (!kind) throw actiError('HOST_KIND_REQUIRED', 'hostKind is required');
15
+ if (!kind) throw pactiveError('HOST_KIND_REQUIRED', 'hostKind is required');
16
16
 
17
- const source = env && typeof env.ACTI_STATE_DIR === 'string' ? env.ACTI_STATE_DIR.trim() : '';
17
+ const source = env && typeof env.PACTIVE_STATE_DIR === 'string' ? env.PACTIVE_STATE_DIR.trim() : '';
18
18
  const root = path.resolve(source || path.join(os.homedir(), DEFAULT_ROOT_DIRNAME));
19
19
 
20
20
  let stat = null;
@@ -25,12 +25,12 @@ export function resolvePaths(hostKind, env = process.env) {
25
25
  }
26
26
  if (stat) {
27
27
  if (!stat.isDirectory()) {
28
- throw actiError('STATE_ROOT_INVALID', `state root is not a directory: ${root}`);
28
+ throw pactiveError('STATE_ROOT_INVALID', `state root is not a directory: ${root}`);
29
29
  }
30
30
  try {
31
31
  fs.accessSync(root, fs.constants.W_OK);
32
32
  } catch {
33
- throw actiError('STATE_ROOT_NOT_WRITABLE', `state root is not writable: ${root}`);
33
+ throw pactiveError('STATE_ROOT_NOT_WRITABLE', `state root is not writable: ${root}`);
34
34
  }
35
35
  }
36
36
 
@@ -1,4 +1,4 @@
1
- import { actiError } from './errors.mjs';
1
+ import { pactiveError } from './errors.mjs';
2
2
 
3
3
  export const TASK_KINDS = ['in', 'at', 'every'];
4
4
 
@@ -10,22 +10,22 @@ let seq = 0;
10
10
  */
11
11
  export const schedule = {
12
12
  add(state, spec, now = Date.now()) {
13
- if (!spec || typeof spec !== 'object') throw actiError('TASK_SPEC_REQUIRED', 'a task spec is required');
13
+ if (!spec || typeof spec !== 'object') throw pactiveError('TASK_SPEC_REQUIRED', 'a task spec is required');
14
14
  const ts = Number.isFinite(now) ? now : Date.now();
15
15
  const kind = spec.kind ?? (Number.isFinite(spec.at) ? 'at' : 'in');
16
16
  if (!TASK_KINDS.includes(kind)) {
17
- throw actiError('TASK_KIND_UNKNOWN', `unknown task kind: ${kind}`);
17
+ throw pactiveError('TASK_KIND_UNKNOWN', `unknown task kind: ${kind}`);
18
18
  }
19
19
  if (!Array.isArray(state.tasks)) state.tasks = [];
20
20
 
21
21
  let dueAt;
22
22
  let everyMs = 0;
23
23
  if (kind === 'at') {
24
- if (!Number.isFinite(spec.at)) throw actiError('TASK_AT_REQUIRED', 'an "at" task needs spec.at');
24
+ if (!Number.isFinite(spec.at)) throw pactiveError('TASK_AT_REQUIRED', 'an "at" task needs spec.at');
25
25
  dueAt = spec.at;
26
26
  } else if (kind === 'every') {
27
27
  everyMs = Number.isFinite(spec.everyMs) ? spec.everyMs : Number.isFinite(spec.intervalMs) ? spec.intervalMs : 0;
28
- if (everyMs <= 0) throw actiError('TASK_INTERVAL_REQUIRED', 'an "every" task needs a positive interval');
28
+ if (everyMs <= 0) throw pactiveError('TASK_INTERVAL_REQUIRED', 'an "every" task needs a positive interval');
29
29
  dueAt = ts + everyMs;
30
30
  } else {
31
31
  const delay = Number.isFinite(spec.delayMs) ? spec.delayMs
@@ -1,7 +1,7 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
 
4
- import { actiError } from './errors.mjs';
4
+ import { pactiveError } from './errors.mjs';
5
5
  import { ensureDir, withLock } from './lock.mjs';
6
6
  import { ledger } from './lineage.mjs';
7
7
 
@@ -38,7 +38,7 @@ export function defaultState() {
38
38
 
39
39
  /** Accept either a paths object (from resolvePaths) or a state file path. */
40
40
  export function toPaths(target) {
41
- if (!target) throw actiError('STATE_TARGET_REQUIRED', 'a paths object or state file path is required');
41
+ if (!target) throw pactiveError('STATE_TARGET_REQUIRED', 'a paths object or state file path is required');
42
42
  if (typeof target === 'string') {
43
43
  const stateFile = path.resolve(target);
44
44
  const root = path.dirname(stateFile);
@@ -54,7 +54,7 @@ export function toPaths(target) {
54
54
  };
55
55
  }
56
56
  if (typeof target.stateFile !== 'string' || !target.stateFile) {
57
- throw actiError('STATE_TARGET_REQUIRED', 'a paths object or state file path is required');
57
+ throw pactiveError('STATE_TARGET_REQUIRED', 'a paths object or state file path is required');
58
58
  }
59
59
  const root = target.root ?? path.dirname(target.stateFile);
60
60
  return {
@@ -111,7 +111,7 @@ export function loadState(target) {
111
111
  try {
112
112
  const parsed = JSON.parse(raw);
113
113
  if (!parsed || typeof parsed !== 'object' || parsed.schemaVersion !== SCHEMA_VERSION) {
114
- throw actiError('STATE_SCHEMA_UNSUPPORTED', `unsupported schemaVersion in ${paths.stateFile}`);
114
+ throw pactiveError('STATE_SCHEMA_UNSUPPORTED', `unsupported schemaVersion in ${paths.stateFile}`);
115
115
  }
116
116
  return normalize(parsed);
117
117
  } catch (err) {
@@ -143,10 +143,10 @@ function quarantine(paths, cause) {
143
143
  export function saveState(target, state) {
144
144
  const paths = toPaths(target);
145
145
  if (!state || typeof state !== 'object') {
146
- throw actiError('STATE_REQUIRED', 'a state object is required');
146
+ throw pactiveError('STATE_REQUIRED', 'a state object is required');
147
147
  }
148
148
  if (state.schemaVersion !== SCHEMA_VERSION) {
149
- throw actiError('STATE_SCHEMA_UNSUPPORTED', `expected schemaVersion ${SCHEMA_VERSION}`);
149
+ throw pactiveError('STATE_SCHEMA_UNSUPPORTED', `expected schemaVersion ${SCHEMA_VERSION}`);
150
150
  }
151
151
  ensureStateDirs(paths);
152
152
  const dir = path.dirname(paths.stateFile);
@@ -168,7 +168,7 @@ export function saveState(target, state) {
168
168
 
169
169
  /** Read-modify-write under a lock; the callback may be async and may mutate in place. */
170
170
  export async function mutateState(target, fn) {
171
- if (typeof fn !== 'function') throw actiError('MUTATOR_REQUIRED', 'mutateState needs a function');
171
+ if (typeof fn !== 'function') throw pactiveError('MUTATOR_REQUIRED', 'mutateState needs a function');
172
172
  const paths = toPaths(target);
173
173
  ensureStateDirs(paths);
174
174
  return withLock(paths.locksDir, 'state', async () => {
@@ -1,4 +1,4 @@
1
- // DSH host adapter (contract §2). argv is verbatim per design §1: acti owns a dedicated profile.
1
+ // DSH host adapter (contract §2). argv is verbatim per design §1: pactive owns a dedicated profile.
2
2
  import { readFileSync, readdirSync, rmSync } from 'node:fs';
3
3
  import os from 'node:os';
4
4
  import path from 'node:path';
@@ -17,7 +17,7 @@ export const MODE = 'profile';
17
17
  export const resolveExec = makeResolver('dsh', 'dsh');
18
18
 
19
19
  export function buildArgv({ pkgRoot, userArgs = [] }) {
20
- return ['--profile', 'acti', ...userArgs];
20
+ return ['--profile', 'pactive', ...userArgs];
21
21
  }
22
22
 
23
23
  export function plan({ pkgRoot, env = process.env, userArgs = [] }) {
@@ -62,13 +62,13 @@ export function stateRoot(ctx = {}) {
62
62
  }
63
63
 
64
64
  export function installArgs({ pkgRoot }) {
65
- return ['plugin', '--profile', 'acti', 'add', pkgRoot];
65
+ return ['plugin', '--profile', 'pactive', 'add', pkgRoot];
66
66
  }
67
67
 
68
68
  export function uninstallArgs({ pkgRoot }) {
69
69
  // pnpm removes by DEPENDENCY NAME, not by path: `pnpm remove /abs/path` exits 1 and leaves
70
70
  // the profile untouched (defect found 2026-11-05 via install→uninstall→residue probe).
71
- return ['plugin', '--profile', 'acti', 'remove', packageName(pkgRoot)];
71
+ return ['plugin', '--profile', 'pactive', 'remove', packageName(pkgRoot)];
72
72
  }
73
73
 
74
74
  /** The dependency name that `install` registered (falls back to the package's own default). */
@@ -79,25 +79,25 @@ function packageName(pkgRoot) {
79
79
  } catch {
80
80
  // manifest unreadable: fall back to the contract name
81
81
  }
82
- return 'acti';
82
+ return 'pactive';
83
83
  }
84
84
 
85
- /** Whether the acti profile currently exists (used to keep `uninstall` idempotent and quiet). */
85
+ /** Whether the pactive profile currently exists (used to keep `uninstall` idempotent and quiet). */
86
86
  export function isInstalled({ env }) {
87
87
  const home = env.DSH_HOME || path.join(os.homedir(), '.dsh');
88
- const manifest = path.join(home, 'profiles', 'acti', 'package.json');
88
+ const manifest = path.join(home, 'profiles', 'pactive', 'package.json');
89
89
  try {
90
90
  const pkg = JSON.parse(readFileSync(manifest, 'utf8'));
91
- return Boolean(pkg?.dependencies?.acti) || (pkg?.dsh?.profile?.bundles || []).includes('acti');
91
+ return Boolean(pkg?.dependencies?.pactive) || (pkg?.dsh?.profile?.bundles || []).includes('pactive');
92
92
  } catch {
93
93
  return false;
94
94
  }
95
95
  }
96
96
 
97
- /** Remove the acti-owned DSH profile directory so `uninstall` leaves zero residue (I40). */
97
+ /** Remove the pactive-owned DSH profile directory so `uninstall` leaves zero residue (I40). */
98
98
  export function cleanup({ env }) {
99
99
  const home = env.DSH_HOME || path.join(os.homedir(), '.dsh');
100
- const profileDir = path.join(home, 'profiles', 'acti');
100
+ const profileDir = path.join(home, 'profiles', 'pactive');
101
101
  const manifest = path.join(profileDir, 'package.json');
102
102
  let pkg;
103
103
  try {
@@ -105,7 +105,7 @@ export function cleanup({ env }) {
105
105
  } catch {
106
106
  return { removed: false, reason: 'no-profile' };
107
107
  }
108
- if (pkg.name !== 'dsh-profile-acti') {
108
+ if (pkg.name !== 'dsh-profile-pactive') {
109
109
  return { removed: false, reason: 'profile-not-owned' };
110
110
  }
111
111
  rmSync(profileDir, { recursive: true, force: true });
@@ -120,7 +120,7 @@ export function cleanup({ env }) {
120
120
  }
121
121
 
122
122
  export function verifyArgs({ pkgRoot }) {
123
- return ['plugin', '--profile', 'acti', 'list'];
123
+ return ['plugin', '--profile', 'pactive', 'list'];
124
124
  }
125
125
 
126
126
  export function verifyMatch({ pkgRoot }, output) {
@@ -1,17 +1,17 @@
1
1
  // Contract §2 error taxonomy.
2
- export class ActiError extends Error {
3
- constructor(message, { code = 'ACTI_ERROR', exitCode = 1, cause } = {}) {
2
+ export class PactiveError extends Error {
3
+ constructor(message, { code = 'PACTIVE_ERROR', exitCode = 1, cause } = {}) {
4
4
  super(message, cause === undefined ? undefined : { cause });
5
- this.name = 'ActiError';
5
+ this.name = 'PactiveError';
6
6
  this.code = code;
7
7
  this.exitCode = exitCode;
8
8
  }
9
9
  }
10
10
 
11
11
  /** The host binary could not be found → CLI exit code 127. */
12
- export class HostMissingError extends ActiError {
12
+ export class HostMissingError extends PactiveError {
13
13
  constructor(host, attempted = null) {
14
- const envVar = `ACTI_${host.toUpperCase()}_BIN`;
14
+ const envVar = `PACTIVE_${host.toUpperCase()}_BIN`;
15
15
  super(
16
16
  `host binary for '${host}' not found${attempted ? ` (${attempted})` : ''} — install '${host}' or set ${envVar}`,
17
17
  { code: 'HOST_MISSING', exitCode: 127 },
@@ -19,7 +19,7 @@ export function buildArgv({ pkgRoot, userArgs = [] }) {
19
19
  '--extension',
20
20
  path.join(pkgRoot, 'pi', 'extension.ts'),
21
21
  '--skill',
22
- path.join(pkgRoot, 'pi', 'skills', 'acti'),
22
+ path.join(pkgRoot, 'pi', 'skills', 'pactive'),
23
23
  ...userArgs,
24
24
  ];
25
25
  }
@@ -26,10 +26,10 @@ export function findOnPath(name, env = process.env) {
26
26
  return null;
27
27
  }
28
28
 
29
- /** env.ACTI_<HOST>_BIN → PATH lookup → throw HostMissingError (exit 127). */
29
+ /** env.PACTIVE_<HOST>_BIN → PATH lookup → throw HostMissingError (exit 127). */
30
30
  export function makeResolver(host, binaryName) {
31
31
  return function resolveExec({ env = process.env } = {}) {
32
- const override = env[`ACTI_${host.toUpperCase()}_BIN`];
32
+ const override = env[`PACTIVE_${host.toUpperCase()}_BIN`];
33
33
  if (override) {
34
34
  if (isExecutable(override)) return path.resolve(override);
35
35
  throw new HostMissingError(host, override);
@@ -60,5 +60,5 @@ export function unreachableReceipt(reason) {
60
60
  }
61
61
 
62
62
  export function defaultStateRoot(ctx = {}) {
63
- return ctx.stateRoot ?? ctx.paths?.root ?? path.join(os.homedir(), '.acti');
63
+ return ctx.stateRoot ?? ctx.paths?.root ?? path.join(os.homedir(), '.pactive');
64
64
  }
package/commands/acti.md DELETED
@@ -1,46 +0,0 @@
1
- ---
2
- description: Acti 主动机制 — status、schedule、tell、history(经 acti MCP server)
3
- argument-hint: "[status|schedule|tell|history] [args...]"
4
- ---
5
-
6
- # acti:acti
7
-
8
- 把用户关于「主动机制 / 空闲触发 / 定时任务 / 跨会话消息」的请求路由到 `acti` MCP server。
9
- 宿主把它暴露为 `/acti:acti`;裸 `/acti` 不由插件契约保证。
10
-
11
- ## 工具名
12
-
13
- OPCteam 用命名空间暴露插件 MCP 工具:
14
-
15
- ```
16
- mcp__plugin_acti_acti__<tool>
17
- ```
18
-
19
- 具体名字:
20
-
21
- - `mcp__plugin_acti_acti__acti_status`
22
- - `mcp__plugin_acti_acti__acti_schedule`
23
- - `mcp__plugin_acti_acti__acti_tell`
24
- - `mcp__plugin_acti_acti__acti_history`
25
-
26
- 裸名(`acti_status`)不一定能被 ToolSearch 发现;若命名空间工具没加载,用
27
- `select:mcp__plugin_acti_acti__acti_status` 搜一次。
28
-
29
- ## 路由
30
-
31
- | 用户说 | 调用 |
32
- |---|---|
33
- | 状态 / 为什么没触发 | `acti_status`(看 `idle.reason`,别猜) |
34
- | 10 分钟后提醒我 / 定时 | `acti_schedule` `when:"10m"` `prompt:"…"` |
35
- | 18:30 提醒我 | `acti_schedule` `when:"18:30"` |
36
- | 每小时 | `acti_schedule` `when:"every 1h"` |
37
- | 告诉另一个会话 | `acti_tell` `to` `body`(可选 `kind:"ask"`) |
38
- | 最近发生过什么 | `acti_history` `limit` |
39
-
40
- ## 硬规则
41
-
42
- 1. **不要替用户改配置**:acti 的开关与配置是人工闸门(CLI `--confirm`)。用户要求改配置时,
43
- 告诉他用 `acti config set … --confirm`,不要自行绕过。
44
- 2. **回执如实转述**:`queued` 就是**还没送到**(对端离线),不要说成已送达。
45
- 3. **默认不打扰**:空闲触发默认关闭;用户没明确要「空闲自动推进」时,不要假设它已开启。
46
- 4. **只报新事实**:状态类输出要给出 `idle.reason` 与护栏结论,不要泛泛说「一切正常」。