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.
- package/.opcteam-plugin/plugin.json +4 -4
- package/BREAKING-CHANGES.md +23 -7
- package/README.md +50 -48
- package/agents/{acti-observer.md → pactive-observer.md} +4 -4
- package/bin/{acti.mjs → pactive.mjs} +40 -40
- package/commands/pactive.md +46 -0
- package/dsh/cordis.patch.yml +4 -4
- package/dsh/index.js +24 -24
- package/hooks/post-tool-use.mjs +1 -1
- package/hooks/session-end.mjs +1 -1
- package/hooks/session-start.mjs +1 -1
- package/hooks/stop.mjs +2 -2
- package/hooks/user-prompt-submit.mjs +2 -2
- package/mcp/server.mjs +4 -4
- package/mcp/tools/_core.mjs +6 -6
- package/mcp/tools/definitions.mjs +12 -12
- package/package.json +4 -5
- package/pi/extension.ts +35 -35
- package/{skills/acti → pi/skills/pactive}/SKILL.md +13 -13
- package/{pi/skills/acti → skills/pactive}/SKILL.md +13 -13
- package/src/core/config.mjs +2 -2
- package/src/core/errors.mjs +1 -1
- package/src/core/identity.mjs +2 -2
- package/src/core/lineage.mjs +2 -2
- package/src/core/lock.mjs +2 -2
- package/src/core/paths.mjs +6 -6
- package/src/core/scheduler.mjs +5 -5
- package/src/core/state.mjs +7 -7
- package/src/hosts/dsh/index.mjs +12 -12
- package/src/hosts/errors.mjs +5 -5
- package/src/hosts/pi/index.mjs +1 -1
- package/src/hosts/shared/index.mjs +3 -3
- package/commands/acti.md +0 -46
|
@@ -1,28 +1,28 @@
|
|
|
1
1
|
---
|
|
2
|
-
name:
|
|
2
|
+
name: pactive
|
|
3
3
|
description: >-
|
|
4
|
-
Correct usage of the
|
|
5
|
-
|
|
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
|
|
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
|
-
#
|
|
13
|
+
# pactive:怎么正确用它
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
pactive 给会话加**主动机制**:空闲判定(到点才动手)、**定时任务**、**跨会话消息**与**事件账本**。
|
|
16
16
|
|
|
17
17
|
## 工具与命令
|
|
18
18
|
|
|
19
19
|
| 入口 | 作用 |
|
|
20
20
|
|---|---|
|
|
21
|
-
| `
|
|
22
|
-
| `
|
|
23
|
-
| `
|
|
24
|
-
| `
|
|
25
|
-
| `/
|
|
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
|
-
- 任务登记了但没触发 → 先看 `
|
|
47
|
-
- 消息一直是 `queued` → 对端不在线;看 `
|
|
46
|
+
- 任务登记了但没触发 → 先看 `pactive_history` 里有没有 `triggered` 记录;没有说明调度器没跑到。
|
|
47
|
+
- 消息一直是 `queued` → 对端不在线;看 `pactive_status` 的 peers。
|
package/src/core/config.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
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
|
|
48
|
+
throw pactiveError('CONFIG_KEY_UNKNOWN', `unknown config key: ${key}`);
|
|
49
49
|
}
|
|
50
50
|
return spec;
|
|
51
51
|
}
|
package/src/core/errors.mjs
CHANGED
|
@@ -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
|
|
4
|
+
export function pactiveError(code, message) {
|
|
5
5
|
const err = new Error(message);
|
|
6
6
|
err.code = code;
|
|
7
7
|
return err;
|
package/src/core/identity.mjs
CHANGED
|
@@ -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 {
|
|
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
|
|
17
|
+
throw pactiveError('IDENTITY_INCOMPLETE', 'name, hostKind and sessionId are required');
|
|
18
18
|
}
|
|
19
19
|
const projectHash = hashProject(cwd ?? process.cwd());
|
|
20
20
|
return {
|
package/src/core/lineage.mjs
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
|
|
4
|
-
import {
|
|
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
|
|
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 {
|
|
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
|
|
56
|
+
throw pactiveError('LOCK_TIMEOUT', `could not acquire lock ${lockFile}`);
|
|
57
57
|
}
|
|
58
58
|
await sleep(20);
|
|
59
59
|
}
|
package/src/core/paths.mjs
CHANGED
|
@@ -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 {
|
|
5
|
+
import { pactiveError } from './errors.mjs';
|
|
6
6
|
|
|
7
|
-
export const DEFAULT_ROOT_DIRNAME = '.
|
|
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
|
|
15
|
+
if (!kind) throw pactiveError('HOST_KIND_REQUIRED', 'hostKind is required');
|
|
16
16
|
|
|
17
|
-
const source = env && typeof env.
|
|
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
|
|
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
|
|
33
|
+
throw pactiveError('STATE_ROOT_NOT_WRITABLE', `state root is not writable: ${root}`);
|
|
34
34
|
}
|
|
35
35
|
}
|
|
36
36
|
|
package/src/core/scheduler.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
package/src/core/state.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
|
|
4
|
-
import {
|
|
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
|
|
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
|
|
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
|
|
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
|
|
146
|
+
throw pactiveError('STATE_REQUIRED', 'a state object is required');
|
|
147
147
|
}
|
|
148
148
|
if (state.schemaVersion !== SCHEMA_VERSION) {
|
|
149
|
-
throw
|
|
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
|
|
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 () => {
|
package/src/hosts/dsh/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// DSH host adapter (contract §2). argv is verbatim per design §1:
|
|
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', '
|
|
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', '
|
|
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', '
|
|
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 '
|
|
82
|
+
return 'pactive';
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
-
/** Whether the
|
|
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', '
|
|
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?.
|
|
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
|
|
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', '
|
|
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-
|
|
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', '
|
|
123
|
+
return ['plugin', '--profile', 'pactive', 'list'];
|
|
124
124
|
}
|
|
125
125
|
|
|
126
126
|
export function verifyMatch({ pkgRoot }, output) {
|
package/src/hosts/errors.mjs
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
// Contract §2 error taxonomy.
|
|
2
|
-
export class
|
|
3
|
-
constructor(message, { code = '
|
|
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 = '
|
|
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
|
|
12
|
+
export class HostMissingError extends PactiveError {
|
|
13
13
|
constructor(host, attempted = null) {
|
|
14
|
-
const envVar = `
|
|
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 },
|
package/src/hosts/pi/index.mjs
CHANGED
|
@@ -26,10 +26,10 @@ export function findOnPath(name, env = process.env) {
|
|
|
26
26
|
return null;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
/** env.
|
|
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[`
|
|
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(), '.
|
|
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` 与护栏结论,不要泛泛说「一切正常」。
|