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
package/dsh/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Pactive — DSH (Cordis) plugin.
|
|
3
3
|
*
|
|
4
|
-
* Registers the
|
|
5
|
-
* `/
|
|
4
|
+
* Registers the pactive tool surface (`pactive_status` / `pactive_schedule`) and the
|
|
5
|
+
* `/pactive` command, and subscribes to session events so the pactive activity
|
|
6
6
|
* timeline and the control flag stay accurate.
|
|
7
7
|
*
|
|
8
8
|
* The core module (`src/core/index.mjs`) is loaded lazily by relative path —
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
|
|
19
19
|
import { loadCore, mutateState, parseWhen, pickState, readState } from "../mcp/tools/_core.mjs";
|
|
20
20
|
|
|
21
|
-
export const name = "
|
|
21
|
+
export const name = "pactive";
|
|
22
22
|
|
|
23
23
|
/** Every Cordis service this plugin touches must be declared here. */
|
|
24
24
|
export const inject = ["tools", "commands", "sessions"];
|
|
@@ -47,7 +47,7 @@ async function markActivity(kind, ctx) {
|
|
|
47
47
|
return next && typeof next === "object" ? next : state;
|
|
48
48
|
});
|
|
49
49
|
} catch (err) {
|
|
50
|
-
safeLog(ctx, "warn", `
|
|
50
|
+
safeLog(ctx, "warn", `pactive: markActivity(${kind}) failed`, { error: errorText(err) });
|
|
51
51
|
}
|
|
52
52
|
}
|
|
53
53
|
|
|
@@ -61,7 +61,7 @@ async function markControlReturned(ctx) {
|
|
|
61
61
|
return next && typeof next === "object" ? next : state;
|
|
62
62
|
});
|
|
63
63
|
} catch (err) {
|
|
64
|
-
safeLog(ctx, "warn", "
|
|
64
|
+
safeLog(ctx, "warn", "pactive: markControlReturned failed", { error: errorText(err) });
|
|
65
65
|
}
|
|
66
66
|
}
|
|
67
67
|
|
|
@@ -87,9 +87,9 @@ export function apply(ctx, config) {
|
|
|
87
87
|
if (!ctx?.tools?.register) return;
|
|
88
88
|
|
|
89
89
|
ctx.tools.register({
|
|
90
|
-
name: "
|
|
90
|
+
name: "pactive_status",
|
|
91
91
|
description:
|
|
92
|
-
"
|
|
92
|
+
"Pactive 主动机制状态:空闲判定与 reason、护栏四闸门、定时任务数、最近活动。只读。",
|
|
93
93
|
parameters: STATUS_PARAMETERS,
|
|
94
94
|
async execute(args = {}) {
|
|
95
95
|
try {
|
|
@@ -120,7 +120,7 @@ export function apply(ctx, config) {
|
|
|
120
120
|
});
|
|
121
121
|
|
|
122
122
|
ctx.tools.register({
|
|
123
|
-
name: "
|
|
123
|
+
name: "pactive_schedule",
|
|
124
124
|
description: "登记一个定时任务(真正落盘)。when 支持时长(10m)、时刻(18:30)、ISO 时间、every 1h。",
|
|
125
125
|
parameters: SCHEDULE_PARAMETERS,
|
|
126
126
|
async execute(args = {}) {
|
|
@@ -161,8 +161,8 @@ export function apply(ctx, config) {
|
|
|
161
161
|
if (!ctx?.commands?.register) return;
|
|
162
162
|
|
|
163
163
|
ctx.commands.register({
|
|
164
|
-
name: "
|
|
165
|
-
description: "
|
|
164
|
+
name: "pactive",
|
|
165
|
+
description: "Pactive:status | tasks | schedule <when> <prompt> | help",
|
|
166
166
|
input: { hint: "status | tasks | schedule <when> <prompt> | help" },
|
|
167
167
|
async handler(invocation) {
|
|
168
168
|
const raw = String(invocation?.rawInput ?? "").trim();
|
|
@@ -182,7 +182,7 @@ export function apply(ctx, config) {
|
|
|
182
182
|
const guard = core.checkGuard ? core.checkGuard(state, now) : { pass: false, reason: "requires-action" };
|
|
183
183
|
const tasks = core.schedule?.list ? core.schedule.list(state) : (state.tasks ?? []);
|
|
184
184
|
return ok(
|
|
185
|
-
`
|
|
185
|
+
`pactive: idle=${idle.idle} reason=${idle.reason} guard=${guard.pass ? "pass" : "blocked"} ` +
|
|
186
186
|
`(${guard.reason ?? "-"}) tasks=${Array.isArray(tasks) ? tasks.length : 0}`,
|
|
187
187
|
);
|
|
188
188
|
}
|
|
@@ -191,17 +191,17 @@ export function apply(ctx, config) {
|
|
|
191
191
|
const list = Array.isArray(tasks) ? tasks : [];
|
|
192
192
|
return ok(
|
|
193
193
|
list.length === 0
|
|
194
|
-
? "
|
|
195
|
-
: `
|
|
194
|
+
? "pactive: 没有定时任务"
|
|
195
|
+
: `pactive: ${list.length} 个定时任务\n` +
|
|
196
196
|
list.map((task) => `- [${task?.id ?? "?"}] ${task?.prompt ?? task?.content ?? ""}`).join("\n"),
|
|
197
197
|
);
|
|
198
198
|
}
|
|
199
199
|
if (sub === "schedule") {
|
|
200
200
|
const when = parts[1] ?? "10m";
|
|
201
201
|
const prompt = parts.slice(2).join(" ");
|
|
202
|
-
if (prompt === "") return fail("用法:/
|
|
202
|
+
if (prompt === "") return fail("用法:/pactive schedule <when> <prompt>");
|
|
203
203
|
const spec = parseWhen(when, now);
|
|
204
|
-
if (!spec) return fail(`
|
|
204
|
+
if (!spec) return fail(`pactive: 无法解析 when(${when})`);
|
|
205
205
|
const next = await mutateState(core, paths, (current) => {
|
|
206
206
|
const out = core.schedule.add(
|
|
207
207
|
current,
|
|
@@ -212,11 +212,11 @@ export function apply(ctx, config) {
|
|
|
212
212
|
});
|
|
213
213
|
const tasks = core.schedule?.list ? core.schedule.list(next) : (next.tasks ?? []);
|
|
214
214
|
const created = Array.isArray(tasks) ? tasks[tasks.length - 1] : null;
|
|
215
|
-
return ok(`
|
|
215
|
+
return ok(`pactive: 已登记定时任务 ${created?.id ?? "-"}(${when})`);
|
|
216
216
|
}
|
|
217
|
-
return fail("用法:/
|
|
217
|
+
return fail("用法:/pactive status | tasks | schedule <when> <prompt> | help");
|
|
218
218
|
} catch (err) {
|
|
219
|
-
return fail(`
|
|
219
|
+
return fail(`pactive 失败:${errorText(err)}`);
|
|
220
220
|
}
|
|
221
221
|
},
|
|
222
222
|
});
|
|
@@ -247,7 +247,7 @@ export function apply(ctx, config) {
|
|
|
247
247
|
void markActivity(kind, ctx);
|
|
248
248
|
});
|
|
249
249
|
} catch (err) {
|
|
250
|
-
safeLog(ctx, "debug", `
|
|
250
|
+
safeLog(ctx, "debug", `pactive: session event ${event} unavailable`, { error: errorText(err) });
|
|
251
251
|
}
|
|
252
252
|
}
|
|
253
253
|
// The agent settled → control returned to the human.
|
|
@@ -257,7 +257,7 @@ export function apply(ctx, config) {
|
|
|
257
257
|
void markControlReturned(ctx);
|
|
258
258
|
});
|
|
259
259
|
} catch (err) {
|
|
260
|
-
safeLog(ctx, "debug", `
|
|
260
|
+
safeLog(ctx, "debug", `pactive: session event ${event} unavailable`, { error: errorText(err) });
|
|
261
261
|
}
|
|
262
262
|
}
|
|
263
263
|
};
|
|
@@ -267,7 +267,7 @@ export function apply(ctx, config) {
|
|
|
267
267
|
registerCommands();
|
|
268
268
|
subscribeSessionEvents();
|
|
269
269
|
} catch (err) {
|
|
270
|
-
safeLog(ctx, "error", "
|
|
270
|
+
safeLog(ctx, "error", "pactive: failed to register tools/commands", { error: errorText(err) });
|
|
271
271
|
}
|
|
272
272
|
|
|
273
273
|
// The agent stops → control returns to the human.
|
|
@@ -276,10 +276,10 @@ export function apply(ctx, config) {
|
|
|
276
276
|
void markControlReturned(ctx);
|
|
277
277
|
});
|
|
278
278
|
} catch (err) {
|
|
279
|
-
safeLog(ctx, "warn", "
|
|
279
|
+
safeLog(ctx, "warn", "pactive: ctx.effect unavailable; control flag updated on events only", {
|
|
280
280
|
error: errorText(err),
|
|
281
281
|
});
|
|
282
282
|
}
|
|
283
283
|
|
|
284
|
-
safeLog(ctx, "info", "
|
|
284
|
+
safeLog(ctx, "info", "pactive plugin loaded", { config: pluginConfig });
|
|
285
285
|
}
|
package/hooks/post-tool-use.mjs
CHANGED
|
@@ -13,7 +13,7 @@ await readStdinJson();
|
|
|
13
13
|
try {
|
|
14
14
|
await markActivity("tool");
|
|
15
15
|
} catch (err) {
|
|
16
|
-
process.stderr.write(`[
|
|
16
|
+
process.stderr.write(`[pactive] PostToolUse: ${String(err?.message ?? err)}\n`);
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
writeHookOutput({});
|
package/hooks/session-end.mjs
CHANGED
|
@@ -13,7 +13,7 @@ await readStdinJson();
|
|
|
13
13
|
try {
|
|
14
14
|
await markActivity("session-end");
|
|
15
15
|
} catch (err) {
|
|
16
|
-
process.stderr.write(`[
|
|
16
|
+
process.stderr.write(`[pactive] SessionEnd: ${String(err?.message ?? err)}\n`);
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
writeHookOutput({});
|
package/hooks/session-start.mjs
CHANGED
|
@@ -14,7 +14,7 @@ await readStdinJson();
|
|
|
14
14
|
try {
|
|
15
15
|
await markActivity("session-start");
|
|
16
16
|
} catch (err) {
|
|
17
|
-
process.stderr.write(`[
|
|
17
|
+
process.stderr.write(`[pactive] SessionStart: ${String(err?.message ?? err)}\n`);
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
writeHookOutput({});
|
package/hooks/stop.mjs
CHANGED
|
@@ -44,12 +44,12 @@ try {
|
|
|
44
44
|
writeHookOutput({});
|
|
45
45
|
} else {
|
|
46
46
|
const lines = tasks.map((task) => `- [${task?.id ?? "?"}] ${task?.prompt ?? task?.content ?? ""}`.trim());
|
|
47
|
-
const reason = `
|
|
47
|
+
const reason = `pactive: ${tasks.length} 个定时任务到期\n${lines.join("\n")}`;
|
|
48
48
|
await mutateState(core, paths, (current) => ({ ...current, stopBlockCount: 1, controlReturned: false }));
|
|
49
49
|
writeHookOutput({ decision: "block", reason });
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
52
|
} catch (err) {
|
|
53
|
-
process.stderr.write(`[
|
|
53
|
+
process.stderr.write(`[pactive] Stop: ${String(err?.message ?? err)}\n`);
|
|
54
54
|
writeHookOutput({});
|
|
55
55
|
}
|
|
@@ -20,7 +20,7 @@ await readStdinJson();
|
|
|
20
20
|
try {
|
|
21
21
|
await markActivity("user");
|
|
22
22
|
} catch (err) {
|
|
23
|
-
process.stderr.write(`[
|
|
23
|
+
process.stderr.write(`[pactive] UserPromptSubmit: ${String(err?.message ?? err)}\n`);
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
try {
|
|
@@ -28,7 +28,7 @@ try {
|
|
|
28
28
|
const paths = core.resolvePaths("opcteam", process.env);
|
|
29
29
|
await mutateState(core, paths, (state) => ({ ...state, stopBlockCount: 0 }));
|
|
30
30
|
} catch (err) {
|
|
31
|
-
process.stderr.write(`[
|
|
31
|
+
process.stderr.write(`[pactive] UserPromptSubmit (reset): ${String(err?.message ?? err)}\n`);
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
writeHookOutput({});
|
package/mcp/server.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* `
|
|
3
|
+
* `pactive` MCP server for OPCteam — stdio JSON-RPC, zero third-party dependencies.
|
|
4
4
|
*
|
|
5
|
-
* Exposes:
|
|
5
|
+
* Exposes: pactive_status / pactive_schedule / pactive_tell / pactive_history.
|
|
6
6
|
* Protocol: newline-delimited JSON-RPC 2.0 (MCP `initialize` / `tools/list` /
|
|
7
7
|
* `tools/call`), the same wire shape the OPCteam plugin loader expects.
|
|
8
8
|
*/
|
|
@@ -14,8 +14,8 @@ import { fileURLToPath } from "node:url";
|
|
|
14
14
|
import { TOOL_DEFINITIONS, findTool, runTool } from "./tools/definitions.mjs";
|
|
15
15
|
|
|
16
16
|
const DEFAULT_PROTOCOL = "2024-11-05";
|
|
17
|
-
const SERVER_NAME = "
|
|
18
|
-
const SERVER_VERSION = "0.0.
|
|
17
|
+
const SERVER_NAME = "pactive";
|
|
18
|
+
const SERVER_VERSION = "0.0.2-alpha.1";
|
|
19
19
|
|
|
20
20
|
/** @param {NodeJS.WritableStream} output */
|
|
21
21
|
function send(output, message) {
|
package/mcp/tools/_core.mjs
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Shared
|
|
2
|
+
* Shared Pactive core access for the OPCteam / DSH host surfaces.
|
|
3
3
|
*
|
|
4
4
|
* `src/core/index.mjs` is produced by the parallel core worker. It is loaded
|
|
5
5
|
* **lazily by relative path** so a missing (not yet built) core degrades into
|
|
6
6
|
* a readable error instead of breaking plugin load.
|
|
7
7
|
*
|
|
8
|
-
* unverified: the frozen host contract (docs/team/task-cards/ACTI-CONTRACT.md)
|
|
8
|
+
* unverified: the frozen host contract (docs/team/task-cards/ACTI-CONTRACT.md, historical file name)
|
|
9
9
|
* freezes the **export names** of core, but not the argument shapes of
|
|
10
10
|
* `loadState` / `saveState` / `mutateState`. Every call below therefore tries
|
|
11
11
|
* the plausible shapes in order and falls back to an explicit read/compute/write
|
|
@@ -31,7 +31,7 @@ let cachedCore = null;
|
|
|
31
31
|
*/
|
|
32
32
|
export async function loadCore(env = process.env) {
|
|
33
33
|
if (cachedCore) return cachedCore;
|
|
34
|
-
const explicit = env.
|
|
34
|
+
const explicit = env.PACTIVE_CORE_MODULE;
|
|
35
35
|
const candidates = explicit ? [explicit, ...CORE_CANDIDATES] : CORE_CANDIDATES;
|
|
36
36
|
const tried = [];
|
|
37
37
|
for (const candidate of candidates) {
|
|
@@ -50,8 +50,8 @@ export async function loadCore(env = process.env) {
|
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
52
|
throw new Error(
|
|
53
|
-
`
|
|
54
|
-
`Build src/core before using this surface; set
|
|
53
|
+
`pactive: core module not found (tried: ${tried.join(", ")}). ` +
|
|
54
|
+
`Build src/core before using this surface; set PACTIVE_CORE_MODULE to override.`,
|
|
55
55
|
);
|
|
56
56
|
}
|
|
57
57
|
|
|
@@ -137,7 +137,7 @@ export async function mutateState(core, paths, fn) {
|
|
|
137
137
|
try {
|
|
138
138
|
await writeState(core, paths, after);
|
|
139
139
|
} catch (err) {
|
|
140
|
-
throw new Error(`
|
|
140
|
+
throw new Error(`pactive: state write failed (${errorText(err)})`);
|
|
141
141
|
}
|
|
142
142
|
return after;
|
|
143
143
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* MCP tool surface of the `
|
|
2
|
+
* MCP tool surface of the `pactive` OPCteam plugin (stdio JSON-RPC server).
|
|
3
3
|
*
|
|
4
|
-
* Tools:
|
|
4
|
+
* Tools: pactive_status / pactive_schedule / pactive_tell / pactive_history.
|
|
5
5
|
* Zero third-party dependencies — the schemas are plain JSON Schema.
|
|
6
6
|
*
|
|
7
7
|
* unverified: mailbox.send / ledger.tail argument shapes come from the frozen
|
|
@@ -33,13 +33,13 @@ const str = (description) => ({ type: "string", description });
|
|
|
33
33
|
/** @type {Array<{name:string, description:string, inputSchema:object}>} */
|
|
34
34
|
export const TOOL_DEFINITIONS = [
|
|
35
35
|
{
|
|
36
|
-
name: "
|
|
36
|
+
name: "pactive_status",
|
|
37
37
|
description:
|
|
38
|
-
"
|
|
38
|
+
"Pactive 主动机制状态:空闲判定与 reason、护栏四闸门、定时任务数、最近活动。只读。",
|
|
39
39
|
inputSchema: obj({}, []),
|
|
40
40
|
},
|
|
41
41
|
{
|
|
42
|
-
name: "
|
|
42
|
+
name: "pactive_schedule",
|
|
43
43
|
description:
|
|
44
44
|
"登记一个定时任务(真正落盘)。when 支持时长(10m)、时刻(18:30)、ISO 时间、every 1h。",
|
|
45
45
|
inputSchema: obj(
|
|
@@ -51,7 +51,7 @@ export const TOOL_DEFINITIONS = [
|
|
|
51
51
|
),
|
|
52
52
|
},
|
|
53
53
|
{
|
|
54
|
-
name: "
|
|
54
|
+
name: "pactive_tell",
|
|
55
55
|
description:
|
|
56
56
|
"给同机另一个会话投递一条消息。对端离线时进入 outbox 排队,不伪装成功。",
|
|
57
57
|
inputSchema: obj(
|
|
@@ -64,8 +64,8 @@ export const TOOL_DEFINITIONS = [
|
|
|
64
64
|
),
|
|
65
65
|
},
|
|
66
66
|
{
|
|
67
|
-
name: "
|
|
68
|
-
description: "读取
|
|
67
|
+
name: "pactive_history",
|
|
68
|
+
description: "读取 Pactive 事件账本尾部(触发→投递→回应→结账),用于追溯主动行为。",
|
|
69
69
|
inputSchema: obj({ limit: { type: "integer", description: "返回条数,默认 20" } }, []),
|
|
70
70
|
},
|
|
71
71
|
];
|
|
@@ -98,7 +98,7 @@ export async function runTool(name, args = {}, options = {}) {
|
|
|
98
98
|
|
|
99
99
|
try {
|
|
100
100
|
switch (name) {
|
|
101
|
-
case "
|
|
101
|
+
case "pactive_status": {
|
|
102
102
|
const state = (await readState(core, paths)) ?? core.defaultState();
|
|
103
103
|
const policy = state.policy ?? {};
|
|
104
104
|
const idle = core.evaluateIdle ? core.evaluateIdle(state, now, policy) : { idle: false, reason: "requires-action" };
|
|
@@ -117,7 +117,7 @@ export async function runTool(name, args = {}, options = {}) {
|
|
|
117
117
|
});
|
|
118
118
|
}
|
|
119
119
|
|
|
120
|
-
case "
|
|
120
|
+
case "pactive_schedule": {
|
|
121
121
|
const prompt = typeof args.prompt === "string" ? args.prompt.trim() : "";
|
|
122
122
|
if (prompt === "") return json("ERROR INVALID_ARGUMENT: prompt 必填", true);
|
|
123
123
|
const spec = parseWhen(args.when, now);
|
|
@@ -138,7 +138,7 @@ const next = await mutateState(core, paths, (state) => {
|
|
|
138
138
|
});
|
|
139
139
|
}
|
|
140
140
|
|
|
141
|
-
case "
|
|
141
|
+
case "pactive_tell": {
|
|
142
142
|
const to = typeof args.to === "string" ? args.to.trim() : "";
|
|
143
143
|
const body = typeof args.body === "string" ? args.body.trim() : "";
|
|
144
144
|
if (to === "" || body === "") return json("ERROR INVALID_ARGUMENT: to 与 body 必填", true);
|
|
@@ -158,7 +158,7 @@ const next = await mutateState(core, paths, (state) => {
|
|
|
158
158
|
});
|
|
159
159
|
}
|
|
160
160
|
|
|
161
|
-
case "
|
|
161
|
+
case "pactive_history": {
|
|
162
162
|
const limit = Number.isInteger(args.limit) ? args.limit : 20;
|
|
163
163
|
const events = core.ledger?.tail ? core.ledger.tail(paths.ledgerFile, limit) : [];
|
|
164
164
|
return json({ count: Array.isArray(events) ? events.length : 0, events });
|
package/package.json
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pactive",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.0.2-alpha.1",
|
|
4
|
+
"description": "Pactive:跨 Pi / OPCteam / DSH 的主动能力运行时(idle 判定默认关闭、人工闸门、事件账本)。专有软件:使用条款见包内 LICENSE(发行物含 Pi 扩展源码 pi/extension.ts)。",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"bin": {
|
|
8
|
-
"pactive": "bin/
|
|
9
|
-
"acti": "bin/acti.mjs"
|
|
8
|
+
"pactive": "bin/pactive.mjs"
|
|
10
9
|
},
|
|
11
10
|
"exports": {
|
|
12
11
|
".": "./src/core/index.mjs",
|
|
@@ -33,7 +32,7 @@
|
|
|
33
32
|
"./pi/extension.ts"
|
|
34
33
|
],
|
|
35
34
|
"skills": [
|
|
36
|
-
"./pi/skills/
|
|
35
|
+
"./pi/skills/pactive"
|
|
37
36
|
]
|
|
38
37
|
},
|
|
39
38
|
"dsh": {
|
package/pi/extension.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Pactive — Pi extension (the single shipped entry (Pi loads this .ts directly via jiti; there is no dist copy)).
|
|
3
3
|
*
|
|
4
4
|
* Surface:
|
|
5
|
-
* - tools: `
|
|
6
|
-
* - command: `/
|
|
5
|
+
* - tools: `pactive_status`, `pactive_schedule`
|
|
6
|
+
* - command: `/pactive status|tasks|schedule <when> <prompt>|help`
|
|
7
7
|
* - events: session_start / input / agent_start / tool_execution_start /
|
|
8
8
|
* message_end / agent_settled / session_shutdown → activity + control tracking
|
|
9
9
|
*
|
|
@@ -21,12 +21,12 @@
|
|
|
21
21
|
*/
|
|
22
22
|
|
|
23
23
|
/** Minimal mirror of the host API (kept local so the extension stays dependency-free). */
|
|
24
|
-
export interface
|
|
24
|
+
export interface PactiveToolResult {
|
|
25
25
|
content: Array<{ type: "text"; text: string }>;
|
|
26
26
|
details?: Record<string, unknown>;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
export interface
|
|
29
|
+
export interface PactiveToolDefinition {
|
|
30
30
|
name: string;
|
|
31
31
|
label?: string;
|
|
32
32
|
description: string;
|
|
@@ -37,22 +37,22 @@ export interface ActiToolDefinition {
|
|
|
37
37
|
signal?: AbortSignal,
|
|
38
38
|
onUpdate?: (update: unknown) => void,
|
|
39
39
|
ctx?: unknown,
|
|
40
|
-
): Promise<
|
|
40
|
+
): Promise<PactiveToolResult>;
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
export interface
|
|
44
|
-
registerTool(tool:
|
|
43
|
+
export interface PactiveExtensionAPI {
|
|
44
|
+
registerTool(tool: PactiveToolDefinition): void;
|
|
45
45
|
registerCommand(
|
|
46
46
|
name: string,
|
|
47
47
|
definition: {
|
|
48
48
|
description: string;
|
|
49
|
-
handler(args: string, ctx:
|
|
49
|
+
handler(args: string, ctx: PactiveUiContext): Promise<void> | void;
|
|
50
50
|
},
|
|
51
51
|
): void;
|
|
52
52
|
on(event: string, handler: (event: any, ctx?: any) => void): void;
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
export interface
|
|
55
|
+
export interface PactiveUiContext {
|
|
56
56
|
ui?: { notify?: (message: string, level?: string) => void };
|
|
57
57
|
sessionManager?: { getSessionId?: () => string | undefined };
|
|
58
58
|
cwd?: string;
|
|
@@ -73,7 +73,7 @@ function errorText(err: unknown): string {
|
|
|
73
73
|
async function loadCore(): Promise<any> {
|
|
74
74
|
if (cachedCore) return cachedCore;
|
|
75
75
|
const env = process.env;
|
|
76
|
-
const explicit = env.
|
|
76
|
+
const explicit = env.PACTIVE_CORE_MODULE;
|
|
77
77
|
const candidates = explicit ? [explicit, ...CORE_CANDIDATES] : CORE_CANDIDATES;
|
|
78
78
|
const tried: string[] = [];
|
|
79
79
|
for (const candidate of candidates) {
|
|
@@ -92,7 +92,7 @@ async function loadCore(): Promise<any> {
|
|
|
92
92
|
}
|
|
93
93
|
}
|
|
94
94
|
throw new Error(
|
|
95
|
-
`
|
|
95
|
+
`pactive: core module not found (tried: ${tried.join(", ")}). Build src/core before loading the extension.`,
|
|
96
96
|
);
|
|
97
97
|
}
|
|
98
98
|
|
|
@@ -189,7 +189,7 @@ function parseWhen(when: unknown, now = Date.now()): { kind: string; when: strin
|
|
|
189
189
|
return null;
|
|
190
190
|
}
|
|
191
191
|
|
|
192
|
-
function textResult(text: string, details?: Record<string, unknown>):
|
|
192
|
+
function textResult(text: string, details?: Record<string, unknown>): PactiveToolResult {
|
|
193
193
|
return details ? { content: [{ type: "text", text }], details } : { content: [{ type: "text", text }] };
|
|
194
194
|
}
|
|
195
195
|
|
|
@@ -214,7 +214,7 @@ async function markActivity(kind: "user" | "agent" | "session-start" | "session-
|
|
|
214
214
|
return next && typeof next === "object" ? next : state;
|
|
215
215
|
});
|
|
216
216
|
} catch (err) {
|
|
217
|
-
process.stderr.write(`[
|
|
217
|
+
process.stderr.write(`[pactive] markActivity(${kind}): ${errorText(err)}\n`);
|
|
218
218
|
}
|
|
219
219
|
}
|
|
220
220
|
|
|
@@ -228,16 +228,16 @@ async function markControlReturned(): Promise<void> {
|
|
|
228
228
|
return next && typeof next === "object" ? next : state;
|
|
229
229
|
});
|
|
230
230
|
} catch (err) {
|
|
231
|
-
process.stderr.write(`[
|
|
231
|
+
process.stderr.write(`[pactive] markControlReturned: ${errorText(err)}\n`);
|
|
232
232
|
}
|
|
233
233
|
}
|
|
234
234
|
|
|
235
|
-
export default function activate(pi:
|
|
235
|
+
export default function activate(pi: PactiveExtensionAPI): void {
|
|
236
236
|
pi.registerTool({
|
|
237
|
-
name: "
|
|
238
|
-
label: "
|
|
237
|
+
name: "pactive_status",
|
|
238
|
+
label: "Pactive Status",
|
|
239
239
|
description:
|
|
240
|
-
"
|
|
240
|
+
"Pactive 主动机制状态:空闲判定与 reason、护栏四闸门、定时任务数、最近活动。只读。",
|
|
241
241
|
parameters: STATUS_PARAMETERS,
|
|
242
242
|
async execute() {
|
|
243
243
|
try {
|
|
@@ -268,24 +268,24 @@ export default function activate(pi: ActiExtensionAPI): void {
|
|
|
268
268
|
{ host: HOST_KIND },
|
|
269
269
|
);
|
|
270
270
|
} catch (err) {
|
|
271
|
-
return textResult(`
|
|
271
|
+
return textResult(`pactive_status 失败:${errorText(err)}`, { error: true });
|
|
272
272
|
}
|
|
273
273
|
},
|
|
274
274
|
});
|
|
275
275
|
|
|
276
276
|
pi.registerTool({
|
|
277
|
-
name: "
|
|
278
|
-
label: "
|
|
277
|
+
name: "pactive_schedule",
|
|
278
|
+
label: "Pactive Schedule",
|
|
279
279
|
description: "登记一个定时任务(真正落盘)。when 支持时长(10m)、时刻(18:30)、ISO 时间、every 1h。",
|
|
280
280
|
parameters: SCHEDULE_PARAMETERS,
|
|
281
281
|
async execute(_toolCallId: string, params: Record<string, any>) {
|
|
282
282
|
try {
|
|
283
283
|
const prompt = typeof params?.prompt === "string" ? params.prompt.trim() : "";
|
|
284
|
-
if (prompt === "") return textResult("
|
|
284
|
+
if (prompt === "") return textResult("pactive_schedule 失败:prompt 必填", { error: true });
|
|
285
285
|
const now = Date.now();
|
|
286
286
|
const spec = parseWhen(params?.when, now);
|
|
287
287
|
if (!spec) {
|
|
288
|
-
return textResult(`
|
|
288
|
+
return textResult(`pactive_schedule 失败:无法解析 when(${String(params?.when)})`, { error: true });
|
|
289
289
|
}
|
|
290
290
|
const core = await loadCore();
|
|
291
291
|
const paths = core.resolvePaths(HOST_KIND, process.env);
|
|
@@ -316,21 +316,21 @@ export default function activate(pi: ActiExtensionAPI): void {
|
|
|
316
316
|
{ taskId: created?.id ?? null },
|
|
317
317
|
);
|
|
318
318
|
} catch (err) {
|
|
319
|
-
return textResult(`
|
|
319
|
+
return textResult(`pactive_schedule 失败:${errorText(err)}`, { error: true });
|
|
320
320
|
}
|
|
321
321
|
},
|
|
322
322
|
});
|
|
323
323
|
|
|
324
|
-
pi.registerCommand("
|
|
325
|
-
description: "
|
|
326
|
-
async handler(args: string, ctx:
|
|
324
|
+
pi.registerCommand("pactive", {
|
|
325
|
+
description: "Pactive:status | tasks | schedule <when> <prompt> | help",
|
|
326
|
+
async handler(args: string, ctx: PactiveUiContext) {
|
|
327
327
|
const notify = (message: string, level = "info") => {
|
|
328
328
|
try {
|
|
329
329
|
ctx?.ui?.notify?.(message, level);
|
|
330
330
|
} catch {
|
|
331
331
|
/* the host may not have a UI in non-interactive modes */
|
|
332
332
|
}
|
|
333
|
-
process.stderr.write(`[
|
|
333
|
+
process.stderr.write(`[pactive] ${message}\n`);
|
|
334
334
|
};
|
|
335
335
|
const parts = String(args ?? "").trim().split(/\s+/).filter(Boolean);
|
|
336
336
|
const sub = parts[0] ?? "status";
|
|
@@ -357,7 +357,7 @@ export default function activate(pi: ActiExtensionAPI): void {
|
|
|
357
357
|
notify(
|
|
358
358
|
list.length === 0
|
|
359
359
|
? "没有定时任务"
|
|
360
|
-
: `
|
|
360
|
+
: `pactive: ${list.length} 个定时任务\n` +
|
|
361
361
|
list.map((task: any) => `- [${task?.id ?? "?"}] ${task?.prompt ?? task?.content ?? ""}`).join("\n"),
|
|
362
362
|
);
|
|
363
363
|
return;
|
|
@@ -366,12 +366,12 @@ export default function activate(pi: ActiExtensionAPI): void {
|
|
|
366
366
|
const when = parts[1] ?? "10m";
|
|
367
367
|
const prompt = parts.slice(2).join(" ");
|
|
368
368
|
if (prompt === "") {
|
|
369
|
-
notify("用法:/
|
|
369
|
+
notify("用法:/pactive schedule <when> <prompt>", "warning");
|
|
370
370
|
return;
|
|
371
371
|
}
|
|
372
372
|
const spec = parseWhen(when, now);
|
|
373
373
|
if (!spec) {
|
|
374
|
-
notify(`
|
|
374
|
+
notify(`pactive: 无法解析 when(${when})`, "warning");
|
|
375
375
|
return;
|
|
376
376
|
}
|
|
377
377
|
const next = await mutateState(core, paths, (current) => {
|
|
@@ -385,12 +385,12 @@ export default function activate(pi: ActiExtensionAPI): void {
|
|
|
385
385
|
});
|
|
386
386
|
const tasks = core.schedule?.list ? core.schedule.list(next) : (next.tasks ?? []);
|
|
387
387
|
const created = Array.isArray(tasks) ? tasks[tasks.length - 1] : null;
|
|
388
|
-
notify(`
|
|
388
|
+
notify(`pactive: 已登记定时任务 ${created?.id ?? "-"}(${when})`);
|
|
389
389
|
return;
|
|
390
390
|
}
|
|
391
|
-
notify("用法:/
|
|
391
|
+
notify("用法:/pactive status | tasks | schedule <when> <prompt> | help", "warning");
|
|
392
392
|
} catch (err) {
|
|
393
|
-
notify(`
|
|
393
|
+
notify(`pactive 失败:${errorText(err)}`, "error");
|
|
394
394
|
}
|
|
395
395
|
},
|
|
396
396
|
});
|
|
@@ -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。
|