@gianfrancopiana/openclaw-autoresearch 1.0.5 → 1.0.7
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 +11 -5
- package/RELEASING.md +20 -3
- package/extensions/openclaw-autoresearch/index.ts +38 -11
- package/extensions/openclaw-autoresearch/src/commands/autoresearch.ts +94 -54
- package/extensions/openclaw-autoresearch/src/execute.ts +1 -1
- package/extensions/openclaw-autoresearch/src/git.ts +1 -1
- package/extensions/openclaw-autoresearch/src/hooks.ts +56 -59
- package/extensions/openclaw-autoresearch/src/runtime-state.ts +188 -42
- package/extensions/openclaw-autoresearch/src/scope.ts +149 -0
- package/extensions/openclaw-autoresearch/src/session-lock.ts +81 -16
- package/extensions/openclaw-autoresearch/src/tools/autoresearch-status.ts +33 -20
- package/extensions/openclaw-autoresearch/src/tools/init-experiment.ts +25 -8
- package/extensions/openclaw-autoresearch/src/tools/log-experiment.ts +118 -118
- package/extensions/openclaw-autoresearch/src/tools/preflight.ts +255 -0
- package/extensions/openclaw-autoresearch/src/tools/run-experiment.ts +28 -10
- package/extensions/openclaw-autoresearch/src/tools/tool-cwd.ts +40 -11
- package/openclaw.plugin.json +9 -1
- package/package.json +20 -6
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@ Three tools drive the loop:
|
|
|
16
16
|
| `run_experiment` | Executes a shell command, times it, captures stdout/stderr, parses `METRIC name=number` lines, and opens a pending experiment window that must be logged before another run can start. |
|
|
17
17
|
| `log_experiment` | Records the pending run. The first logged run in a segment is tagged as the baseline automatically. `keep` auto-commits to git. `discard`/`crash` log without committing, and `discard` now requires an `idea` note that is appended to `autoresearch.ideas.md`. If the prior `run_experiment` captured the primary metric, `log_experiment` can infer `commit` and `metric` automatically. After 3+ runs in a segment, it also reports a confidence score for the best improvement versus noise. |
|
|
18
18
|
|
|
19
|
-
Each tool also accepts an optional `cwd` so callers can target a nested
|
|
19
|
+
In OpenClaw sessions, the plugin uses the host-provided `workspaceDir` as the normal repo root. Each tool also accepts an optional `cwd` so callers can explicitly target a nested or non-session repo when needed.
|
|
20
20
|
|
|
21
21
|
All state lives in six repo-root files:
|
|
22
22
|
|
|
@@ -33,7 +33,8 @@ The design is file-first: any agent can pick up the repo-root files and continue
|
|
|
33
33
|
|
|
34
34
|
## Install
|
|
35
35
|
|
|
36
|
-
Requires OpenClaw `2026.
|
|
36
|
+
Requires OpenClaw `2026.4.25` or newer.
|
|
37
|
+
Needs bash, git, and a git repo.
|
|
37
38
|
|
|
38
39
|
Use OpenClaw's plugin installer:
|
|
39
40
|
|
|
@@ -63,12 +64,15 @@ npm pack
|
|
|
63
64
|
openclaw plugins install ./gianfrancopiana-openclaw-autoresearch-<version>.tgz
|
|
64
65
|
```
|
|
65
66
|
|
|
66
|
-
The install command records the plugin, enables it, and
|
|
67
|
+
The install command records the plugin, enables it, and makes it available
|
|
68
|
+
after restart. OpenClaw reads the package metadata, loads the root
|
|
69
|
+
[`index.ts`](index.ts), and finds the manifest in
|
|
70
|
+
[`openclaw.plugin.json`](openclaw.plugin.json).
|
|
67
71
|
|
|
68
72
|
Verify:
|
|
69
73
|
|
|
70
74
|
- skill: `autoresearch-create`
|
|
71
|
-
- tools: `init_experiment`, `run_experiment`, `log_experiment`
|
|
75
|
+
- tools: `init_experiment`, `run_experiment`, `log_experiment`, `autoresearch_status`
|
|
72
76
|
- command: `/autoresearch` (recommended)
|
|
73
77
|
- direct skill fallback: `/skill autoresearch-create`
|
|
74
78
|
|
|
@@ -119,15 +123,17 @@ This port preserves upstream semantics, names, and file contracts while adapting
|
|
|
119
123
|
|
|
120
124
|
```bash
|
|
121
125
|
npm install --include=dev
|
|
126
|
+
npm run check:release-metadata
|
|
122
127
|
npm run typecheck
|
|
123
128
|
npm test
|
|
124
129
|
npm run validate
|
|
125
130
|
npm run release:verify
|
|
131
|
+
npm run smoke:openclaw-host -- /absolute/path/to/openclaw
|
|
126
132
|
```
|
|
127
133
|
|
|
128
134
|
Release instructions, including npm 2FA publishing, live in [`RELEASING.md`](RELEASING.md).
|
|
129
135
|
|
|
130
|
-
The local test shim supports typechecking and tests without a full OpenClaw host checkout. Runtime behavior depends on a real OpenClaw host.
|
|
136
|
+
The local test shim supports typechecking and tests without a full OpenClaw host checkout. Runtime behavior depends on a real OpenClaw host, so run the host smoke against a current checkout before release.
|
|
131
137
|
|
|
132
138
|
## License
|
|
133
139
|
|
package/RELEASING.md
CHANGED
|
@@ -7,7 +7,15 @@
|
|
|
7
7
|
|
|
8
8
|
## Release
|
|
9
9
|
|
|
10
|
-
1. Update
|
|
10
|
+
1. Update the package version in `package.json`, then sync generated metadata:
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npm run sync:release-metadata
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
If you change the minimum supported OpenClaw version, keep
|
|
17
|
+
`openclaw.install`, `openclaw.compat`, and `openclaw.build` aligned too.
|
|
18
|
+
|
|
11
19
|
2. Run the release checks:
|
|
12
20
|
|
|
13
21
|
```bash
|
|
@@ -15,7 +23,16 @@
|
|
|
15
23
|
npm run release:verify
|
|
16
24
|
```
|
|
17
25
|
|
|
18
|
-
|
|
26
|
+
CI runs the same release verification, so metadata drift should fail before
|
|
27
|
+
publish.
|
|
28
|
+
|
|
29
|
+
3. Smoke-test against a current local OpenClaw checkout:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
npm run smoke:openclaw-host -- /absolute/path/to/openclaw
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
4. Publish:
|
|
19
36
|
|
|
20
37
|
```bash
|
|
21
38
|
npm publish --otp=123456
|
|
@@ -23,7 +40,7 @@
|
|
|
23
40
|
|
|
24
41
|
Replace `123456` with the current code from your authenticator app.
|
|
25
42
|
|
|
26
|
-
|
|
43
|
+
5. Verify install:
|
|
27
44
|
|
|
28
45
|
```bash
|
|
29
46
|
openclaw plugins install @gianfrancopiana/openclaw-autoresearch
|
|
@@ -1,9 +1,12 @@
|
|
|
1
|
-
import
|
|
1
|
+
import {
|
|
2
|
+
definePluginEntry,
|
|
3
|
+
type OpenClawPluginApi,
|
|
4
|
+
type OpenClawPluginToolContext,
|
|
5
|
+
} from "openclaw/plugin-sdk/core";
|
|
2
6
|
import {
|
|
3
7
|
AUTORESEARCH_PLUGIN_DESCRIPTION,
|
|
4
8
|
AUTORESEARCH_PLUGIN_ID,
|
|
5
9
|
AUTORESEARCH_PLUGIN_NAME,
|
|
6
|
-
autoresearchPluginConfigSchema,
|
|
7
10
|
} from "./src/config.js";
|
|
8
11
|
import { createInitExperimentTool } from "./src/tools/init-experiment.js";
|
|
9
12
|
import { createRunExperimentTool } from "./src/tools/run-experiment.js";
|
|
@@ -12,19 +15,43 @@ import { createAutoresearchStatusTool } from "./src/tools/autoresearch-status.js
|
|
|
12
15
|
import { registerAutoresearchHooks } from "./src/hooks.js";
|
|
13
16
|
import { registerAutoresearchCommand } from "./src/commands/autoresearch.js";
|
|
14
17
|
|
|
15
|
-
|
|
18
|
+
function createToolFactory<TTool>(
|
|
19
|
+
createTool: (
|
|
20
|
+
api: OpenClawPluginApi,
|
|
21
|
+
toolContext?: Pick<OpenClawPluginToolContext, "sessionKey" | "sessionId" | "workspaceDir">,
|
|
22
|
+
) => TTool,
|
|
23
|
+
api: OpenClawPluginApi,
|
|
24
|
+
) {
|
|
25
|
+
return (toolContext: OpenClawPluginToolContext) => createTool(api, toolContext);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function registerRequiredTool<TTool>(
|
|
29
|
+
api: OpenClawPluginApi,
|
|
30
|
+
name: string,
|
|
31
|
+
createTool: (
|
|
32
|
+
api: OpenClawPluginApi,
|
|
33
|
+
toolContext?: Pick<OpenClawPluginToolContext, "sessionKey" | "sessionId" | "workspaceDir">,
|
|
34
|
+
) => TTool,
|
|
35
|
+
) {
|
|
36
|
+
api.registerTool(
|
|
37
|
+
createToolFactory(createTool, api) as Parameters<OpenClawPluginApi["registerTool"]>[0],
|
|
38
|
+
{
|
|
39
|
+
name,
|
|
40
|
+
optional: false,
|
|
41
|
+
},
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export default definePluginEntry({
|
|
16
46
|
id: AUTORESEARCH_PLUGIN_ID,
|
|
17
47
|
name: AUTORESEARCH_PLUGIN_NAME,
|
|
18
48
|
description: AUTORESEARCH_PLUGIN_DESCRIPTION,
|
|
19
|
-
configSchema: autoresearchPluginConfigSchema,
|
|
20
49
|
register(api: OpenClawPluginApi) {
|
|
21
50
|
registerAutoresearchHooks(api);
|
|
22
51
|
registerAutoresearchCommand(api);
|
|
23
|
-
api
|
|
24
|
-
api
|
|
25
|
-
api
|
|
26
|
-
api
|
|
52
|
+
registerRequiredTool(api, "init_experiment", createInitExperimentTool);
|
|
53
|
+
registerRequiredTool(api, "run_experiment", createRunExperimentTool);
|
|
54
|
+
registerRequiredTool(api, "log_experiment", createLogExperimentTool);
|
|
55
|
+
registerRequiredTool(api, "autoresearch_status", createAutoresearchStatusTool);
|
|
27
56
|
},
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
export default plugin;
|
|
57
|
+
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
|
-
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
|
|
2
|
+
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/core";
|
|
3
3
|
import {
|
|
4
4
|
AUTORESEARCH_ROOT_FILES,
|
|
5
5
|
getAutoresearchRootFilePath,
|
|
@@ -8,10 +8,9 @@ import {
|
|
|
8
8
|
import { reconstructStateFromJsonl } from "../state.js";
|
|
9
9
|
import { formatAutoresearchStatusText } from "../tools/autoresearch-status.js";
|
|
10
10
|
import {
|
|
11
|
-
|
|
11
|
+
clearAutoresearchRuntimeState,
|
|
12
12
|
getAutoresearchRuntimeState,
|
|
13
13
|
setAutoresearchPendingCommand,
|
|
14
|
-
setAutoresearchRunInFlight,
|
|
15
14
|
setAutoresearchRuntimeMode,
|
|
16
15
|
} from "../runtime-state.js";
|
|
17
16
|
import {
|
|
@@ -19,11 +18,16 @@ import {
|
|
|
19
18
|
getAutoresearchSessionLockStatus,
|
|
20
19
|
removeAutoresearchSessionLock,
|
|
21
20
|
} from "../session-lock.js";
|
|
21
|
+
import { type AutoresearchScopeRef, resolveAutoresearchScope } from "../scope.js";
|
|
22
22
|
|
|
23
23
|
type CommandContext = {
|
|
24
24
|
args?: string;
|
|
25
25
|
channel?: string;
|
|
26
26
|
senderId?: string;
|
|
27
|
+
sessionKey?: string;
|
|
28
|
+
sessionId?: string;
|
|
29
|
+
workspaceDir?: string;
|
|
30
|
+
runId?: string;
|
|
27
31
|
cwd?: string;
|
|
28
32
|
};
|
|
29
33
|
|
|
@@ -45,7 +49,7 @@ export function registerAutoresearchCommand(api: OpenClawPluginApi): void {
|
|
|
45
49
|
description: "Enable, disable, or inspect repo-root autoresearch mode.",
|
|
46
50
|
acceptsArgs: true,
|
|
47
51
|
handler: (ctx: CommandContext) => {
|
|
48
|
-
const
|
|
52
|
+
const scope = resolveCommandScope(ctx);
|
|
49
53
|
const rawArgs = (ctx.args ?? "").trim();
|
|
50
54
|
const [verb, ...rest] = rawArgs.split(/\s+/).filter(Boolean);
|
|
51
55
|
const action = (verb ?? "").toLowerCase();
|
|
@@ -53,18 +57,19 @@ export function registerAutoresearchCommand(api: OpenClawPluginApi): void {
|
|
|
53
57
|
|
|
54
58
|
if (!rawArgs || action === "resume" || action === "on") {
|
|
55
59
|
return {
|
|
56
|
-
text: enableAutoresearchMode(
|
|
60
|
+
text: enableAutoresearchMode(
|
|
61
|
+
scope,
|
|
62
|
+
rawArgs && action !== "resume" && action !== "on" ? rawArgs : remainder,
|
|
63
|
+
),
|
|
57
64
|
};
|
|
58
65
|
}
|
|
59
66
|
if (action === "setup") {
|
|
60
|
-
return { text: primeAutoresearchSetup(
|
|
67
|
+
return { text: primeAutoresearchSetup(scope, remainder) };
|
|
61
68
|
}
|
|
62
69
|
if (action === "off") {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
setAutoresearchRunInFlight(cwd, false);
|
|
67
|
-
removeAutoresearchSessionLock(cwd);
|
|
70
|
+
clearAutoresearchRuntimeState(scope);
|
|
71
|
+
setAutoresearchRuntimeMode(scope, "off");
|
|
72
|
+
removeAutoresearchSessionLock(scope);
|
|
68
73
|
return {
|
|
69
74
|
text: [
|
|
70
75
|
"Autoresearch mode OFF.",
|
|
@@ -73,30 +78,35 @@ export function registerAutoresearchCommand(api: OpenClawPluginApi): void {
|
|
|
73
78
|
};
|
|
74
79
|
}
|
|
75
80
|
if (action === "status") {
|
|
76
|
-
return { text: buildAutoresearchCommandText(
|
|
81
|
+
return { text: buildAutoresearchCommandText(scope, "status") };
|
|
77
82
|
}
|
|
78
83
|
if (action === "help") {
|
|
79
|
-
return { text: `${COMMAND_USAGE}\n\n${buildAutoresearchCommandText(
|
|
84
|
+
return { text: `${COMMAND_USAGE}\n\n${buildAutoresearchCommandText(scope, "default")}` };
|
|
80
85
|
}
|
|
81
86
|
|
|
82
87
|
return {
|
|
83
|
-
text: enableAutoresearchMode(
|
|
88
|
+
text: enableAutoresearchMode(scope, rawArgs),
|
|
84
89
|
};
|
|
85
90
|
},
|
|
86
91
|
});
|
|
87
92
|
}
|
|
88
93
|
|
|
89
94
|
export function buildAutoresearchCommandText(
|
|
90
|
-
|
|
95
|
+
scopeRef: AutoresearchScopeRef,
|
|
91
96
|
mode: "default" | "status",
|
|
92
97
|
): string {
|
|
93
|
-
const
|
|
94
|
-
const
|
|
98
|
+
const scope = resolveAutoresearchScope(scopeRef);
|
|
99
|
+
const runtimeState = getAutoresearchRuntimeState(scopeRef);
|
|
100
|
+
if (!scope.repoDir) {
|
|
101
|
+
return buildWorkspacePendingCommandText(runtimeState);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const presentFiles = getPresentCanonicalFiles(scope.repoDir);
|
|
95
105
|
const presentSessionFiles = presentFiles.filter(
|
|
96
106
|
(file) => file !== AUTORESEARCH_ROOT_FILES.sessionLock,
|
|
97
107
|
);
|
|
98
108
|
const hasSession = presentSessionFiles.length > 0;
|
|
99
|
-
const lockStatus = getAutoresearchSessionLockStatus(
|
|
109
|
+
const lockStatus = getAutoresearchSessionLockStatus(scope);
|
|
100
110
|
|
|
101
111
|
if (!hasSession) {
|
|
102
112
|
return [
|
|
@@ -109,14 +119,18 @@ export function buildAutoresearchCommandText(
|
|
|
109
119
|
].join("\n");
|
|
110
120
|
}
|
|
111
121
|
|
|
112
|
-
const state = reconstructStateFromJsonl(
|
|
122
|
+
const state = reconstructStateFromJsonl(scope.repoDir);
|
|
113
123
|
const lines = [
|
|
114
124
|
`Autoresearch session detected at repo root: ${presentFiles.join(", ")}`,
|
|
115
125
|
`Read \`${AUTORESEARCH_ROOT_FILES.sessionDoc}\` before resuming or changing the loop.`,
|
|
116
126
|
];
|
|
117
127
|
|
|
118
128
|
if (mode === "status") {
|
|
119
|
-
lines.push(
|
|
129
|
+
lines.push(
|
|
130
|
+
"",
|
|
131
|
+
formatAutoresearchStatusText(state, runtimeState),
|
|
132
|
+
`Session lock: ${formatLockStatus(lockStatus)}`,
|
|
133
|
+
);
|
|
120
134
|
} else if (state.mode === "active" || state.hasSessionDoc) {
|
|
121
135
|
lines.push(
|
|
122
136
|
"Use `/autoresearch` or `/autoresearch on` to enable mode for the next agent turn, then continue the upstream loop with `init_experiment`, `run_experiment`, and `log_experiment` as needed.",
|
|
@@ -130,25 +144,47 @@ export function buildAutoresearchCommandText(
|
|
|
130
144
|
return lines.join("\n");
|
|
131
145
|
}
|
|
132
146
|
|
|
133
|
-
function
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
147
|
+
function buildWorkspacePendingCommandText(
|
|
148
|
+
runtimeState: ReturnType<typeof getAutoresearchRuntimeState>,
|
|
149
|
+
): string {
|
|
150
|
+
return [
|
|
151
|
+
"OpenClaw has not exposed a workspace root for this session yet.",
|
|
152
|
+
"Send a normal message from the target workspace, or invoke an autoresearch tool there, so the plugin can bind `workspaceDir` and inspect the repo-root files.",
|
|
153
|
+
`Runtime mode: ${runtimeState.mode}`,
|
|
154
|
+
`Pending run: ${runtimeState.pendingRun ? "yes" : "no"}`,
|
|
155
|
+
].join("\n");
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function enableAutoresearchMode(scopeRef: AutoresearchScopeRef, args: string | null): string {
|
|
159
|
+
const scope = resolveAutoresearchScope(scopeRef);
|
|
160
|
+
if (scope.repoDir) {
|
|
161
|
+
const lockStatus = acquireAutoresearchSessionLock(scope);
|
|
162
|
+
if (lockStatus.state === "active" && !lockStatus.ownedByCurrentSession) {
|
|
163
|
+
return [
|
|
164
|
+
"Autoresearch mode NOT enabled.",
|
|
165
|
+
`Another live autoresearch loop holds autoresearch.lock (PID ${lockStatus.pid}, started ${new Date(lockStatus.timestamp ?? 0).toISOString()}).`,
|
|
166
|
+
"Resume that loop instead of creating a parallel session.",
|
|
167
|
+
].join("\n");
|
|
168
|
+
}
|
|
141
169
|
}
|
|
142
170
|
|
|
143
|
-
setAutoresearchRuntimeMode(
|
|
144
|
-
const presentFiles = getPresentCanonicalFiles(
|
|
171
|
+
setAutoresearchRuntimeMode(scopeRef, "on");
|
|
172
|
+
const presentFiles = scope.repoDir ? getPresentCanonicalFiles(scope.repoDir) : [];
|
|
145
173
|
const hasSession = presentFiles.some((file) => file !== AUTORESEARCH_ROOT_FILES.sessionLock);
|
|
174
|
+
setAutoresearchPendingCommand(scopeRef, {
|
|
175
|
+
kind: hasSession ? "resume" : "setup",
|
|
176
|
+
args,
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
if (!scope.repoDir) {
|
|
180
|
+
return [
|
|
181
|
+
"Autoresearch mode ON.",
|
|
182
|
+
"Workspace root is not bound yet, so the next agent or tool turn for this session will resolve it from OpenClaw `workspaceDir` before continuing.",
|
|
183
|
+
args ? `Captured instruction: ${args}` : "Send a normal message to continue once the workspace is available.",
|
|
184
|
+
].join("\n");
|
|
185
|
+
}
|
|
146
186
|
|
|
147
187
|
if (!hasSession) {
|
|
148
|
-
setAutoresearchPendingCommand(cwd, {
|
|
149
|
-
kind: "setup",
|
|
150
|
-
args,
|
|
151
|
-
});
|
|
152
188
|
return [
|
|
153
189
|
"Autoresearch mode ON.",
|
|
154
190
|
"No repo-root session was detected, so the next agent turn will be primed for setup.",
|
|
@@ -158,10 +194,6 @@ function enableAutoresearchMode(cwd: string, args: string | null): string {
|
|
|
158
194
|
].join("\n");
|
|
159
195
|
}
|
|
160
196
|
|
|
161
|
-
setAutoresearchPendingCommand(cwd, {
|
|
162
|
-
kind: "resume",
|
|
163
|
-
args,
|
|
164
|
-
});
|
|
165
197
|
return [
|
|
166
198
|
"Autoresearch mode ON.",
|
|
167
199
|
`Next agent turn will be primed from \`${AUTORESEARCH_ROOT_FILES.sessionDoc}\` and the canonical repo-root files.`,
|
|
@@ -169,35 +201,43 @@ function enableAutoresearchMode(cwd: string, args: string | null): string {
|
|
|
169
201
|
].join("\n");
|
|
170
202
|
}
|
|
171
203
|
|
|
172
|
-
function primeAutoresearchSetup(
|
|
173
|
-
const
|
|
174
|
-
if (
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
204
|
+
function primeAutoresearchSetup(scopeRef: AutoresearchScopeRef, args: string | null): string {
|
|
205
|
+
const scope = resolveAutoresearchScope(scopeRef);
|
|
206
|
+
if (scope.repoDir) {
|
|
207
|
+
const lockStatus = acquireAutoresearchSessionLock(scope);
|
|
208
|
+
if (lockStatus.state === "active" && !lockStatus.ownedByCurrentSession) {
|
|
209
|
+
return [
|
|
210
|
+
"Autoresearch setup NOT primed.",
|
|
211
|
+
`Another live autoresearch loop holds autoresearch.lock (PID ${lockStatus.pid}, started ${new Date(lockStatus.timestamp ?? 0).toISOString()}).`,
|
|
212
|
+
"Resume that loop instead of starting a parallel setup flow.",
|
|
213
|
+
].join("\n");
|
|
214
|
+
}
|
|
180
215
|
}
|
|
181
216
|
|
|
182
|
-
setAutoresearchRuntimeMode(
|
|
183
|
-
setAutoresearchPendingCommand(
|
|
217
|
+
setAutoresearchRuntimeMode(scopeRef, "on");
|
|
218
|
+
setAutoresearchPendingCommand(scopeRef, {
|
|
184
219
|
kind: "setup",
|
|
185
220
|
args,
|
|
186
221
|
});
|
|
187
222
|
|
|
188
223
|
return [
|
|
189
224
|
"Autoresearch setup primed.",
|
|
190
|
-
|
|
225
|
+
scope.repoDir
|
|
226
|
+
? "The next agent turn will be told to create the canonical repo-root files and start the loop."
|
|
227
|
+
: "The next agent turn will wait for OpenClaw to provide a workspace root, then create the canonical repo-root files and start the loop.",
|
|
191
228
|
"Continue with a normal message on the next turn, or invoke the skill directly with `/skill autoresearch-create`.",
|
|
192
229
|
args ? `Captured setup instruction: ${args}` : "Add an argument to `/autoresearch setup` if you want a specific goal or constraint carried forward.",
|
|
193
230
|
].join("\n");
|
|
194
231
|
}
|
|
195
232
|
|
|
196
|
-
function
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
233
|
+
function resolveCommandScope(ctx: CommandContext): AutoresearchScopeRef {
|
|
234
|
+
return {
|
|
235
|
+
sessionKey: ctx.sessionKey,
|
|
236
|
+
sessionId: ctx.sessionId,
|
|
237
|
+
workspaceDir: ctx.workspaceDir,
|
|
238
|
+
runId: ctx.runId,
|
|
239
|
+
legacyCwd: ctx.cwd,
|
|
240
|
+
};
|
|
201
241
|
}
|
|
202
242
|
|
|
203
243
|
function getPresentCanonicalFiles(cwd: string): string[] {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
|
|
1
|
+
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/core";
|
|
2
2
|
import { AUTORESEARCH_ROOT_FILES } from "./files.js";
|
|
3
3
|
import { reconstructStateFromJsonl } from "./state.js";
|
|
4
4
|
import { readAutoresearchCheckpoint } from "./checkpoint.js";
|
|
@@ -12,6 +12,11 @@ import {
|
|
|
12
12
|
setAutoresearchContinuationReminder,
|
|
13
13
|
} from "./runtime-state.js";
|
|
14
14
|
import { removeAutoresearchSessionLock } from "./session-lock.js";
|
|
15
|
+
import {
|
|
16
|
+
forgetAutoresearchScope,
|
|
17
|
+
type AutoresearchScopeRef,
|
|
18
|
+
resolveAutoresearchScope,
|
|
19
|
+
} from "./scope.js";
|
|
15
20
|
|
|
16
21
|
type BeforeAgentStartEvent = {
|
|
17
22
|
systemPrompt?: string;
|
|
@@ -19,9 +24,13 @@ type BeforeAgentStartEvent = {
|
|
|
19
24
|
|
|
20
25
|
type HookContext = {
|
|
21
26
|
cwd?: string;
|
|
27
|
+
workspaceDir?: string;
|
|
28
|
+
sessionKey?: string;
|
|
29
|
+
sessionId?: string;
|
|
30
|
+
runId?: string;
|
|
22
31
|
};
|
|
23
32
|
|
|
24
|
-
type HookCapablePluginApi =
|
|
33
|
+
type HookCapablePluginApi = {
|
|
25
34
|
on?: (hookName: string, handler: (event: unknown, ctx: HookContext) => unknown) => void;
|
|
26
35
|
registerHook?: (
|
|
27
36
|
hookName: string,
|
|
@@ -30,15 +39,10 @@ type HookCapablePluginApi = OpenClawPluginApi & {
|
|
|
30
39
|
};
|
|
31
40
|
|
|
32
41
|
export function registerAutoresearchHooks(api: OpenClawPluginApi): void {
|
|
33
|
-
const hookApi = api as HookCapablePluginApi;
|
|
42
|
+
const hookApi = api as unknown as HookCapablePluginApi;
|
|
34
43
|
if (typeof hookApi.on === "function") {
|
|
35
44
|
hookApi.on("before_prompt_build", (_event, ctx) => {
|
|
36
|
-
const
|
|
37
|
-
if (cwd === null) {
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const addition = buildBeforePromptBuildContext(cwd);
|
|
45
|
+
const addition = buildBeforePromptBuildContext(resolveHookScope(ctx));
|
|
42
46
|
if (addition === null) {
|
|
43
47
|
return;
|
|
44
48
|
}
|
|
@@ -49,12 +53,8 @@ export function registerAutoresearchHooks(api: OpenClawPluginApi): void {
|
|
|
49
53
|
});
|
|
50
54
|
|
|
51
55
|
hookApi.on("message_received", (event, ctx) => {
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
return;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
const state = getAutoresearchRuntimeState(cwd);
|
|
56
|
+
const scope = resolveHookScope(ctx);
|
|
57
|
+
const state = getAutoresearchRuntimeState(scope);
|
|
58
58
|
if (!state.runInFlight) {
|
|
59
59
|
return;
|
|
60
60
|
}
|
|
@@ -64,12 +64,12 @@ export function registerAutoresearchHooks(api: OpenClawPluginApi): void {
|
|
|
64
64
|
return;
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
-
queueAutoresearchSteer(
|
|
67
|
+
queueAutoresearchSteer(scope, messageText);
|
|
68
68
|
});
|
|
69
69
|
|
|
70
70
|
hookApi.on("before_tool_call", (event, ctx) => {
|
|
71
|
-
const
|
|
72
|
-
if (
|
|
71
|
+
const scope = resolveHookScope(ctx);
|
|
72
|
+
if (!shouldEnforceAutoresearchMode(scope)) {
|
|
73
73
|
return;
|
|
74
74
|
}
|
|
75
75
|
|
|
@@ -92,25 +92,22 @@ export function registerAutoresearchHooks(api: OpenClawPluginApi): void {
|
|
|
92
92
|
});
|
|
93
93
|
|
|
94
94
|
hookApi.on("agent_end", (_event, ctx) => {
|
|
95
|
-
const
|
|
96
|
-
if (
|
|
95
|
+
const scope = resolveAutoresearchScope(resolveHookScope(ctx));
|
|
96
|
+
if (!scope.repoDir) {
|
|
97
97
|
return;
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
-
const state = reconstructStateFromJsonl(
|
|
100
|
+
const state = reconstructStateFromJsonl(scope.repoDir);
|
|
101
101
|
if (state.mode === "active" && state.ideas.hasBacklog) {
|
|
102
|
-
setAutoresearchContinuationReminder(
|
|
102
|
+
setAutoresearchContinuationReminder(resolveHookScope(ctx), true);
|
|
103
103
|
}
|
|
104
104
|
});
|
|
105
105
|
|
|
106
106
|
hookApi.on("session_end", (_event, ctx) => {
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
removeAutoresearchSessionLock(cwd);
|
|
113
|
-
clearAutoresearchRuntimeState(cwd);
|
|
107
|
+
const scope = resolveHookScope(ctx);
|
|
108
|
+
removeAutoresearchSessionLock(scope);
|
|
109
|
+
clearAutoresearchRuntimeState(scope);
|
|
110
|
+
forgetAutoresearchScope(scope);
|
|
114
111
|
});
|
|
115
112
|
return;
|
|
116
113
|
}
|
|
@@ -119,13 +116,8 @@ export function registerAutoresearchHooks(api: OpenClawPluginApi): void {
|
|
|
119
116
|
return;
|
|
120
117
|
}
|
|
121
118
|
|
|
122
|
-
hookApi.registerHook("before_agent_start", (event, ctx) => {
|
|
123
|
-
const
|
|
124
|
-
if (cwd === null) {
|
|
125
|
-
return;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
const addition = buildBeforePromptBuildContext(cwd);
|
|
119
|
+
hookApi.registerHook("before_agent_start", (event: BeforeAgentStartEvent, ctx: HookContext) => {
|
|
120
|
+
const addition = buildBeforePromptBuildContext(resolveHookScope(ctx));
|
|
129
121
|
if (addition === null) {
|
|
130
122
|
return;
|
|
131
123
|
}
|
|
@@ -137,11 +129,16 @@ export function registerAutoresearchHooks(api: OpenClawPluginApi): void {
|
|
|
137
129
|
});
|
|
138
130
|
}
|
|
139
131
|
|
|
140
|
-
export function buildBeforePromptBuildContext(
|
|
141
|
-
const
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
132
|
+
export function buildBeforePromptBuildContext(scopeRef: AutoresearchScopeRef): string | null {
|
|
133
|
+
const scope = resolveAutoresearchScope(scopeRef);
|
|
134
|
+
if (!scope.repoDir) {
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const state = reconstructStateFromJsonl(scope.repoDir);
|
|
139
|
+
const runtimeState = getAutoresearchRuntimeState(scopeRef);
|
|
140
|
+
const checkpoint = readAutoresearchCheckpoint(scope.repoDir);
|
|
141
|
+
if (!shouldEnforceAutoresearchMode(scopeRef, state, runtimeState)) {
|
|
145
142
|
return null;
|
|
146
143
|
}
|
|
147
144
|
|
|
@@ -150,8 +147,8 @@ export function buildBeforePromptBuildContext(cwd: string): string | null {
|
|
|
150
147
|
AUTORESEARCH_ROOT_FILES.runnerScript,
|
|
151
148
|
AUTORESEARCH_ROOT_FILES.resultsLog,
|
|
152
149
|
];
|
|
153
|
-
const pendingCommand = consumeAutoresearchPendingCommand(
|
|
154
|
-
const needsContinuationReminder = consumeAutoresearchContinuationReminder(
|
|
150
|
+
const pendingCommand = consumeAutoresearchPendingCommand(scopeRef);
|
|
151
|
+
const needsContinuationReminder = consumeAutoresearchContinuationReminder(scopeRef);
|
|
155
152
|
|
|
156
153
|
const lines = ["", "", "## Autoresearch Mode (ACTIVE)"];
|
|
157
154
|
|
|
@@ -195,7 +192,7 @@ export function buildBeforePromptBuildContext(cwd: string): string | null {
|
|
|
195
192
|
}
|
|
196
193
|
|
|
197
194
|
lines.push(
|
|
198
|
-
|
|
195
|
+
"For discard or crash results, log_experiment records the outcome but does not revert your tree for you. Run `git checkout -- .` yourself after logging when you want to discard tracked changes.",
|
|
199
196
|
);
|
|
200
197
|
|
|
201
198
|
if (state.ideas.hasBacklog) {
|
|
@@ -219,17 +216,14 @@ export function buildBeforePromptBuildContext(cwd: string): string | null {
|
|
|
219
216
|
return lines.join("\n");
|
|
220
217
|
}
|
|
221
218
|
|
|
222
|
-
function
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
} catch {
|
|
231
|
-
return null;
|
|
232
|
-
}
|
|
219
|
+
function resolveHookScope(ctx: HookContext | undefined): Exclude<AutoresearchScopeRef, string> {
|
|
220
|
+
return {
|
|
221
|
+
sessionKey: ctx?.sessionKey,
|
|
222
|
+
sessionId: ctx?.sessionId,
|
|
223
|
+
workspaceDir: ctx?.workspaceDir,
|
|
224
|
+
runId: ctx?.runId,
|
|
225
|
+
legacyCwd: ctx?.cwd,
|
|
226
|
+
};
|
|
233
227
|
}
|
|
234
228
|
|
|
235
229
|
function extractMessageText(event: unknown): string | null {
|
|
@@ -278,15 +272,18 @@ function isCommandLikeMessage(text: string): boolean {
|
|
|
278
272
|
}
|
|
279
273
|
|
|
280
274
|
function shouldEnforceAutoresearchMode(
|
|
281
|
-
|
|
282
|
-
state =
|
|
283
|
-
|
|
275
|
+
scopeRef: AutoresearchScopeRef,
|
|
276
|
+
state = (() => {
|
|
277
|
+
const scope = resolveAutoresearchScope(scopeRef);
|
|
278
|
+
return scope.repoDir ? reconstructStateFromJsonl(scope.repoDir) : null;
|
|
279
|
+
})(),
|
|
280
|
+
runtimeState = getAutoresearchRuntimeState(scopeRef),
|
|
284
281
|
): boolean {
|
|
285
282
|
return (
|
|
286
283
|
runtimeState.mode === "on" ||
|
|
287
284
|
runtimeState.runInFlight ||
|
|
288
285
|
runtimeState.pendingRun !== null ||
|
|
289
|
-
(runtimeState.mode !== "off" && (state.mode === "active" || state.hasSessionDoc))
|
|
286
|
+
(runtimeState.mode !== "off" && Boolean(state && (state.mode === "active" || state.hasSessionDoc)))
|
|
290
287
|
);
|
|
291
288
|
}
|
|
292
289
|
|