@akalsey/openclaw-sapience 0.1.0 → 0.1.2
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 +7 -7
- package/dist/src/processed-passes.js +16 -0
- package/dist/src/service.js +14 -7
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -6,14 +6,14 @@ The suite has four plugins that each work independently and compose into a whole
|
|
|
6
6
|
|
|
7
7
|
| Plugin | Does |
|
|
8
8
|
|--------|------|
|
|
9
|
-
| `openclaw-
|
|
9
|
+
| `openclaw-thinking` | Periodic thinking passes; generates observations and proposals |
|
|
10
10
|
| `openclaw-sapience` *(this plugin)* | Routes proposals through autonomy tiers; calibrates to your preferences; delivers weekly digest |
|
|
11
11
|
| `openclaw-feedback` | Captures corrections and confirmations from chat; recalibrates autonomy profile |
|
|
12
12
|
| `openclaw-goals` | Accepts fuzzy long-running goals; decomposes them; tracks progress; weekly status |
|
|
13
13
|
|
|
14
14
|
## How it works
|
|
15
15
|
|
|
16
|
-
`openclaw-
|
|
16
|
+
`openclaw-thinking` runs a thinking pass every 15 minutes and writes proposals to `proposals.jsonl`. `openclaw-sapience` reads that sidecar, routes each proposal through an autonomy decision function, and delivers it to your main session at the right level:
|
|
17
17
|
|
|
18
18
|
- **Act** — high-confidence, reversible, low-blast-radius → done immediately, brief notification
|
|
19
19
|
- **Propose** — worth doing, needs your approval → surfaces it for a yes/no
|
|
@@ -27,15 +27,15 @@ The routing decision uses a calibration profile: per-domain, per-action-class en
|
|
|
27
27
|
|
|
28
28
|
### Prerequisites
|
|
29
29
|
|
|
30
|
-
Install `openclaw-
|
|
30
|
+
Install `openclaw-thinking` first. Sapience reads its output.
|
|
31
31
|
|
|
32
32
|
### Install order
|
|
33
33
|
|
|
34
34
|
```bash
|
|
35
|
-
openclaw plugins install
|
|
36
|
-
openclaw plugins install
|
|
37
|
-
openclaw plugins install
|
|
38
|
-
openclaw plugins install
|
|
35
|
+
openclaw plugins install npm:@akalsey/openclaw-thinking
|
|
36
|
+
openclaw plugins install npm:@akalsey/openclaw-sapience
|
|
37
|
+
openclaw plugins install npm:@akalsey/openclaw-feedback # optional
|
|
38
|
+
openclaw plugins install npm:@akalsey/openclaw-goals # optional
|
|
39
39
|
```
|
|
40
40
|
|
|
41
41
|
### Configuration (sapience)
|
|
@@ -11,6 +11,22 @@ export async function loadProcessedPasses(path) {
|
|
|
11
11
|
return new Set();
|
|
12
12
|
}
|
|
13
13
|
}
|
|
14
|
+
export async function bootstrapProcessedPasses(proposalsPath, processedPath) {
|
|
15
|
+
try {
|
|
16
|
+
const content = await readFile(resolvePath(proposalsPath), "utf-8");
|
|
17
|
+
const ids = new Set(content.trim().split("\n").filter(Boolean)
|
|
18
|
+
.map(l => JSON.parse(l).pass_id));
|
|
19
|
+
if (ids.size === 0)
|
|
20
|
+
return ids;
|
|
21
|
+
const resolved = resolvePath(processedPath);
|
|
22
|
+
await mkdir(dirname(resolved), { recursive: true });
|
|
23
|
+
await writeFile(resolved, JSON.stringify({ pass_ids: [...ids] }, null, 2), "utf-8");
|
|
24
|
+
return ids;
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return new Set();
|
|
28
|
+
}
|
|
29
|
+
}
|
|
14
30
|
export async function markPassProcessed(passId, path, processed) {
|
|
15
31
|
const updated = new Set([...processed, passId]);
|
|
16
32
|
const resolved = resolvePath(path);
|
package/dist/src/service.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
// src/service.ts
|
|
2
|
+
import { writeFile, mkdir } from "fs/promises";
|
|
3
|
+
import { join } from "path";
|
|
2
4
|
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
|
|
3
5
|
import { DEFAULT_CONFIG } from "./types.js";
|
|
4
6
|
import { resolveDataPath, isWithinActiveHours } from "./utils.js";
|
|
5
7
|
import { loadProfile, saveProfile, upsertEntry } from "./calibration.js";
|
|
6
8
|
import { routeItem } from "./autonomy.js";
|
|
7
9
|
import { readUnprocessedPasses, proposalSetToItems } from "./proposal-adapter.js";
|
|
8
|
-
import { loadProcessedPasses, markPassProcessed } from "./processed-passes.js";
|
|
10
|
+
import { loadProcessedPasses, markPassProcessed, bootstrapProcessedPasses } from "./processed-passes.js";
|
|
9
11
|
import { deliverItems } from "./delivery.js";
|
|
10
12
|
import { isDigestDay, buildDigestPrompt } from "./weekly-digest.js";
|
|
11
13
|
function mergeConfig(raw, workspaceDir) {
|
|
@@ -33,22 +35,27 @@ function mergeConfig(raw, workspaceDir) {
|
|
|
33
35
|
export default definePluginEntry({
|
|
34
36
|
id: "sapience",
|
|
35
37
|
name: "Sapience",
|
|
36
|
-
description: "Autonomy layer: routes
|
|
38
|
+
description: "Autonomy layer: routes sapience-thinking proposals through tier function, calibrates to human preferences, delivers weekly digest",
|
|
37
39
|
register(api) {
|
|
38
40
|
const workspaceDir = api.runtime.agent.resolveAgentWorkspaceDir(api.pluginConfig);
|
|
39
41
|
const config = mergeConfig(api.pluginConfig, workspaceDir);
|
|
42
|
+
// Write presence marker so sapience-thinking knows to defer direct delivery
|
|
43
|
+
const markerDir = join(workspaceDir, "sapience");
|
|
44
|
+
void mkdir(markerDir, { recursive: true }).then(() => writeFile(join(markerDir, ".present"), "", "utf-8"));
|
|
40
45
|
api.registerTool({
|
|
41
46
|
name: "process_proposals",
|
|
42
|
-
description: "Process new proposals from the
|
|
47
|
+
description: "Process new proposals from the sapience-thinking log and route them through the autonomy tier function. Called by the sapience cron.",
|
|
43
48
|
parameters: {},
|
|
44
49
|
async execute(_id, _params) {
|
|
45
50
|
if (!isWithinActiveHours(config)) {
|
|
46
51
|
return { content: [{ type: "text", text: "SILENT_REPLY_TOKEN" }] };
|
|
47
52
|
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
53
|
+
let processed = await loadProcessedPasses(config.output.processedPassesPath);
|
|
54
|
+
const profile = await loadProfile(config.output.calibrationPath);
|
|
55
|
+
// On first run, mark all existing passes as processed to avoid re-delivering stale proposals
|
|
56
|
+
if (processed.size === 0) {
|
|
57
|
+
processed = await bootstrapProcessedPasses(config.proactiveThinking.proposalsPath, config.output.processedPassesPath);
|
|
58
|
+
}
|
|
52
59
|
const newPasses = await readUnprocessedPasses(config.proactiveThinking.proposalsPath, processed);
|
|
53
60
|
let updatedProcessed = processed;
|
|
54
61
|
let updatedProfile = profile;
|
package/openclaw.plugin.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"id": "sapience",
|
|
3
3
|
"name": "Sapience",
|
|
4
4
|
"version": "0.1.0",
|
|
5
|
-
"description": "Autonomy layer: routes
|
|
5
|
+
"description": "Autonomy layer: routes sapience-thinking proposals through tier function, calibrates to human preferences, delivers weekly digest",
|
|
6
6
|
"contracts": {
|
|
7
7
|
"tools": ["process_proposals"]
|
|
8
8
|
},
|