opensyn 0.1.0
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 +73 -0
- package/README.zh-CN.md +65 -0
- package/index.ts +3507 -0
- package/openclaw.plugin.json +173 -0
- package/package.json +33 -0
- package/runtime/bin/linux-x64/synapse-cli +0 -0
- package/runtime/bin/linux-x64/synapse-daemon +0 -0
- package/runtime/opensyn-cli +43 -0
- package/runtime/opensyn-daemon +43 -0
- package/runtime/opensyn-host-distiller.mjs +173 -0
- package/runtime/opensyn-ingest-command +124 -0
- package/runtime/opensyn-run +99 -0
- package/runtime/postinstall.mjs +85 -0
package/index.ts
ADDED
|
@@ -0,0 +1,3507 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import {
|
|
4
|
+
closeSync,
|
|
5
|
+
chmodSync,
|
|
6
|
+
copyFileSync,
|
|
7
|
+
existsSync,
|
|
8
|
+
mkdirSync,
|
|
9
|
+
openSync,
|
|
10
|
+
readFileSync,
|
|
11
|
+
readdirSync,
|
|
12
|
+
realpathSync,
|
|
13
|
+
rmSync,
|
|
14
|
+
statSync,
|
|
15
|
+
writeFileSync,
|
|
16
|
+
} from "node:fs";
|
|
17
|
+
import os from "node:os";
|
|
18
|
+
import path from "node:path";
|
|
19
|
+
import { fileURLToPath } from "node:url";
|
|
20
|
+
import { Type } from "@sinclair/typebox";
|
|
21
|
+
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
|
|
22
|
+
|
|
23
|
+
type ToolContent = { type: "text"; text: string };
|
|
24
|
+
|
|
25
|
+
type SemanticHint = {
|
|
26
|
+
activity_kind?: string;
|
|
27
|
+
operator_focus?: string;
|
|
28
|
+
status_label?: string;
|
|
29
|
+
matched_item_kind?: string;
|
|
30
|
+
summary_label?: string;
|
|
31
|
+
primary_signature?: string;
|
|
32
|
+
primary_artifact?: string;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
type ContextLike = {
|
|
36
|
+
title?: string;
|
|
37
|
+
summary?: string;
|
|
38
|
+
project_root?: string;
|
|
39
|
+
goal_guess?: string;
|
|
40
|
+
pack_type?: string;
|
|
41
|
+
recent_failures?: string[];
|
|
42
|
+
recent_changes?: string[];
|
|
43
|
+
commands?: string[];
|
|
44
|
+
files_touched?: string[];
|
|
45
|
+
semantic_hint?: SemanticHint;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
type SearchLike = {
|
|
49
|
+
title?: string;
|
|
50
|
+
snippet?: string;
|
|
51
|
+
item_kind?: string;
|
|
52
|
+
semantic_hint?: SemanticHint;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
type AssetLike = {
|
|
56
|
+
title?: string;
|
|
57
|
+
summary?: string;
|
|
58
|
+
kind?: string;
|
|
59
|
+
status?: string;
|
|
60
|
+
revision?: number;
|
|
61
|
+
semantic_hint?: SemanticHint & {
|
|
62
|
+
asset_kind?: string;
|
|
63
|
+
revision?: number;
|
|
64
|
+
};
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
type AssetHistoryLike = {
|
|
68
|
+
asset_id?: string;
|
|
69
|
+
lineage_key?: string;
|
|
70
|
+
revision?: number;
|
|
71
|
+
change_reason?: string;
|
|
72
|
+
asset?: AssetLike;
|
|
73
|
+
semantic_hint?: SemanticHint & {
|
|
74
|
+
asset_kind?: string;
|
|
75
|
+
revision?: number;
|
|
76
|
+
};
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
type AssetDiffLike = {
|
|
80
|
+
asset_id?: string;
|
|
81
|
+
lineage_key?: string;
|
|
82
|
+
from_revision?: number;
|
|
83
|
+
to_revision?: number;
|
|
84
|
+
title_changed?: boolean;
|
|
85
|
+
summary_changed?: boolean;
|
|
86
|
+
content_changed?: boolean;
|
|
87
|
+
added_tags?: string[];
|
|
88
|
+
removed_tags?: string[];
|
|
89
|
+
semantic_hint?: SemanticHint & {
|
|
90
|
+
asset_kind?: string;
|
|
91
|
+
};
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
type CollectionSettingsLike = {
|
|
95
|
+
project_root?: string;
|
|
96
|
+
mode?: string;
|
|
97
|
+
auto_distill?: boolean;
|
|
98
|
+
distill_on_flush?: boolean;
|
|
99
|
+
semantic_hint?: SemanticHint;
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
type ReviewPolicySettingsLike = {
|
|
103
|
+
project_root?: string;
|
|
104
|
+
auto_approve_memory?: boolean;
|
|
105
|
+
auto_approve_skill?: boolean;
|
|
106
|
+
auto_approve_rule?: boolean;
|
|
107
|
+
auto_approve_negative_example?: boolean;
|
|
108
|
+
min_confidence?: number;
|
|
109
|
+
max_auto_approve_revision?: number;
|
|
110
|
+
require_reapproval_on_revision_bump?: boolean;
|
|
111
|
+
semantic_hint?: SemanticHint;
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
type DistillationSettingsLike = {
|
|
115
|
+
project_root?: string;
|
|
116
|
+
backend?: string;
|
|
117
|
+
allow_network?: boolean;
|
|
118
|
+
restricted_to_host_model?: boolean;
|
|
119
|
+
auto_enqueue_on_flush?: boolean;
|
|
120
|
+
auto_apply_submissions?: boolean;
|
|
121
|
+
semantic_hint?: SemanticHint;
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
type DistillationJobLike = {
|
|
125
|
+
id?: string;
|
|
126
|
+
project_root?: string;
|
|
127
|
+
context_pack_id?: string;
|
|
128
|
+
status?: string;
|
|
129
|
+
title?: string;
|
|
130
|
+
source_summary?: string;
|
|
131
|
+
prompt?: string;
|
|
132
|
+
submitted_asset_ids?: string[];
|
|
133
|
+
semantic_hint?: SemanticHint;
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
type DistillationWorkPacketLike = {
|
|
137
|
+
job?: DistillationJobLike;
|
|
138
|
+
output_spec?: {
|
|
139
|
+
allowed_asset_kinds?: string[];
|
|
140
|
+
required_fields?: string[];
|
|
141
|
+
completion_tool?: string;
|
|
142
|
+
requeue_tool?: string;
|
|
143
|
+
completion_argument_key?: string;
|
|
144
|
+
notes?: string[];
|
|
145
|
+
};
|
|
146
|
+
executor_prompt?: string;
|
|
147
|
+
completion_arguments_template?: unknown;
|
|
148
|
+
runtime_overlay?: RuntimeOverlayLike | null;
|
|
149
|
+
review_policy?: ReviewPolicySettingsLike | null;
|
|
150
|
+
semantic_hint?: SemanticHint;
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
type AutonomousTickLike = {
|
|
154
|
+
contract_version?: string;
|
|
155
|
+
project_root?: string;
|
|
156
|
+
action?: string;
|
|
157
|
+
automation_state?: string;
|
|
158
|
+
cycle_plan?: {
|
|
159
|
+
primary_steps?: Array<unknown>;
|
|
160
|
+
fallback_steps?: Array<unknown>;
|
|
161
|
+
resume_steps?: Array<unknown>;
|
|
162
|
+
};
|
|
163
|
+
execution_plan?: Array<{
|
|
164
|
+
kind?: string;
|
|
165
|
+
title?: string;
|
|
166
|
+
tool?: string | null;
|
|
167
|
+
arguments_template?: unknown;
|
|
168
|
+
delay_secs?: number;
|
|
169
|
+
notes?: string[];
|
|
170
|
+
}>;
|
|
171
|
+
transition?: {
|
|
172
|
+
kind?: string;
|
|
173
|
+
success_state?: string;
|
|
174
|
+
failure_state?: string;
|
|
175
|
+
success_action_tool?: string | null;
|
|
176
|
+
success_action_arguments_template?: unknown;
|
|
177
|
+
failure_action_tool?: string | null;
|
|
178
|
+
failure_action_arguments_template?: unknown;
|
|
179
|
+
resume_action_tool?: string | null;
|
|
180
|
+
resume_action_arguments_template?: unknown;
|
|
181
|
+
resume_after_secs?: number;
|
|
182
|
+
stop_after_transition?: boolean;
|
|
183
|
+
};
|
|
184
|
+
pending_distillation_jobs?: number;
|
|
185
|
+
claimed_job_id?: string | null;
|
|
186
|
+
work_packet?: DistillationWorkPacketLike | null;
|
|
187
|
+
runtime_overlay?: RuntimeOverlayLike;
|
|
188
|
+
next_action_tool?: string | null;
|
|
189
|
+
next_action_instruction?: string;
|
|
190
|
+
should_continue?: boolean;
|
|
191
|
+
poll_after_secs?: number;
|
|
192
|
+
next_action_arguments_template?: unknown;
|
|
193
|
+
semantic_hint?: SemanticHint;
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
type ProjectionSettingsLike = {
|
|
197
|
+
project_root?: string;
|
|
198
|
+
enabled?: boolean;
|
|
199
|
+
host?: string;
|
|
200
|
+
target_root?: string;
|
|
201
|
+
auto_apply_approved?: boolean;
|
|
202
|
+
semantic_hint?: SemanticHint;
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
type ProjectionLike = {
|
|
206
|
+
asset_id?: string;
|
|
207
|
+
status?: string;
|
|
208
|
+
content?: string;
|
|
209
|
+
semantic_hint?: SemanticHint;
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
type ProjectionSyncLike = {
|
|
213
|
+
project_root?: string;
|
|
214
|
+
host?: string;
|
|
215
|
+
target_root?: string;
|
|
216
|
+
applied_asset_count?: number;
|
|
217
|
+
applied_record_count?: number;
|
|
218
|
+
skipped_asset_ids?: string[];
|
|
219
|
+
records?: ProjectionLike[];
|
|
220
|
+
semantic_hint?: SemanticHint;
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
type RuntimeOverlaySkillLike = {
|
|
224
|
+
asset_id?: string;
|
|
225
|
+
lineage_key?: string;
|
|
226
|
+
revision?: number;
|
|
227
|
+
title?: string;
|
|
228
|
+
summary?: string;
|
|
229
|
+
relative_path?: string;
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
type RuntimeOverlayLike = {
|
|
233
|
+
project_root?: string;
|
|
234
|
+
host?: string;
|
|
235
|
+
approved_asset_count?: number;
|
|
236
|
+
approved_asset_ids?: string[];
|
|
237
|
+
pending_distillation_jobs?: number | null;
|
|
238
|
+
target_root?: string | null;
|
|
239
|
+
index_markdown?: string;
|
|
240
|
+
memory_markdown?: string;
|
|
241
|
+
agents_markdown?: string;
|
|
242
|
+
automation_markdown?: string;
|
|
243
|
+
soul_markdown?: string;
|
|
244
|
+
skills?: RuntimeOverlaySkillLike[];
|
|
245
|
+
managed_files?: string[];
|
|
246
|
+
semantic_hint?: SemanticHint;
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
type BackgroundWatchStatus = {
|
|
250
|
+
supported: boolean;
|
|
251
|
+
running: boolean;
|
|
252
|
+
pid?: number;
|
|
253
|
+
source: "watch_linux" | "unsupported_platform";
|
|
254
|
+
project_root: string;
|
|
255
|
+
platform: string;
|
|
256
|
+
state_path: string;
|
|
257
|
+
log_path: string;
|
|
258
|
+
reason?: string;
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
type DistillationWorkerStatus = {
|
|
262
|
+
supported: boolean;
|
|
263
|
+
enabled: boolean;
|
|
264
|
+
running: boolean;
|
|
265
|
+
pid?: number;
|
|
266
|
+
source: "openclaw_host_model_agent";
|
|
267
|
+
project_root: string;
|
|
268
|
+
state_path: string;
|
|
269
|
+
log_path: string;
|
|
270
|
+
openclaw_bin: string;
|
|
271
|
+
agent_id: string;
|
|
272
|
+
interval_secs: number;
|
|
273
|
+
last_exit_code?: number | null;
|
|
274
|
+
last_error?: string;
|
|
275
|
+
last_finished_at?: string;
|
|
276
|
+
reason?: string;
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
type DistillationWorkerHealth = {
|
|
280
|
+
status: "running" | "disabled" | "stopped" | "auth_required" | "error";
|
|
281
|
+
summary: string;
|
|
282
|
+
action?: string;
|
|
283
|
+
log_path: string;
|
|
284
|
+
last_finished_at?: string;
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
type ShellHookDoctorLike = {
|
|
288
|
+
installed?: boolean;
|
|
289
|
+
self_test?: { success?: boolean };
|
|
290
|
+
recommendations?: Array<{ issue?: string; command?: string; severity?: string }>;
|
|
291
|
+
notes?: string[];
|
|
292
|
+
rc_file?: string;
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
type ShellCaptureHealth = {
|
|
296
|
+
status: "ready" | "pending_reload" | "missing" | "error";
|
|
297
|
+
summary: string;
|
|
298
|
+
action?: string;
|
|
299
|
+
};
|
|
300
|
+
|
|
301
|
+
type UserNextStep = {
|
|
302
|
+
status: "none" | "optional" | "required";
|
|
303
|
+
code:
|
|
304
|
+
| "none"
|
|
305
|
+
| "reload_shell"
|
|
306
|
+
| "repair_command_capture"
|
|
307
|
+
| "configure_model_auth"
|
|
308
|
+
| "restart_background_watch"
|
|
309
|
+
| "repair_distillation_worker"
|
|
310
|
+
| "unsupported_platform";
|
|
311
|
+
summary: string;
|
|
312
|
+
action?: string;
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
type OpenSynConfig = {
|
|
316
|
+
runtimeRoot?: string;
|
|
317
|
+
dbPath?: string;
|
|
318
|
+
cliBin?: string;
|
|
319
|
+
daemonBin?: string;
|
|
320
|
+
helperPath?: string;
|
|
321
|
+
runnerPath?: string;
|
|
322
|
+
defaultShell?: string;
|
|
323
|
+
defaultProjectRoot?: string;
|
|
324
|
+
defaultWindowSecs?: number;
|
|
325
|
+
defaultSearchLimit?: number;
|
|
326
|
+
daemonTimeoutMs?: number;
|
|
327
|
+
projectionBundleRoot?: string;
|
|
328
|
+
projectionHost?: string;
|
|
329
|
+
projectionAutoApplyApproved?: boolean;
|
|
330
|
+
watchIdleWindowSecs?: number;
|
|
331
|
+
watchPollIntervalSecs?: number;
|
|
332
|
+
openclawBin?: string;
|
|
333
|
+
distillationWorkerEnabled?: boolean;
|
|
334
|
+
distillationWorkerIntervalSecs?: number;
|
|
335
|
+
distillationWorkerAgentId?: string;
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
type ResolvedOpenSynConfig = {
|
|
339
|
+
runtimeRoot: string;
|
|
340
|
+
dbPath: string;
|
|
341
|
+
cliBin: string;
|
|
342
|
+
daemonBin: string;
|
|
343
|
+
helperPath: string;
|
|
344
|
+
runnerPath: string;
|
|
345
|
+
defaultShell: string;
|
|
346
|
+
defaultProjectRoot?: string;
|
|
347
|
+
defaultWindowSecs: number;
|
|
348
|
+
defaultSearchLimit: number;
|
|
349
|
+
daemonTimeoutMs: number;
|
|
350
|
+
projectionBundleRoot: string;
|
|
351
|
+
projectionHost: string;
|
|
352
|
+
projectionAutoApplyApproved: boolean;
|
|
353
|
+
watchIdleWindowSecs: number;
|
|
354
|
+
watchPollIntervalSecs: number;
|
|
355
|
+
openclawBin: string;
|
|
356
|
+
distillationWorkerEnabled: boolean;
|
|
357
|
+
distillationWorkerIntervalSecs: number;
|
|
358
|
+
distillationWorkerAgentId: string;
|
|
359
|
+
};
|
|
360
|
+
|
|
361
|
+
type RpcResponse = {
|
|
362
|
+
result?: unknown;
|
|
363
|
+
error?: {
|
|
364
|
+
message?: string;
|
|
365
|
+
};
|
|
366
|
+
};
|
|
367
|
+
|
|
368
|
+
type SpawnResponse = {
|
|
369
|
+
stdout: string;
|
|
370
|
+
stderr: string;
|
|
371
|
+
exitCode: number | null;
|
|
372
|
+
};
|
|
373
|
+
|
|
374
|
+
const PLUGIN_ID = "opensyn";
|
|
375
|
+
const PACKAGE_ROOT = path.dirname(fileURLToPath(import.meta.url));
|
|
376
|
+
|
|
377
|
+
function packageRuntimePath(...segments: string[]): string {
|
|
378
|
+
return path.join(PACKAGE_ROOT, "runtime", ...segments);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
function repoFallbackPath(...segments: string[]): string {
|
|
382
|
+
return path.resolve(PACKAGE_ROOT, "..", "..", ...segments);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
function resolvePreferredPath(candidates: Array<string | undefined>): string | undefined {
|
|
386
|
+
for (const candidate of candidates) {
|
|
387
|
+
if (!candidate) {
|
|
388
|
+
continue;
|
|
389
|
+
}
|
|
390
|
+
if (existsSync(candidate)) {
|
|
391
|
+
return candidate;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
return candidates.find(Boolean);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function readJsonFile<T>(filePath: string): T | undefined {
|
|
398
|
+
if (!existsSync(filePath)) {
|
|
399
|
+
return undefined;
|
|
400
|
+
}
|
|
401
|
+
try {
|
|
402
|
+
return JSON.parse(readFileSync(filePath, "utf8")) as T;
|
|
403
|
+
} catch {
|
|
404
|
+
return undefined;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
function ensureProjectRoot(projectRoot: string): void {
|
|
409
|
+
mkdirSync(projectRoot, { recursive: true });
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
function defaultDbPath(): string {
|
|
413
|
+
const home = process.env.HOME || os.homedir();
|
|
414
|
+
return path.join(home, ".opensyn", "opensyn.db");
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
function defaultRuntimeRoot(): string {
|
|
418
|
+
const home = process.env.HOME || os.homedir();
|
|
419
|
+
return path.join(home, ".opensyn");
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
function defaultShell(): string {
|
|
423
|
+
return path.basename(process.env.SHELL || "bash") || "bash";
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
function defaultOpenClawBin(): string {
|
|
427
|
+
if (process.env.OPENCLAW_BIN) {
|
|
428
|
+
return process.env.OPENCLAW_BIN;
|
|
429
|
+
}
|
|
430
|
+
const pathEnv = process.env.PATH || "";
|
|
431
|
+
for (const entry of pathEnv.split(path.delimiter)) {
|
|
432
|
+
if (!entry) {
|
|
433
|
+
continue;
|
|
434
|
+
}
|
|
435
|
+
const candidate = path.join(entry, "openclaw");
|
|
436
|
+
if (existsSync(candidate)) {
|
|
437
|
+
try {
|
|
438
|
+
return realpathSync(candidate);
|
|
439
|
+
} catch {
|
|
440
|
+
return candidate;
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
return "openclaw";
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
function stableNodeRunner(): string {
|
|
448
|
+
if (process.execPath && existsSync(process.execPath)) {
|
|
449
|
+
return process.execPath;
|
|
450
|
+
}
|
|
451
|
+
return existsSync("/usr/bin/node") ? "/usr/bin/node" : process.execPath;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
function defaultProjectionBundleRoot(): string {
|
|
455
|
+
const home = process.env.HOME || os.homedir();
|
|
456
|
+
return path.join(home, ".openclaw", "workspace");
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
function syncRuntimeTree(sourceDir: string, destDir: string): void {
|
|
460
|
+
if (!existsSync(sourceDir)) {
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
mkdirSync(destDir, { recursive: true });
|
|
465
|
+
for (const entry of readdirSync(sourceDir, { withFileTypes: true })) {
|
|
466
|
+
const sourcePath = path.join(sourceDir, entry.name);
|
|
467
|
+
const destPath = path.join(destDir, entry.name);
|
|
468
|
+
if (entry.isDirectory()) {
|
|
469
|
+
syncRuntimeTree(sourcePath, destPath);
|
|
470
|
+
continue;
|
|
471
|
+
}
|
|
472
|
+
const sourceStat = statSync(sourcePath);
|
|
473
|
+
const needsCopy =
|
|
474
|
+
!existsSync(destPath) ||
|
|
475
|
+
statSync(destPath).size !== sourceStat.size ||
|
|
476
|
+
statSync(destPath).mtimeMs < sourceStat.mtimeMs;
|
|
477
|
+
if (!needsCopy) {
|
|
478
|
+
continue;
|
|
479
|
+
}
|
|
480
|
+
mkdirSync(path.dirname(destPath), { recursive: true });
|
|
481
|
+
copyFileSync(sourcePath, destPath);
|
|
482
|
+
chmodSync(destPath, sourceStat.mode);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
function ensureRuntimeRootAssets(runtimeRoot: string): string {
|
|
487
|
+
const managedRuntimeDir = path.join(runtimeRoot, "runtime");
|
|
488
|
+
syncRuntimeTree(packageRuntimePath(), managedRuntimeDir);
|
|
489
|
+
return managedRuntimeDir;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
function resolveOpenSynConfig(
|
|
493
|
+
config: OpenSynConfig,
|
|
494
|
+
defaults?: { defaultProjectRoot?: string },
|
|
495
|
+
): ResolvedOpenSynConfig {
|
|
496
|
+
const runtimeRoot = config.runtimeRoot || defaultRuntimeRoot();
|
|
497
|
+
const managedRuntimeDir = ensureRuntimeRootAssets(runtimeRoot);
|
|
498
|
+
const daemonBin = resolvePreferredPath([
|
|
499
|
+
config.daemonBin,
|
|
500
|
+
process.env.OPENSYN_DAEMON_BIN,
|
|
501
|
+
path.join(managedRuntimeDir, "opensyn-daemon"),
|
|
502
|
+
packageRuntimePath("opensyn-daemon"),
|
|
503
|
+
repoFallbackPath("target", "debug", "synapse-daemon"),
|
|
504
|
+
repoFallbackPath("target", "release", "synapse-daemon"),
|
|
505
|
+
]) || path.join(managedRuntimeDir, "opensyn-daemon");
|
|
506
|
+
|
|
507
|
+
const cliBin = resolvePreferredPath([
|
|
508
|
+
config.cliBin,
|
|
509
|
+
process.env.OPENSYN_CLI_BIN,
|
|
510
|
+
path.join(managedRuntimeDir, "opensyn-cli"),
|
|
511
|
+
packageRuntimePath("opensyn-cli"),
|
|
512
|
+
repoFallbackPath("target", "debug", "synapse-cli"),
|
|
513
|
+
repoFallbackPath("target", "release", "synapse-cli"),
|
|
514
|
+
]) || path.join(managedRuntimeDir, "opensyn-cli");
|
|
515
|
+
|
|
516
|
+
const helperPath =
|
|
517
|
+
resolvePreferredPath([
|
|
518
|
+
config.helperPath,
|
|
519
|
+
path.join(managedRuntimeDir, "opensyn-ingest-command"),
|
|
520
|
+
packageRuntimePath("opensyn-ingest-command"),
|
|
521
|
+
repoFallbackPath("scripts", "synapse-ingest-command"),
|
|
522
|
+
]) || path.join(managedRuntimeDir, "opensyn-ingest-command");
|
|
523
|
+
|
|
524
|
+
const runnerPath =
|
|
525
|
+
resolvePreferredPath([
|
|
526
|
+
config.runnerPath,
|
|
527
|
+
path.join(managedRuntimeDir, "opensyn-run"),
|
|
528
|
+
packageRuntimePath("opensyn-run"),
|
|
529
|
+
repoFallbackPath("scripts", "synapse-run"),
|
|
530
|
+
]) || path.join(managedRuntimeDir, "opensyn-run");
|
|
531
|
+
|
|
532
|
+
return {
|
|
533
|
+
runtimeRoot,
|
|
534
|
+
dbPath: config.dbPath || path.join(runtimeRoot, "opensyn.db"),
|
|
535
|
+
cliBin,
|
|
536
|
+
daemonBin,
|
|
537
|
+
helperPath,
|
|
538
|
+
runnerPath,
|
|
539
|
+
defaultShell: config.defaultShell || defaultShell(),
|
|
540
|
+
defaultProjectRoot: config.defaultProjectRoot || defaults?.defaultProjectRoot,
|
|
541
|
+
defaultWindowSecs: config.defaultWindowSecs ?? 300,
|
|
542
|
+
defaultSearchLimit: config.defaultSearchLimit ?? 10,
|
|
543
|
+
daemonTimeoutMs: config.daemonTimeoutMs ?? 10_000,
|
|
544
|
+
projectionBundleRoot: config.projectionBundleRoot || defaultProjectionBundleRoot(),
|
|
545
|
+
projectionHost: config.projectionHost || "openclaw",
|
|
546
|
+
projectionAutoApplyApproved: config.projectionAutoApplyApproved ?? true,
|
|
547
|
+
watchIdleWindowSecs: config.watchIdleWindowSecs ?? 10,
|
|
548
|
+
watchPollIntervalSecs: config.watchPollIntervalSecs ?? 2,
|
|
549
|
+
openclawBin: config.openclawBin || defaultOpenClawBin(),
|
|
550
|
+
distillationWorkerEnabled: config.distillationWorkerEnabled ?? true,
|
|
551
|
+
distillationWorkerIntervalSecs: config.distillationWorkerIntervalSecs ?? 60,
|
|
552
|
+
distillationWorkerAgentId: config.distillationWorkerAgentId || "main",
|
|
553
|
+
};
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
function backgroundWatchDir(config: ResolvedOpenSynConfig, projectRoot: string): string {
|
|
557
|
+
const projectHash = createHash("sha256").update(projectRoot).digest("hex").slice(0, 16);
|
|
558
|
+
return path.join(config.runtimeRoot, "background", projectHash);
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
function backgroundWatchStatePath(config: ResolvedOpenSynConfig, projectRoot: string): string {
|
|
562
|
+
return path.join(backgroundWatchDir(config, projectRoot), "watch-linux.json");
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
function backgroundWatchLogPath(config: ResolvedOpenSynConfig, projectRoot: string): string {
|
|
566
|
+
return path.join(backgroundWatchDir(config, projectRoot), "watch-linux.log");
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
function distillationWorkerStatePath(
|
|
570
|
+
config: ResolvedOpenSynConfig,
|
|
571
|
+
projectRoot: string,
|
|
572
|
+
): string {
|
|
573
|
+
return path.join(backgroundWatchDir(config, projectRoot), "host-distiller.json");
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
function distillationWorkerLogPath(
|
|
577
|
+
config: ResolvedOpenSynConfig,
|
|
578
|
+
projectRoot: string,
|
|
579
|
+
): string {
|
|
580
|
+
return path.join(backgroundWatchDir(config, projectRoot), "host-distiller.log");
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
function isPidAlive(pid?: number): boolean {
|
|
584
|
+
if (!pid || pid <= 0) {
|
|
585
|
+
return false;
|
|
586
|
+
}
|
|
587
|
+
try {
|
|
588
|
+
process.kill(pid, 0);
|
|
589
|
+
return true;
|
|
590
|
+
} catch {
|
|
591
|
+
return false;
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
function getBackgroundWatchStatus(
|
|
596
|
+
config: ResolvedOpenSynConfig,
|
|
597
|
+
projectRoot: string,
|
|
598
|
+
): BackgroundWatchStatus {
|
|
599
|
+
const statePath = backgroundWatchStatePath(config, projectRoot);
|
|
600
|
+
const logPath = backgroundWatchLogPath(config, projectRoot);
|
|
601
|
+
if (process.platform !== "linux") {
|
|
602
|
+
return {
|
|
603
|
+
supported: false,
|
|
604
|
+
running: false,
|
|
605
|
+
source: "unsupported_platform",
|
|
606
|
+
project_root: projectRoot,
|
|
607
|
+
platform: process.platform,
|
|
608
|
+
state_path: statePath,
|
|
609
|
+
log_path: logPath,
|
|
610
|
+
reason: "background_watch_linux_only",
|
|
611
|
+
};
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
const state = readJsonFile<{ pid?: number }>(statePath);
|
|
615
|
+
const pid = state?.pid;
|
|
616
|
+
return {
|
|
617
|
+
supported: true,
|
|
618
|
+
running: isPidAlive(pid),
|
|
619
|
+
pid,
|
|
620
|
+
source: "watch_linux",
|
|
621
|
+
project_root: projectRoot,
|
|
622
|
+
platform: process.platform,
|
|
623
|
+
state_path: statePath,
|
|
624
|
+
log_path: logPath,
|
|
625
|
+
};
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
async function ensureBackgroundWatch(
|
|
629
|
+
config: ResolvedOpenSynConfig,
|
|
630
|
+
projectRoot: string,
|
|
631
|
+
): Promise<BackgroundWatchStatus> {
|
|
632
|
+
const status = getBackgroundWatchStatus(config, projectRoot);
|
|
633
|
+
if (!status.supported || status.running) {
|
|
634
|
+
return status;
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
const statePath = backgroundWatchStatePath(config, projectRoot);
|
|
638
|
+
const logPath = backgroundWatchLogPath(config, projectRoot);
|
|
639
|
+
mkdirSync(path.dirname(statePath), { recursive: true });
|
|
640
|
+
const logFd = openSync(logPath, "a");
|
|
641
|
+
const child = spawn(
|
|
642
|
+
config.daemonBin,
|
|
643
|
+
[
|
|
644
|
+
"watch-linux",
|
|
645
|
+
"--db",
|
|
646
|
+
config.dbPath,
|
|
647
|
+
"--project-root",
|
|
648
|
+
projectRoot,
|
|
649
|
+
"--idle-window-secs",
|
|
650
|
+
String(config.watchIdleWindowSecs),
|
|
651
|
+
"--poll-interval-secs",
|
|
652
|
+
String(config.watchPollIntervalSecs),
|
|
653
|
+
],
|
|
654
|
+
{
|
|
655
|
+
detached: true,
|
|
656
|
+
stdio: ["ignore", logFd, logFd],
|
|
657
|
+
},
|
|
658
|
+
);
|
|
659
|
+
child.unref();
|
|
660
|
+
closeSync(logFd);
|
|
661
|
+
|
|
662
|
+
writeFileSync(
|
|
663
|
+
statePath,
|
|
664
|
+
`${JSON.stringify(
|
|
665
|
+
{
|
|
666
|
+
pid: child.pid,
|
|
667
|
+
source: "watch_linux",
|
|
668
|
+
project_root: projectRoot,
|
|
669
|
+
started_at: new Date().toISOString(),
|
|
670
|
+
log_path: logPath,
|
|
671
|
+
},
|
|
672
|
+
null,
|
|
673
|
+
2,
|
|
674
|
+
)}\n`,
|
|
675
|
+
"utf8",
|
|
676
|
+
);
|
|
677
|
+
return getBackgroundWatchStatus(config, projectRoot);
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
function stopBackgroundWatch(
|
|
681
|
+
config: ResolvedOpenSynConfig,
|
|
682
|
+
projectRoot: string,
|
|
683
|
+
): BackgroundWatchStatus {
|
|
684
|
+
const status = getBackgroundWatchStatus(config, projectRoot);
|
|
685
|
+
if (status.running && status.pid) {
|
|
686
|
+
try {
|
|
687
|
+
process.kill(status.pid, "SIGTERM");
|
|
688
|
+
} catch {
|
|
689
|
+
// Ignore pid races; the state file is still cleaned up below.
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
rmSync(status.state_path, { force: true });
|
|
693
|
+
return getBackgroundWatchStatus(config, projectRoot);
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
function getDistillationWorkerStatus(
|
|
697
|
+
config: ResolvedOpenSynConfig,
|
|
698
|
+
projectRoot: string,
|
|
699
|
+
): DistillationWorkerStatus {
|
|
700
|
+
const statePath = distillationWorkerStatePath(config, projectRoot);
|
|
701
|
+
const logPath = distillationWorkerLogPath(config, projectRoot);
|
|
702
|
+
const state = readJsonFile<{
|
|
703
|
+
pid?: number;
|
|
704
|
+
last_exit_code?: number | null;
|
|
705
|
+
last_error?: string;
|
|
706
|
+
last_finished_at?: string;
|
|
707
|
+
}>(statePath);
|
|
708
|
+
const pid = state?.pid;
|
|
709
|
+
return {
|
|
710
|
+
supported: true,
|
|
711
|
+
enabled: config.distillationWorkerEnabled,
|
|
712
|
+
running: config.distillationWorkerEnabled && isPidAlive(pid),
|
|
713
|
+
pid,
|
|
714
|
+
source: "openclaw_host_model_agent",
|
|
715
|
+
project_root: projectRoot,
|
|
716
|
+
state_path: statePath,
|
|
717
|
+
log_path: logPath,
|
|
718
|
+
openclaw_bin: config.openclawBin,
|
|
719
|
+
agent_id: config.distillationWorkerAgentId,
|
|
720
|
+
interval_secs: config.distillationWorkerIntervalSecs,
|
|
721
|
+
last_exit_code: state?.last_exit_code,
|
|
722
|
+
last_error: state?.last_error,
|
|
723
|
+
last_finished_at: state?.last_finished_at,
|
|
724
|
+
reason: config.distillationWorkerEnabled ? undefined : "disabled_by_config",
|
|
725
|
+
};
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
function summarizeDistillationWorkerError(error: string): string {
|
|
729
|
+
const trimmed = error.trim();
|
|
730
|
+
if (!trimmed) {
|
|
731
|
+
return "OpenSyn has not recorded a recent distillation worker error.";
|
|
732
|
+
}
|
|
733
|
+
const firstLine = trimmed.split("\n").find(Boolean) || trimmed;
|
|
734
|
+
if (firstLine.length <= 220) {
|
|
735
|
+
return firstLine;
|
|
736
|
+
}
|
|
737
|
+
return `${firstLine.slice(0, 217)}...`;
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
function buildDistillationWorkerHealth(
|
|
741
|
+
status: DistillationWorkerStatus,
|
|
742
|
+
): DistillationWorkerHealth {
|
|
743
|
+
if (!status.enabled) {
|
|
744
|
+
return {
|
|
745
|
+
status: "disabled",
|
|
746
|
+
summary: "The plugin-managed host-model distillation worker is disabled by config.",
|
|
747
|
+
log_path: status.log_path,
|
|
748
|
+
last_finished_at: status.last_finished_at,
|
|
749
|
+
};
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
const lastError = status.last_error?.trim() || "";
|
|
753
|
+
if (lastError) {
|
|
754
|
+
if (lastError.includes("No API key found for provider")) {
|
|
755
|
+
return {
|
|
756
|
+
status: "auth_required",
|
|
757
|
+
summary:
|
|
758
|
+
"OpenClaw host-model distillation is blocked because the selected OpenClaw agent does not have model auth configured.",
|
|
759
|
+
action:
|
|
760
|
+
`Configure auth for agent '${status.agent_id}' in OpenClaw, then run openclaw repair_opensyn or wait for the next worker interval.`,
|
|
761
|
+
log_path: status.log_path,
|
|
762
|
+
last_finished_at: status.last_finished_at,
|
|
763
|
+
};
|
|
764
|
+
}
|
|
765
|
+
return {
|
|
766
|
+
status: "error",
|
|
767
|
+
summary: summarizeDistillationWorkerError(lastError),
|
|
768
|
+
action: `Inspect ${status.log_path} for the full worker log, then run openclaw repair_opensyn if needed.`,
|
|
769
|
+
log_path: status.log_path,
|
|
770
|
+
last_finished_at: status.last_finished_at,
|
|
771
|
+
};
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
if (status.running) {
|
|
775
|
+
return {
|
|
776
|
+
status: "running",
|
|
777
|
+
summary: "The plugin-managed host-model distillation worker is running.",
|
|
778
|
+
log_path: status.log_path,
|
|
779
|
+
last_finished_at: status.last_finished_at,
|
|
780
|
+
};
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
return {
|
|
784
|
+
status: "stopped",
|
|
785
|
+
summary: "The plugin-managed host-model distillation worker is not currently running.",
|
|
786
|
+
action: "Run openclaw repair_opensyn to restart the worker.",
|
|
787
|
+
log_path: status.log_path,
|
|
788
|
+
last_finished_at: status.last_finished_at,
|
|
789
|
+
};
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
function buildShellCaptureHealth(doctor: ShellHookDoctorLike | undefined): ShellCaptureHealth {
|
|
793
|
+
const installed = doctor?.installed ?? false;
|
|
794
|
+
const recommendations = doctor?.recommendations ?? [];
|
|
795
|
+
const staleRecommendation = recommendations.find(
|
|
796
|
+
(entry) => entry.issue === "stale_shell_environment",
|
|
797
|
+
);
|
|
798
|
+
const missingRecommendation = recommendations.find(
|
|
799
|
+
(entry) => entry.issue === "hook_not_installed",
|
|
800
|
+
);
|
|
801
|
+
|
|
802
|
+
if (!installed || missingRecommendation) {
|
|
803
|
+
return {
|
|
804
|
+
status: "missing",
|
|
805
|
+
summary: "Managed command capture is not installed for this shell yet.",
|
|
806
|
+
action:
|
|
807
|
+
missingRecommendation?.command ||
|
|
808
|
+
"Run openclaw enable_opensyn or openclaw repair_opensyn to install the managed shell hook.",
|
|
809
|
+
};
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
if (staleRecommendation) {
|
|
813
|
+
return {
|
|
814
|
+
status: "pending_reload",
|
|
815
|
+
summary:
|
|
816
|
+
"Managed command capture is installed and will work for new shells, but the current shell has not reloaded the hook yet.",
|
|
817
|
+
action:
|
|
818
|
+
staleRecommendation.command ||
|
|
819
|
+
(doctor?.rc_file ? `source '${doctor.rc_file}'` : "Open a new shell session."),
|
|
820
|
+
};
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
return {
|
|
824
|
+
status: "ready",
|
|
825
|
+
summary: "Managed command capture is installed and ready.",
|
|
826
|
+
};
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
function buildUserNextStep(params: {
|
|
830
|
+
collectionMode?: string;
|
|
831
|
+
backgroundWatch?: BackgroundWatchStatus;
|
|
832
|
+
shellCaptureHealth?: ShellCaptureHealth;
|
|
833
|
+
distillationWorkerHealth?: DistillationWorkerHealth;
|
|
834
|
+
}): UserNextStep {
|
|
835
|
+
const collectionMode = params.collectionMode || "unknown";
|
|
836
|
+
const backgroundWatch = params.backgroundWatch;
|
|
837
|
+
const shellCaptureHealth = params.shellCaptureHealth;
|
|
838
|
+
const distillationWorkerHealth = params.distillationWorkerHealth;
|
|
839
|
+
|
|
840
|
+
if (collectionMode !== "enabled") {
|
|
841
|
+
return {
|
|
842
|
+
status: "none",
|
|
843
|
+
code: "none",
|
|
844
|
+
summary: "No manual action required while OpenSyn autonomous collection is disabled.",
|
|
845
|
+
};
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
if (backgroundWatch && !backgroundWatch.supported) {
|
|
849
|
+
return {
|
|
850
|
+
status: "required",
|
|
851
|
+
code: "unsupported_platform",
|
|
852
|
+
summary:
|
|
853
|
+
"Plugin-managed background collection currently requires Linux on the local machine.",
|
|
854
|
+
action:
|
|
855
|
+
backgroundWatch.reason ||
|
|
856
|
+
"Use OpenSyn on a Linux machine, or disable plugin-managed background collection for this project.",
|
|
857
|
+
};
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
if (distillationWorkerHealth?.status === "auth_required") {
|
|
861
|
+
return {
|
|
862
|
+
status: "required",
|
|
863
|
+
code: "configure_model_auth",
|
|
864
|
+
summary:
|
|
865
|
+
"Configure OpenClaw model auth for the managed distillation agent before OpenSyn can keep distilling in the background.",
|
|
866
|
+
action: distillationWorkerHealth.action,
|
|
867
|
+
};
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
if (backgroundWatch?.supported && !backgroundWatch.running) {
|
|
871
|
+
return {
|
|
872
|
+
status: "required",
|
|
873
|
+
code: "restart_background_watch",
|
|
874
|
+
summary:
|
|
875
|
+
"Background collection is not currently running for this workspace, so new activity is not being captured.",
|
|
876
|
+
action: "Run openclaw repair_opensyn to restart background collection.",
|
|
877
|
+
};
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
if (shellCaptureHealth?.status === "missing" || shellCaptureHealth?.status === "error") {
|
|
881
|
+
return {
|
|
882
|
+
status: "required",
|
|
883
|
+
code: "repair_command_capture",
|
|
884
|
+
summary:
|
|
885
|
+
"Managed command capture is not active yet, so shell commands from this workstation are not fully captured.",
|
|
886
|
+
action:
|
|
887
|
+
shellCaptureHealth.action ||
|
|
888
|
+
"Run openclaw repair_opensyn to reinstall the managed shell hook.",
|
|
889
|
+
};
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
if (
|
|
893
|
+
distillationWorkerHealth &&
|
|
894
|
+
(distillationWorkerHealth.status === "error" ||
|
|
895
|
+
distillationWorkerHealth.status === "stopped")
|
|
896
|
+
) {
|
|
897
|
+
return {
|
|
898
|
+
status: "required",
|
|
899
|
+
code: "repair_distillation_worker",
|
|
900
|
+
summary:
|
|
901
|
+
"The plugin-managed host-model distillation worker is not healthy, so OpenSyn cannot keep refining new activity automatically.",
|
|
902
|
+
action:
|
|
903
|
+
distillationWorkerHealth.action ||
|
|
904
|
+
"Run openclaw repair_opensyn to restart the distillation worker.",
|
|
905
|
+
};
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
if (shellCaptureHealth?.status === "pending_reload") {
|
|
909
|
+
return {
|
|
910
|
+
status: "optional",
|
|
911
|
+
code: "reload_shell",
|
|
912
|
+
summary:
|
|
913
|
+
"OpenSyn is already installed, but the current shell has not reloaded managed command capture yet.",
|
|
914
|
+
action: shellCaptureHealth.action,
|
|
915
|
+
};
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
return {
|
|
919
|
+
status: "none",
|
|
920
|
+
code: "none",
|
|
921
|
+
summary: "No manual action required. OpenSyn autonomous collection is ready.",
|
|
922
|
+
};
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
async function ensureDistillationWorker(
|
|
926
|
+
config: ResolvedOpenSynConfig,
|
|
927
|
+
projectRoot: string,
|
|
928
|
+
): Promise<DistillationWorkerStatus> {
|
|
929
|
+
const status = getDistillationWorkerStatus(config, projectRoot);
|
|
930
|
+
if (!status.enabled || status.running) {
|
|
931
|
+
return status;
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
const statePath = distillationWorkerStatePath(config, projectRoot);
|
|
935
|
+
const logPath = distillationWorkerLogPath(config, projectRoot);
|
|
936
|
+
mkdirSync(path.dirname(statePath), { recursive: true });
|
|
937
|
+
const logFd = openSync(logPath, "a");
|
|
938
|
+
const child = spawn(
|
|
939
|
+
stableNodeRunner(),
|
|
940
|
+
[
|
|
941
|
+
path.join(path.dirname(config.daemonBin), "opensyn-host-distiller.mjs"),
|
|
942
|
+
"--openclaw-bin",
|
|
943
|
+
config.openclawBin,
|
|
944
|
+
"--agent-id",
|
|
945
|
+
config.distillationWorkerAgentId,
|
|
946
|
+
"--interval-secs",
|
|
947
|
+
String(config.distillationWorkerIntervalSecs),
|
|
948
|
+
"--project-root",
|
|
949
|
+
projectRoot,
|
|
950
|
+
"--state-path",
|
|
951
|
+
statePath,
|
|
952
|
+
],
|
|
953
|
+
{
|
|
954
|
+
detached: true,
|
|
955
|
+
stdio: ["ignore", logFd, logFd],
|
|
956
|
+
env: process.env,
|
|
957
|
+
},
|
|
958
|
+
);
|
|
959
|
+
child.unref();
|
|
960
|
+
closeSync(logFd);
|
|
961
|
+
|
|
962
|
+
writeFileSync(
|
|
963
|
+
statePath,
|
|
964
|
+
`${JSON.stringify(
|
|
965
|
+
{
|
|
966
|
+
pid: child.pid,
|
|
967
|
+
source: "openclaw_host_model_agent",
|
|
968
|
+
project_root: projectRoot,
|
|
969
|
+
openclaw_bin: config.openclawBin,
|
|
970
|
+
agent_id: config.distillationWorkerAgentId,
|
|
971
|
+
interval_secs: config.distillationWorkerIntervalSecs,
|
|
972
|
+
started_at: new Date().toISOString(),
|
|
973
|
+
log_path: logPath,
|
|
974
|
+
last_exit_code: null,
|
|
975
|
+
last_error: "",
|
|
976
|
+
},
|
|
977
|
+
null,
|
|
978
|
+
2,
|
|
979
|
+
)}\n`,
|
|
980
|
+
"utf8",
|
|
981
|
+
);
|
|
982
|
+
return getDistillationWorkerStatus(config, projectRoot);
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
function stopDistillationWorker(
|
|
986
|
+
config: ResolvedOpenSynConfig,
|
|
987
|
+
projectRoot: string,
|
|
988
|
+
): DistillationWorkerStatus {
|
|
989
|
+
const status = getDistillationWorkerStatus(config, projectRoot);
|
|
990
|
+
if (status.running && status.pid) {
|
|
991
|
+
try {
|
|
992
|
+
process.kill(status.pid, "SIGTERM");
|
|
993
|
+
} catch {
|
|
994
|
+
// Ignore pid races; the state file is still cleaned up below.
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
rmSync(status.state_path, { force: true });
|
|
998
|
+
return getDistillationWorkerStatus(config, projectRoot);
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
async function runDaemonCommand(
|
|
1002
|
+
config: ResolvedOpenSynConfig,
|
|
1003
|
+
args: string[],
|
|
1004
|
+
): Promise<unknown> {
|
|
1005
|
+
return new Promise((resolve, reject) => {
|
|
1006
|
+
const timeoutMs = config.daemonTimeoutMs ?? 10_000;
|
|
1007
|
+
const child = spawn(config.daemonBin, args, {
|
|
1008
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
1009
|
+
});
|
|
1010
|
+
|
|
1011
|
+
let stdout = "";
|
|
1012
|
+
let stderr = "";
|
|
1013
|
+
let settled = false;
|
|
1014
|
+
const timeout = setTimeout(() => {
|
|
1015
|
+
if (settled) {
|
|
1016
|
+
return;
|
|
1017
|
+
}
|
|
1018
|
+
settled = true;
|
|
1019
|
+
child.kill();
|
|
1020
|
+
reject(new Error(`synapse-daemon timed out after ${timeoutMs}ms`));
|
|
1021
|
+
}, timeoutMs);
|
|
1022
|
+
|
|
1023
|
+
function finish(fn: () => void) {
|
|
1024
|
+
if (settled) {
|
|
1025
|
+
return;
|
|
1026
|
+
}
|
|
1027
|
+
settled = true;
|
|
1028
|
+
clearTimeout(timeout);
|
|
1029
|
+
fn();
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
child.stdout.on("data", (chunk) => {
|
|
1033
|
+
stdout += chunk.toString();
|
|
1034
|
+
});
|
|
1035
|
+
child.stderr.on("data", (chunk) => {
|
|
1036
|
+
stderr += chunk.toString();
|
|
1037
|
+
});
|
|
1038
|
+
child.on("error", (error) => {
|
|
1039
|
+
finish(() => reject(error));
|
|
1040
|
+
});
|
|
1041
|
+
child.on("close", (code) => {
|
|
1042
|
+
finish(() => {
|
|
1043
|
+
if (code !== 0) {
|
|
1044
|
+
reject(
|
|
1045
|
+
new Error(
|
|
1046
|
+
`synapse-daemon exited with ${code ?? "unknown"}${stderr ? `: ${stderr.trim()}` : ""}`,
|
|
1047
|
+
),
|
|
1048
|
+
);
|
|
1049
|
+
return;
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
const trimmed = stdout.trim();
|
|
1053
|
+
if (!trimmed) {
|
|
1054
|
+
resolve({
|
|
1055
|
+
stdout,
|
|
1056
|
+
stderr,
|
|
1057
|
+
exitCode: code,
|
|
1058
|
+
} satisfies SpawnResponse);
|
|
1059
|
+
return;
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
try {
|
|
1063
|
+
resolve(JSON.parse(trimmed));
|
|
1064
|
+
} catch {
|
|
1065
|
+
resolve({
|
|
1066
|
+
stdout,
|
|
1067
|
+
stderr,
|
|
1068
|
+
exitCode: code,
|
|
1069
|
+
} satisfies SpawnResponse);
|
|
1070
|
+
}
|
|
1071
|
+
});
|
|
1072
|
+
});
|
|
1073
|
+
});
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
async function callDaemon(
|
|
1077
|
+
config: ResolvedOpenSynConfig,
|
|
1078
|
+
toolName: string,
|
|
1079
|
+
argumentsValue: Record<string, unknown>,
|
|
1080
|
+
): Promise<unknown> {
|
|
1081
|
+
const request = JSON.stringify({
|
|
1082
|
+
jsonrpc: "2.0",
|
|
1083
|
+
id: Date.now(),
|
|
1084
|
+
method: "tools/call",
|
|
1085
|
+
params: {
|
|
1086
|
+
name: toolName,
|
|
1087
|
+
arguments: argumentsValue,
|
|
1088
|
+
},
|
|
1089
|
+
});
|
|
1090
|
+
|
|
1091
|
+
return new Promise((resolve, reject) => {
|
|
1092
|
+
const timeoutMs = config.daemonTimeoutMs ?? 10_000;
|
|
1093
|
+
const child = spawn(
|
|
1094
|
+
config.daemonBin,
|
|
1095
|
+
["mcp-stdio", "--db", config.dbPath],
|
|
1096
|
+
{
|
|
1097
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
1098
|
+
},
|
|
1099
|
+
);
|
|
1100
|
+
|
|
1101
|
+
let stdout = "";
|
|
1102
|
+
let stderr = "";
|
|
1103
|
+
let settled = false;
|
|
1104
|
+
const timeout = setTimeout(() => {
|
|
1105
|
+
if (settled) {
|
|
1106
|
+
return;
|
|
1107
|
+
}
|
|
1108
|
+
settled = true;
|
|
1109
|
+
child.kill();
|
|
1110
|
+
reject(new Error(`synapse-daemon timed out after ${timeoutMs}ms`));
|
|
1111
|
+
}, timeoutMs);
|
|
1112
|
+
|
|
1113
|
+
function finish(fn: () => void) {
|
|
1114
|
+
if (settled) {
|
|
1115
|
+
return;
|
|
1116
|
+
}
|
|
1117
|
+
settled = true;
|
|
1118
|
+
clearTimeout(timeout);
|
|
1119
|
+
fn();
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
child.stdout.on("data", (chunk) => {
|
|
1123
|
+
stdout += chunk.toString();
|
|
1124
|
+
});
|
|
1125
|
+
child.stderr.on("data", (chunk) => {
|
|
1126
|
+
stderr += chunk.toString();
|
|
1127
|
+
});
|
|
1128
|
+
child.on("error", (error) => {
|
|
1129
|
+
finish(() => reject(error));
|
|
1130
|
+
});
|
|
1131
|
+
child.on("close", (code) => {
|
|
1132
|
+
finish(() => {
|
|
1133
|
+
const responseLine = stdout
|
|
1134
|
+
.split(/\r?\n/)
|
|
1135
|
+
.map((line) => line.trim())
|
|
1136
|
+
.filter(Boolean)
|
|
1137
|
+
.at(-1);
|
|
1138
|
+
|
|
1139
|
+
if (code !== 0 && !responseLine) {
|
|
1140
|
+
reject(
|
|
1141
|
+
new Error(
|
|
1142
|
+
`synapse-daemon exited with ${code ?? "unknown"}${stderr ? `: ${stderr.trim()}` : ""}`,
|
|
1143
|
+
),
|
|
1144
|
+
);
|
|
1145
|
+
return;
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
if (!responseLine) {
|
|
1149
|
+
reject(new Error("synapse-daemon returned no response"));
|
|
1150
|
+
return;
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
try {
|
|
1154
|
+
const response = JSON.parse(responseLine) as RpcResponse;
|
|
1155
|
+
if (response.error?.message) {
|
|
1156
|
+
reject(new Error(response.error.message));
|
|
1157
|
+
return;
|
|
1158
|
+
}
|
|
1159
|
+
resolve(response.result ?? null);
|
|
1160
|
+
} catch (error) {
|
|
1161
|
+
reject(
|
|
1162
|
+
new Error(
|
|
1163
|
+
`failed to parse synapse-daemon response: ${error instanceof Error ? error.message : String(error)}`,
|
|
1164
|
+
),
|
|
1165
|
+
);
|
|
1166
|
+
}
|
|
1167
|
+
});
|
|
1168
|
+
});
|
|
1169
|
+
|
|
1170
|
+
child.stdin.write(`${request}\n`);
|
|
1171
|
+
child.stdin.end();
|
|
1172
|
+
});
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
async function runCliCommand(
|
|
1176
|
+
config: ResolvedOpenSynConfig,
|
|
1177
|
+
args: string[],
|
|
1178
|
+
): Promise<unknown> {
|
|
1179
|
+
return new Promise((resolve, reject) => {
|
|
1180
|
+
const timeoutMs = config.daemonTimeoutMs;
|
|
1181
|
+
const child = spawn(config.cliBin, args, {
|
|
1182
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
1183
|
+
});
|
|
1184
|
+
|
|
1185
|
+
let stdout = "";
|
|
1186
|
+
let stderr = "";
|
|
1187
|
+
let settled = false;
|
|
1188
|
+
const timeout = setTimeout(() => {
|
|
1189
|
+
if (settled) {
|
|
1190
|
+
return;
|
|
1191
|
+
}
|
|
1192
|
+
settled = true;
|
|
1193
|
+
child.kill();
|
|
1194
|
+
reject(new Error(`synapse-cli timed out after ${timeoutMs}ms`));
|
|
1195
|
+
}, timeoutMs);
|
|
1196
|
+
|
|
1197
|
+
function finish(fn: () => void) {
|
|
1198
|
+
if (settled) {
|
|
1199
|
+
return;
|
|
1200
|
+
}
|
|
1201
|
+
settled = true;
|
|
1202
|
+
clearTimeout(timeout);
|
|
1203
|
+
fn();
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
child.stdout.on("data", (chunk) => {
|
|
1207
|
+
stdout += chunk.toString();
|
|
1208
|
+
});
|
|
1209
|
+
child.stderr.on("data", (chunk) => {
|
|
1210
|
+
stderr += chunk.toString();
|
|
1211
|
+
});
|
|
1212
|
+
child.on("error", (error) => {
|
|
1213
|
+
finish(() => reject(error));
|
|
1214
|
+
});
|
|
1215
|
+
child.on("close", (code) => {
|
|
1216
|
+
finish(() => {
|
|
1217
|
+
if (code !== 0) {
|
|
1218
|
+
reject(
|
|
1219
|
+
new Error(
|
|
1220
|
+
`synapse-cli exited with ${code ?? "unknown"}${stderr ? `: ${stderr.trim()}` : ""}`,
|
|
1221
|
+
),
|
|
1222
|
+
);
|
|
1223
|
+
return;
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
const trimmed = stdout.trim();
|
|
1227
|
+
if (!trimmed) {
|
|
1228
|
+
resolve({ stdout, stderr, exitCode: code } satisfies SpawnResponse);
|
|
1229
|
+
return;
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1232
|
+
try {
|
|
1233
|
+
resolve(JSON.parse(trimmed));
|
|
1234
|
+
} catch {
|
|
1235
|
+
resolve({ stdout, stderr, exitCode: code } satisfies SpawnResponse);
|
|
1236
|
+
}
|
|
1237
|
+
});
|
|
1238
|
+
});
|
|
1239
|
+
});
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
function asText(result: unknown): { content: ToolContent[] } {
|
|
1243
|
+
const summary = summarizeResult(result);
|
|
1244
|
+
const rendered = summary
|
|
1245
|
+
? `${summary}\n\nRaw JSON:\n${JSON.stringify(result, null, 2)}`
|
|
1246
|
+
: JSON.stringify(result, null, 2);
|
|
1247
|
+
return {
|
|
1248
|
+
content: [
|
|
1249
|
+
{
|
|
1250
|
+
type: "text",
|
|
1251
|
+
text: rendered,
|
|
1252
|
+
},
|
|
1253
|
+
],
|
|
1254
|
+
};
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
function summarizeResult(result: unknown): string {
|
|
1258
|
+
if (result === null) {
|
|
1259
|
+
return "No OpenSyn context was found for this request.";
|
|
1260
|
+
}
|
|
1261
|
+
|
|
1262
|
+
if (Array.isArray(result)) {
|
|
1263
|
+
if (result.length === 0) {
|
|
1264
|
+
return "OpenSyn returned an empty result set.";
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
if (looksLikeSearchHit(result[0])) {
|
|
1268
|
+
return summarizeSearchHits(result as SearchLike[]);
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
if (looksLikeAgentAsset(result[0])) {
|
|
1272
|
+
return summarizeAgentAssets(result as AssetLike[]);
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1275
|
+
if (looksLikeAssetHistoryEntry(result[0])) {
|
|
1276
|
+
return summarizeAssetHistory(result as AssetHistoryLike[]);
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
if (looksLikeDistillationJob(result[0])) {
|
|
1280
|
+
return summarizeDistillationJobs(result as DistillationJobLike[]);
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
if (looksLikeProjectionRecord(result[0])) {
|
|
1284
|
+
return summarizeProjectionRecords(result as ProjectionLike[]);
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
if (looksLikeContextObject(result[0])) {
|
|
1288
|
+
return summarizeEpisodes(result as ContextLike[]);
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
if (looksLikeContextObject(result)) {
|
|
1293
|
+
return summarizeContextObject(result as ContextLike);
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
if (looksLikeAutonomousCollectionBootstrap(result)) {
|
|
1297
|
+
return summarizeAutonomousCollectionBootstrap(
|
|
1298
|
+
result as {
|
|
1299
|
+
project_root?: string;
|
|
1300
|
+
shell?: string;
|
|
1301
|
+
semantic_hint?: SemanticHint;
|
|
1302
|
+
shell_hook_fix?: unknown;
|
|
1303
|
+
shell_hook_uninstall?: unknown;
|
|
1304
|
+
background_watch?: BackgroundWatchStatus;
|
|
1305
|
+
distillation_worker?: DistillationWorkerStatus;
|
|
1306
|
+
distillation_worker_health?: DistillationWorkerHealth;
|
|
1307
|
+
collection_settings?: CollectionSettingsLike;
|
|
1308
|
+
review_policy?: ReviewPolicySettingsLike;
|
|
1309
|
+
projection_settings?: ProjectionSettingsLike | null;
|
|
1310
|
+
status?: {
|
|
1311
|
+
collection_settings?: CollectionSettingsLike | null;
|
|
1312
|
+
projection_settings?: ProjectionSettingsLike | null;
|
|
1313
|
+
};
|
|
1314
|
+
},
|
|
1315
|
+
);
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
if (looksLikeAutonomousCollectionStatus(result)) {
|
|
1319
|
+
return summarizeAutonomousCollectionStatus(
|
|
1320
|
+
result as {
|
|
1321
|
+
project_root?: string;
|
|
1322
|
+
shell?: string;
|
|
1323
|
+
shell_hook_doctor?: { installed?: boolean; self_test?: { success?: boolean } };
|
|
1324
|
+
shell_capture_health?: ShellCaptureHealth;
|
|
1325
|
+
background_watch?: BackgroundWatchStatus;
|
|
1326
|
+
distillation_worker?: DistillationWorkerStatus;
|
|
1327
|
+
distillation_worker_health?: DistillationWorkerHealth;
|
|
1328
|
+
collection_settings?: CollectionSettingsLike | null;
|
|
1329
|
+
review_policy?: ReviewPolicySettingsLike | null;
|
|
1330
|
+
projection_settings?: ProjectionSettingsLike | null;
|
|
1331
|
+
},
|
|
1332
|
+
);
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1335
|
+
if (looksLikeCollectionSettings(result)) {
|
|
1336
|
+
return summarizeCollectionSettings(result as CollectionSettingsLike);
|
|
1337
|
+
}
|
|
1338
|
+
|
|
1339
|
+
if (looksLikeReviewPolicySettings(result)) {
|
|
1340
|
+
return summarizeReviewPolicySettings(result as ReviewPolicySettingsLike);
|
|
1341
|
+
}
|
|
1342
|
+
|
|
1343
|
+
if (looksLikeDistillationSettings(result)) {
|
|
1344
|
+
return summarizeDistillationSettings(result as DistillationSettingsLike);
|
|
1345
|
+
}
|
|
1346
|
+
|
|
1347
|
+
if (looksLikeDistillationWorkPacket(result)) {
|
|
1348
|
+
return summarizeDistillationWorkPacket(result as DistillationWorkPacketLike);
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
if (looksLikeAutonomousTick(result)) {
|
|
1352
|
+
return summarizeAutonomousTick(result as AutonomousTickLike);
|
|
1353
|
+
}
|
|
1354
|
+
|
|
1355
|
+
if (looksLikeProjectionSettings(result)) {
|
|
1356
|
+
return summarizeProjectionSettings(result as ProjectionSettingsLike);
|
|
1357
|
+
}
|
|
1358
|
+
|
|
1359
|
+
if (looksLikeProjectionSync(result)) {
|
|
1360
|
+
return summarizeProjectionSync(result as ProjectionSyncLike);
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1363
|
+
if (looksLikeRuntimeOverlay(result)) {
|
|
1364
|
+
return summarizeRuntimeOverlay(result as RuntimeOverlayLike);
|
|
1365
|
+
}
|
|
1366
|
+
|
|
1367
|
+
if (looksLikeAssetDiff(result)) {
|
|
1368
|
+
return summarizeAssetDiff(result as AssetDiffLike);
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1371
|
+
if (looksLikeAssetApprovalResult(result)) {
|
|
1372
|
+
return summarizeAssetApprovalResult(
|
|
1373
|
+
result as { asset?: AssetLike; projection_sync?: ProjectionSyncLike },
|
|
1374
|
+
);
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1377
|
+
return "";
|
|
1378
|
+
}
|
|
1379
|
+
|
|
1380
|
+
function summarizeContextObject(result: ContextLike): string {
|
|
1381
|
+
const hint = result.semantic_hint;
|
|
1382
|
+
const label = result.title || result.summary || hint?.summary_label || "OpenSyn context";
|
|
1383
|
+
const parts = [label];
|
|
1384
|
+
|
|
1385
|
+
if (hint?.activity_kind) {
|
|
1386
|
+
parts.push(`activity=${hint.activity_kind}`);
|
|
1387
|
+
}
|
|
1388
|
+
|
|
1389
|
+
if (hint?.operator_focus) {
|
|
1390
|
+
parts.push(`focus=${hint.operator_focus}`);
|
|
1391
|
+
} else if (result.goal_guess) {
|
|
1392
|
+
parts.push(`focus=${result.goal_guess}`);
|
|
1393
|
+
}
|
|
1394
|
+
|
|
1395
|
+
if (hint?.primary_signature) {
|
|
1396
|
+
parts.push(`signature=${hint.primary_signature}`);
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1399
|
+
if (hint?.primary_artifact) {
|
|
1400
|
+
parts.push(`artifact=${hint.primary_artifact}`);
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1403
|
+
if (result.recent_failures?.length) {
|
|
1404
|
+
parts.push(`recent_failure=${result.recent_failures[0]}`);
|
|
1405
|
+
}
|
|
1406
|
+
|
|
1407
|
+
if (result.recent_changes?.length) {
|
|
1408
|
+
parts.push(`changed_files=${result.recent_changes.length}`);
|
|
1409
|
+
} else if (result.files_touched?.length) {
|
|
1410
|
+
parts.push(`changed_files=${result.files_touched.length}`);
|
|
1411
|
+
}
|
|
1412
|
+
|
|
1413
|
+
if (result.commands?.length) {
|
|
1414
|
+
parts.push(`last_command=${result.commands[0]}`);
|
|
1415
|
+
}
|
|
1416
|
+
|
|
1417
|
+
return parts.join(" | ");
|
|
1418
|
+
}
|
|
1419
|
+
|
|
1420
|
+
function summarizeEpisodes(results: ContextLike[]): string {
|
|
1421
|
+
const first = results[0];
|
|
1422
|
+
const kind = first.semantic_hint?.activity_kind || "development_activity";
|
|
1423
|
+
return `OpenSyn returned ${results.length} recent episode(s); latest=${first.title || "unknown"} | activity=${kind} | focus=${first.goal_guess || first.semantic_hint?.operator_focus || "review recent activity"}`;
|
|
1424
|
+
}
|
|
1425
|
+
|
|
1426
|
+
function summarizeSearchHits(results: SearchLike[]): string {
|
|
1427
|
+
const first = results[0];
|
|
1428
|
+
const kind = first.semantic_hint?.activity_kind || "development_activity";
|
|
1429
|
+
const matched = first.semantic_hint?.matched_item_kind || first.item_kind || "unknown";
|
|
1430
|
+
return `OpenSyn returned ${results.length} search hit(s); top_match=${first.title || "unknown"} | activity=${kind} | item=${matched}`;
|
|
1431
|
+
}
|
|
1432
|
+
|
|
1433
|
+
function summarizeAgentAssets(results: AssetLike[]): string {
|
|
1434
|
+
const first = results[0];
|
|
1435
|
+
const kind = first.semantic_hint?.asset_kind || first.kind || "agent_asset";
|
|
1436
|
+
const status = first.status || first.semantic_hint?.status_label || "unknown";
|
|
1437
|
+
const revision = first.revision ?? first.semantic_hint?.revision ?? 1;
|
|
1438
|
+
return `OpenSyn returned ${results.length} agent asset(s); top_asset=${first.title || "unknown"} | kind=${kind} | status=${status} | revision=${String(revision)}`;
|
|
1439
|
+
}
|
|
1440
|
+
|
|
1441
|
+
function summarizeAssetHistory(results: AssetHistoryLike[]): string {
|
|
1442
|
+
const first = results[0];
|
|
1443
|
+
const kind = first.asset?.kind || first.asset?.semantic_hint?.asset_kind || "agent_asset";
|
|
1444
|
+
const revision = first.revision ?? first.semantic_hint?.revision ?? 1;
|
|
1445
|
+
return `OpenSyn returned ${results.length} asset history entr${results.length === 1 ? "y" : "ies"}; asset=${first.asset_id || "unknown"} | kind=${kind} | revision=${String(revision)} | change=${first.change_reason || "unknown"}`;
|
|
1446
|
+
}
|
|
1447
|
+
|
|
1448
|
+
function summarizeAssetDiff(result: AssetDiffLike): string {
|
|
1449
|
+
const changes = [
|
|
1450
|
+
result.title_changed ? "title" : "",
|
|
1451
|
+
result.summary_changed ? "summary" : "",
|
|
1452
|
+
result.content_changed ? "content" : "",
|
|
1453
|
+
(result.added_tags?.length || result.removed_tags?.length) ? "tags" : "",
|
|
1454
|
+
].filter(Boolean);
|
|
1455
|
+
return `OpenSyn compared asset=${result.asset_id || "unknown"} | revisions=${String(result.from_revision ?? 0)}->${String(result.to_revision ?? 0)} | changed=${changes.join(",") || "none"}`;
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
function summarizeCollectionSettings(result: CollectionSettingsLike): string {
|
|
1459
|
+
return `OpenSyn collection mode=${result.mode || "unknown"} | auto_distill=${String(result.auto_distill ?? false)} | distill_on_flush=${String(result.distill_on_flush ?? false)}`;
|
|
1460
|
+
}
|
|
1461
|
+
|
|
1462
|
+
function summarizeReviewPolicySettings(result: ReviewPolicySettingsLike): string {
|
|
1463
|
+
return `OpenSyn review policy | memory=${String(result.auto_approve_memory ?? false)} | skill=${String(result.auto_approve_skill ?? false)} | rule=${String(result.auto_approve_rule ?? false)} | negative_example=${String(result.auto_approve_negative_example ?? false)} | min_confidence=${String(result.min_confidence ?? 0)} | max_revision=${String(result.max_auto_approve_revision ?? "unbounded")} | require_reapproval=${String(result.require_reapproval_on_revision_bump ?? false)}`;
|
|
1464
|
+
}
|
|
1465
|
+
|
|
1466
|
+
function summarizeDistillationSettings(result: DistillationSettingsLike): string {
|
|
1467
|
+
return `OpenSyn distillation backend=${result.backend || "unknown"} | host_only=${String(result.restricted_to_host_model ?? false)} | allow_network=${String(result.allow_network ?? false)} | auto_enqueue=${String(result.auto_enqueue_on_flush ?? false)} | auto_apply=${String(result.auto_apply_submissions ?? false)}`;
|
|
1468
|
+
}
|
|
1469
|
+
|
|
1470
|
+
function summarizeDistillationJobs(results: DistillationJobLike[]): string {
|
|
1471
|
+
const first = results[0];
|
|
1472
|
+
return `OpenSyn returned ${results.length} distillation job(s); first_status=${first.status || "unknown"} | first_job=${first.id || "unknown"}`;
|
|
1473
|
+
}
|
|
1474
|
+
|
|
1475
|
+
function summarizeDistillationWorkPacket(result: DistillationWorkPacketLike): string {
|
|
1476
|
+
const job = result.job;
|
|
1477
|
+
return `OpenSyn distillation work packet | job=${job?.id || "unknown"} | status=${job?.status || "unknown"} | kinds=${String(result.output_spec?.allowed_asset_kinds?.length ?? 0)} | completion_tool=${result.output_spec?.completion_tool || "unknown"} | arg_key=${result.output_spec?.completion_argument_key || "unknown"} | overlay_assets=${String(result.runtime_overlay?.approved_asset_count ?? 0)} | has_executor_prompt=${String(Boolean(result.executor_prompt))}`;
|
|
1478
|
+
}
|
|
1479
|
+
|
|
1480
|
+
function summarizeAutonomousTick(result: AutonomousTickLike): string {
|
|
1481
|
+
return `OpenSyn autonomous tick | contract=${result.contract_version || "unknown"} | state=${result.automation_state || "unknown"} | transition=${result.transition?.kind || "unknown"} | cycle_primary=${String(result.cycle_plan?.primary_steps?.length ?? 0)} | cycle_fallback=${String(result.cycle_plan?.fallback_steps?.length ?? 0)} | cycle_resume=${String(result.cycle_plan?.resume_steps?.length ?? 0)} | plan_steps=${String(result.execution_plan?.length ?? 0)} | success_tool=${result.transition?.success_action_tool || result.next_action_tool || "none"} | failure_tool=${result.transition?.failure_action_tool || "none"} | resume_tool=${result.transition?.resume_action_tool || "none"} | resume_after_secs=${String(result.transition?.resume_after_secs ?? result.poll_after_secs ?? 0)} | should_continue=${String(result.should_continue ?? false)} | poll_after_secs=${String(result.poll_after_secs ?? 0)} | pending_jobs=${String(result.pending_distillation_jobs ?? 0)} | claimed_job=${result.claimed_job_id || "none"} | overlay_assets=${String(result.runtime_overlay?.approved_asset_count ?? 0)}`;
|
|
1482
|
+
}
|
|
1483
|
+
|
|
1484
|
+
function summarizeAutonomousCollectionBootstrap(result: {
|
|
1485
|
+
project_root?: string;
|
|
1486
|
+
shell?: string;
|
|
1487
|
+
semantic_hint?: SemanticHint;
|
|
1488
|
+
shell_hook_fix?: unknown;
|
|
1489
|
+
shell_hook_uninstall?: unknown;
|
|
1490
|
+
background_watch?: BackgroundWatchStatus;
|
|
1491
|
+
distillation_worker?: DistillationWorkerStatus;
|
|
1492
|
+
distillation_worker_health?: DistillationWorkerHealth;
|
|
1493
|
+
collection_settings?: CollectionSettingsLike;
|
|
1494
|
+
review_policy?: ReviewPolicySettingsLike;
|
|
1495
|
+
projection_settings?: ProjectionSettingsLike | null;
|
|
1496
|
+
status?: {
|
|
1497
|
+
shell_capture_health?: ShellCaptureHealth;
|
|
1498
|
+
background_watch?: BackgroundWatchStatus;
|
|
1499
|
+
distillation_worker_health?: DistillationWorkerHealth;
|
|
1500
|
+
collection_settings?: CollectionSettingsLike | null;
|
|
1501
|
+
projection_settings?: ProjectionSettingsLike | null;
|
|
1502
|
+
};
|
|
1503
|
+
next_step?: UserNextStep;
|
|
1504
|
+
}): string {
|
|
1505
|
+
const statusLabel = result.semantic_hint?.status_label || "updated";
|
|
1506
|
+
const effectiveProjectionSettings = result.projection_settings || result.status?.projection_settings;
|
|
1507
|
+
const projectionEnabled =
|
|
1508
|
+
effectiveProjectionSettings && typeof effectiveProjectionSettings === "object"
|
|
1509
|
+
? String(effectiveProjectionSettings.enabled ?? false)
|
|
1510
|
+
: "false";
|
|
1511
|
+
const collectionMode =
|
|
1512
|
+
result.collection_settings?.mode ||
|
|
1513
|
+
result.status?.collection_settings?.mode ||
|
|
1514
|
+
"unknown";
|
|
1515
|
+
const distillerHealth =
|
|
1516
|
+
result.distillation_worker_health?.status ||
|
|
1517
|
+
result.status?.distillation_worker_health?.status ||
|
|
1518
|
+
(result.distillation_worker?.last_error
|
|
1519
|
+
? "error"
|
|
1520
|
+
: result.distillation_worker?.running
|
|
1521
|
+
? "running"
|
|
1522
|
+
: "stopped");
|
|
1523
|
+
const watcherRunning =
|
|
1524
|
+
result.background_watch?.running ?? result.status?.background_watch?.running ?? false;
|
|
1525
|
+
const nextStep = result.next_step?.code || "none";
|
|
1526
|
+
return `OpenSyn autonomous collection ${statusLabel} | project=${result.project_root || "unknown"} | shell=${result.shell || "unknown"} | watcher=${String(watcherRunning)} | distiller=${distillerHealth} | collection_mode=${collectionMode} | projection=${projectionEnabled} | next=${nextStep}`;
|
|
1527
|
+
}
|
|
1528
|
+
|
|
1529
|
+
function summarizeAutonomousCollectionStatus(result: {
|
|
1530
|
+
project_root?: string;
|
|
1531
|
+
shell?: string;
|
|
1532
|
+
shell_hook_doctor?: { installed?: boolean; self_test?: { success?: boolean } };
|
|
1533
|
+
shell_capture_health?: ShellCaptureHealth;
|
|
1534
|
+
background_watch?: BackgroundWatchStatus;
|
|
1535
|
+
distillation_worker?: DistillationWorkerStatus;
|
|
1536
|
+
distillation_worker_health?: DistillationWorkerHealth;
|
|
1537
|
+
collection_settings?: CollectionSettingsLike | null;
|
|
1538
|
+
review_policy?: ReviewPolicySettingsLike | null;
|
|
1539
|
+
projection_settings?: ProjectionSettingsLike | null;
|
|
1540
|
+
next_step?: UserNextStep;
|
|
1541
|
+
}): string {
|
|
1542
|
+
const shellCapture = result.shell_capture_health?.status || (result.shell_hook_doctor?.installed ? "ready" : "missing");
|
|
1543
|
+
const selfTest = result.shell_hook_doctor?.self_test?.success ?? false;
|
|
1544
|
+
const mode = result.collection_settings?.mode || "unknown";
|
|
1545
|
+
const distillerHealth =
|
|
1546
|
+
result.distillation_worker_health?.status ||
|
|
1547
|
+
(result.distillation_worker?.last_error
|
|
1548
|
+
? "error"
|
|
1549
|
+
: result.distillation_worker?.running
|
|
1550
|
+
? "running"
|
|
1551
|
+
: "stopped");
|
|
1552
|
+
const nextStep = result.next_step?.code || "none";
|
|
1553
|
+
return `OpenSyn autonomous status | project=${result.project_root || "unknown"} | command_capture=${shellCapture} | watcher=${String(result.background_watch?.running ?? false)} | distiller=${distillerHealth} | self_test=${String(selfTest)} | collection_mode=${mode} | next=${nextStep}`;
|
|
1554
|
+
}
|
|
1555
|
+
|
|
1556
|
+
function summarizeProjectionSettings(result: ProjectionSettingsLike): string {
|
|
1557
|
+
return `OpenSyn projection enabled=${String(result.enabled ?? false)} | host=${result.host || "unknown"} | auto_apply_approved=${String(result.auto_apply_approved ?? false)} | target_root=${result.target_root || "unset"}`;
|
|
1558
|
+
}
|
|
1559
|
+
|
|
1560
|
+
function summarizeProjectionRecords(results: ProjectionLike[]): string {
|
|
1561
|
+
const first = results[0];
|
|
1562
|
+
return `OpenSyn returned ${results.length} projection record(s); asset=${first.asset_id || "unknown"} | status=${first.status || first.semantic_hint?.status_label || "unknown"}`;
|
|
1563
|
+
}
|
|
1564
|
+
|
|
1565
|
+
function summarizeProjectionSync(result: ProjectionSyncLike): string {
|
|
1566
|
+
return `OpenSyn synced approved assets | host=${result.host || "unknown"} | applied_assets=${String(result.applied_asset_count ?? 0)} | applied_records=${String(result.applied_record_count ?? 0)} | skipped=${String(result.skipped_asset_ids?.length ?? 0)}`;
|
|
1567
|
+
}
|
|
1568
|
+
|
|
1569
|
+
function summarizeRuntimeOverlay(result: RuntimeOverlayLike): string {
|
|
1570
|
+
const leadFile = result.managed_files?.[0] || "unset";
|
|
1571
|
+
return `OpenSyn runtime overlay | host=${result.host || "unknown"} | approved_assets=${String(result.approved_asset_count ?? 0)} | pending_jobs=${String(result.pending_distillation_jobs ?? 0)} | managed_files=${String(result.managed_files?.length ?? 0)} | skills=${String(result.skills?.length ?? 0)} | lead_file=${leadFile} | target_root=${result.target_root || "unset"}`;
|
|
1572
|
+
}
|
|
1573
|
+
|
|
1574
|
+
function summarizeAssetApprovalResult(result: {
|
|
1575
|
+
asset?: AssetLike;
|
|
1576
|
+
projection_sync?: ProjectionSyncLike;
|
|
1577
|
+
}): string {
|
|
1578
|
+
const asset = result.asset;
|
|
1579
|
+
const sync = result.projection_sync;
|
|
1580
|
+
const revision = asset?.revision ?? asset?.semantic_hint?.revision ?? 1;
|
|
1581
|
+
const base = `OpenSyn approved asset=${asset?.title || "unknown"} | kind=${asset?.kind || asset?.semantic_hint?.asset_kind || "unknown"} | status=${asset?.status || asset?.semantic_hint?.status_label || "approved"} | revision=${String(revision)}`;
|
|
1582
|
+
if (!sync) {
|
|
1583
|
+
return base;
|
|
1584
|
+
}
|
|
1585
|
+
return `${base} | synced_records=${String(sync.applied_record_count ?? 0)} | host=${sync.host || "unknown"}`;
|
|
1586
|
+
}
|
|
1587
|
+
|
|
1588
|
+
function looksLikeContextObject(result: unknown): boolean {
|
|
1589
|
+
return (
|
|
1590
|
+
typeof result === "object" &&
|
|
1591
|
+
result !== null &&
|
|
1592
|
+
("summary" in result || "title" in result || "goal_guess" in result)
|
|
1593
|
+
);
|
|
1594
|
+
}
|
|
1595
|
+
|
|
1596
|
+
function looksLikeSearchHit(result: unknown): boolean {
|
|
1597
|
+
return (
|
|
1598
|
+
typeof result === "object" &&
|
|
1599
|
+
result !== null &&
|
|
1600
|
+
("snippet" in result || "item_kind" in result)
|
|
1601
|
+
);
|
|
1602
|
+
}
|
|
1603
|
+
|
|
1604
|
+
function looksLikeAgentAsset(result: unknown): boolean {
|
|
1605
|
+
return (
|
|
1606
|
+
typeof result === "object" &&
|
|
1607
|
+
result !== null &&
|
|
1608
|
+
("kind" in result || "status" in result)
|
|
1609
|
+
);
|
|
1610
|
+
}
|
|
1611
|
+
|
|
1612
|
+
function looksLikeAssetHistoryEntry(result: unknown): boolean {
|
|
1613
|
+
return (
|
|
1614
|
+
typeof result === "object" &&
|
|
1615
|
+
result !== null &&
|
|
1616
|
+
("change_reason" in result || "asset_id" in result) &&
|
|
1617
|
+
"asset" in result
|
|
1618
|
+
);
|
|
1619
|
+
}
|
|
1620
|
+
|
|
1621
|
+
function looksLikeAssetDiff(result: unknown): boolean {
|
|
1622
|
+
return (
|
|
1623
|
+
typeof result === "object" &&
|
|
1624
|
+
result !== null &&
|
|
1625
|
+
("from_revision" in result || "to_revision" in result) &&
|
|
1626
|
+
"asset_id" in result
|
|
1627
|
+
);
|
|
1628
|
+
}
|
|
1629
|
+
|
|
1630
|
+
function looksLikeCollectionSettings(result: unknown): boolean {
|
|
1631
|
+
return (
|
|
1632
|
+
typeof result === "object" &&
|
|
1633
|
+
result !== null &&
|
|
1634
|
+
("mode" in result || "auto_distill" in result || "distill_on_flush" in result)
|
|
1635
|
+
);
|
|
1636
|
+
}
|
|
1637
|
+
|
|
1638
|
+
function looksLikeReviewPolicySettings(result: unknown): boolean {
|
|
1639
|
+
return (
|
|
1640
|
+
typeof result === "object" &&
|
|
1641
|
+
result !== null &&
|
|
1642
|
+
("auto_approve_memory" in result ||
|
|
1643
|
+
"auto_approve_skill" in result ||
|
|
1644
|
+
"auto_approve_rule" in result ||
|
|
1645
|
+
"auto_approve_negative_example" in result)
|
|
1646
|
+
);
|
|
1647
|
+
}
|
|
1648
|
+
|
|
1649
|
+
function looksLikeDistillationSettings(result: unknown): boolean {
|
|
1650
|
+
return (
|
|
1651
|
+
typeof result === "object" &&
|
|
1652
|
+
result !== null &&
|
|
1653
|
+
("backend" in result ||
|
|
1654
|
+
"restricted_to_host_model" in result ||
|
|
1655
|
+
"auto_enqueue_on_flush" in result)
|
|
1656
|
+
);
|
|
1657
|
+
}
|
|
1658
|
+
|
|
1659
|
+
function looksLikeDistillationJob(result: unknown): boolean {
|
|
1660
|
+
return (
|
|
1661
|
+
typeof result === "object" &&
|
|
1662
|
+
result !== null &&
|
|
1663
|
+
("context_pack_id" in result || "prompt" in result || "submitted_asset_ids" in result)
|
|
1664
|
+
);
|
|
1665
|
+
}
|
|
1666
|
+
|
|
1667
|
+
function looksLikeDistillationWorkPacket(result: unknown): boolean {
|
|
1668
|
+
return (
|
|
1669
|
+
typeof result === "object" &&
|
|
1670
|
+
result !== null &&
|
|
1671
|
+
"job" in result &&
|
|
1672
|
+
"output_spec" in result
|
|
1673
|
+
);
|
|
1674
|
+
}
|
|
1675
|
+
|
|
1676
|
+
function looksLikeAutonomousTick(result: unknown): boolean {
|
|
1677
|
+
return (
|
|
1678
|
+
typeof result === "object" &&
|
|
1679
|
+
result !== null &&
|
|
1680
|
+
("action" in result || "claimed_job_id" in result) &&
|
|
1681
|
+
"runtime_overlay" in result
|
|
1682
|
+
);
|
|
1683
|
+
}
|
|
1684
|
+
|
|
1685
|
+
function looksLikeProjectionSettings(result: unknown): boolean {
|
|
1686
|
+
return (
|
|
1687
|
+
typeof result === "object" &&
|
|
1688
|
+
result !== null &&
|
|
1689
|
+
("enabled" in result || "target_root" in result || "auto_apply_approved" in result)
|
|
1690
|
+
);
|
|
1691
|
+
}
|
|
1692
|
+
|
|
1693
|
+
function looksLikeProjectionRecord(result: unknown): boolean {
|
|
1694
|
+
return (
|
|
1695
|
+
typeof result === "object" &&
|
|
1696
|
+
result !== null &&
|
|
1697
|
+
("asset_id" in result || "content" in result)
|
|
1698
|
+
);
|
|
1699
|
+
}
|
|
1700
|
+
|
|
1701
|
+
function looksLikeProjectionSync(result: unknown): boolean {
|
|
1702
|
+
return (
|
|
1703
|
+
typeof result === "object" &&
|
|
1704
|
+
result !== null &&
|
|
1705
|
+
("applied_asset_count" in result || "applied_record_count" in result || "records" in result)
|
|
1706
|
+
);
|
|
1707
|
+
}
|
|
1708
|
+
|
|
1709
|
+
function looksLikeRuntimeOverlay(result: unknown): boolean {
|
|
1710
|
+
return (
|
|
1711
|
+
typeof result === "object" &&
|
|
1712
|
+
result !== null &&
|
|
1713
|
+
("memory_markdown" in result || "agents_markdown" in result || "managed_files" in result)
|
|
1714
|
+
);
|
|
1715
|
+
}
|
|
1716
|
+
|
|
1717
|
+
function looksLikeAutonomousCollectionBootstrap(result: unknown): boolean {
|
|
1718
|
+
return (
|
|
1719
|
+
typeof result === "object" &&
|
|
1720
|
+
result !== null &&
|
|
1721
|
+
("shell_hook_fix" in result || "shell_hook_uninstall" in result || "status" in result) &&
|
|
1722
|
+
("collection_settings" in result || "status" in result)
|
|
1723
|
+
);
|
|
1724
|
+
}
|
|
1725
|
+
|
|
1726
|
+
function looksLikeAutonomousCollectionStatus(result: unknown): boolean {
|
|
1727
|
+
return (
|
|
1728
|
+
typeof result === "object" &&
|
|
1729
|
+
result !== null &&
|
|
1730
|
+
"shell_hook_doctor" in result &&
|
|
1731
|
+
"collection_settings" in result
|
|
1732
|
+
);
|
|
1733
|
+
}
|
|
1734
|
+
|
|
1735
|
+
function looksLikeAssetApprovalResult(result: unknown): boolean {
|
|
1736
|
+
return (
|
|
1737
|
+
typeof result === "object" &&
|
|
1738
|
+
result !== null &&
|
|
1739
|
+
("asset" in result || "projection_sync" in result)
|
|
1740
|
+
);
|
|
1741
|
+
}
|
|
1742
|
+
|
|
1743
|
+
function requireProjectRoot(
|
|
1744
|
+
params: { project_root?: string },
|
|
1745
|
+
config: ResolvedOpenSynConfig,
|
|
1746
|
+
): string {
|
|
1747
|
+
return params.project_root || config.defaultProjectRoot || "";
|
|
1748
|
+
}
|
|
1749
|
+
|
|
1750
|
+
function defaultProjectionRoot(config: ResolvedOpenSynConfig): string {
|
|
1751
|
+
return config.projectionBundleRoot;
|
|
1752
|
+
}
|
|
1753
|
+
|
|
1754
|
+
function getStoredPluginConfig(api: OpenClawPluginApi): OpenSynConfig {
|
|
1755
|
+
const loaded = (api.runtime.config.loadConfig?.() ?? {}) as Record<string, any>;
|
|
1756
|
+
return (
|
|
1757
|
+
loaded.plugins?.entries?.[PLUGIN_ID]?.config ??
|
|
1758
|
+
(api.pluginConfig ?? {})
|
|
1759
|
+
) as OpenSynConfig;
|
|
1760
|
+
}
|
|
1761
|
+
|
|
1762
|
+
function getResolvedPluginConfig(
|
|
1763
|
+
api: OpenClawPluginApi,
|
|
1764
|
+
defaults?: { defaultProjectRoot?: string },
|
|
1765
|
+
): ResolvedOpenSynConfig {
|
|
1766
|
+
return resolveOpenSynConfig(getStoredPluginConfig(api), defaults);
|
|
1767
|
+
}
|
|
1768
|
+
|
|
1769
|
+
async function upsertPluginConfig(
|
|
1770
|
+
api: OpenClawPluginApi,
|
|
1771
|
+
nextConfig: Partial<OpenSynConfig>,
|
|
1772
|
+
): Promise<ResolvedOpenSynConfig> {
|
|
1773
|
+
const loaded = (api.runtime.config.loadConfig?.() ?? {}) as Record<string, any>;
|
|
1774
|
+
const plugins = typeof loaded.plugins === "object" && loaded.plugins !== null ? loaded.plugins : {};
|
|
1775
|
+
const entries =
|
|
1776
|
+
typeof plugins.entries === "object" && plugins.entries !== null ? plugins.entries : {};
|
|
1777
|
+
const existingEntry =
|
|
1778
|
+
typeof entries[PLUGIN_ID] === "object" && entries[PLUGIN_ID] !== null ? entries[PLUGIN_ID] : {};
|
|
1779
|
+
const existingConfig =
|
|
1780
|
+
typeof existingEntry.config === "object" && existingEntry.config !== null
|
|
1781
|
+
? existingEntry.config
|
|
1782
|
+
: {};
|
|
1783
|
+
const allow = Array.isArray(plugins.allow) ? [...plugins.allow] : [];
|
|
1784
|
+
if (!allow.includes(PLUGIN_ID)) {
|
|
1785
|
+
allow.push(PLUGIN_ID);
|
|
1786
|
+
}
|
|
1787
|
+
const mergedConfig = {
|
|
1788
|
+
...existingConfig,
|
|
1789
|
+
...nextConfig,
|
|
1790
|
+
};
|
|
1791
|
+
const nextLoaded = {
|
|
1792
|
+
...loaded,
|
|
1793
|
+
plugins: {
|
|
1794
|
+
...plugins,
|
|
1795
|
+
allow,
|
|
1796
|
+
entries: {
|
|
1797
|
+
...entries,
|
|
1798
|
+
[PLUGIN_ID]: {
|
|
1799
|
+
...existingEntry,
|
|
1800
|
+
enabled: true,
|
|
1801
|
+
config: mergedConfig,
|
|
1802
|
+
},
|
|
1803
|
+
},
|
|
1804
|
+
},
|
|
1805
|
+
};
|
|
1806
|
+
await api.runtime.config.writeConfigFile(nextLoaded);
|
|
1807
|
+
return resolveOpenSynConfig(mergedConfig as OpenSynConfig);
|
|
1808
|
+
}
|
|
1809
|
+
|
|
1810
|
+
async function enableAutonomousCollection(
|
|
1811
|
+
config: ResolvedOpenSynConfig,
|
|
1812
|
+
params: {
|
|
1813
|
+
project_root: string;
|
|
1814
|
+
shell?: string;
|
|
1815
|
+
auto_approve_memory?: boolean;
|
|
1816
|
+
auto_approve_skill?: boolean;
|
|
1817
|
+
auto_approve_rule?: boolean;
|
|
1818
|
+
auto_approve_negative_example?: boolean;
|
|
1819
|
+
min_confidence?: number;
|
|
1820
|
+
max_auto_approve_revision?: number;
|
|
1821
|
+
require_reapproval_on_revision_bump?: boolean;
|
|
1822
|
+
projection_enabled?: boolean;
|
|
1823
|
+
projection_target_root?: string;
|
|
1824
|
+
projection_host?: string;
|
|
1825
|
+
projection_auto_apply_approved?: boolean;
|
|
1826
|
+
updated_by: string;
|
|
1827
|
+
},
|
|
1828
|
+
) {
|
|
1829
|
+
ensureProjectRoot(params.project_root);
|
|
1830
|
+
const shell = params.shell || config.defaultShell || "bash";
|
|
1831
|
+
const shellHookArgs = ["shell-hook-fix", "--db", config.dbPath, "--shell", shell];
|
|
1832
|
+
if (config.helperPath) {
|
|
1833
|
+
shellHookArgs.push("--helper-path", config.helperPath);
|
|
1834
|
+
}
|
|
1835
|
+
if (config.runnerPath) {
|
|
1836
|
+
shellHookArgs.push("--runner-path", config.runnerPath);
|
|
1837
|
+
}
|
|
1838
|
+
shellHookArgs.push("--daemon-bin", config.daemonBin);
|
|
1839
|
+
|
|
1840
|
+
await runCliCommand(config, ["init-db", "--db", config.dbPath]);
|
|
1841
|
+
const shellHookFix = await runDaemonCommand(config, shellHookArgs);
|
|
1842
|
+
const collectionSettings = await callDaemon(config, "set_collection_settings", {
|
|
1843
|
+
project_root: params.project_root,
|
|
1844
|
+
mode: "enabled",
|
|
1845
|
+
auto_distill: true,
|
|
1846
|
+
distill_on_flush: true,
|
|
1847
|
+
updated_by: params.updated_by,
|
|
1848
|
+
});
|
|
1849
|
+
const distillationSettings = await callDaemon(config, "set_distillation_settings", {
|
|
1850
|
+
project_root: params.project_root,
|
|
1851
|
+
backend: "openclaw_host_model",
|
|
1852
|
+
restricted_to_host_model: true,
|
|
1853
|
+
auto_enqueue_on_flush: true,
|
|
1854
|
+
auto_apply_submissions: true,
|
|
1855
|
+
updated_by: params.updated_by,
|
|
1856
|
+
});
|
|
1857
|
+
const reviewPolicy = await callDaemon(config, "set_review_policy_settings", {
|
|
1858
|
+
project_root: params.project_root,
|
|
1859
|
+
auto_approve_memory: params.auto_approve_memory ?? true,
|
|
1860
|
+
auto_approve_skill: params.auto_approve_skill ?? false,
|
|
1861
|
+
auto_approve_rule: params.auto_approve_rule ?? false,
|
|
1862
|
+
auto_approve_negative_example: params.auto_approve_negative_example ?? false,
|
|
1863
|
+
min_confidence: params.min_confidence ?? 0.9,
|
|
1864
|
+
max_auto_approve_revision: params.max_auto_approve_revision ?? 1,
|
|
1865
|
+
require_reapproval_on_revision_bump:
|
|
1866
|
+
params.require_reapproval_on_revision_bump ?? true,
|
|
1867
|
+
updated_by: params.updated_by,
|
|
1868
|
+
});
|
|
1869
|
+
|
|
1870
|
+
let projectionSettings: unknown = null;
|
|
1871
|
+
const projectionTargetRoot = params.projection_target_root || defaultProjectionRoot(config);
|
|
1872
|
+
const projectionEnabled =
|
|
1873
|
+
params.projection_enabled ??
|
|
1874
|
+
Boolean(projectionTargetRoot || config.projectionAutoApplyApproved);
|
|
1875
|
+
if (projectionEnabled && projectionTargetRoot) {
|
|
1876
|
+
projectionSettings = await callDaemon(config, "set_projection_settings", {
|
|
1877
|
+
project_root: params.project_root,
|
|
1878
|
+
enabled: true,
|
|
1879
|
+
host: params.projection_host || config.projectionHost || "openclaw",
|
|
1880
|
+
target_root: projectionTargetRoot,
|
|
1881
|
+
auto_apply_approved:
|
|
1882
|
+
params.projection_auto_apply_approved ??
|
|
1883
|
+
config.projectionAutoApplyApproved ??
|
|
1884
|
+
true,
|
|
1885
|
+
updated_by: params.updated_by,
|
|
1886
|
+
});
|
|
1887
|
+
}
|
|
1888
|
+
const backgroundWatch = await ensureBackgroundWatch(config, params.project_root);
|
|
1889
|
+
const distillationWorker = await ensureDistillationWorker(config, params.project_root);
|
|
1890
|
+
const distillationWorkerHealth = buildDistillationWorkerHealth(distillationWorker);
|
|
1891
|
+
const status = await getAutonomousCollectionStatus(config, {
|
|
1892
|
+
project_root: params.project_root,
|
|
1893
|
+
shell,
|
|
1894
|
+
});
|
|
1895
|
+
const nextStep = buildUserNextStep({
|
|
1896
|
+
collectionMode:
|
|
1897
|
+
(status.collection_settings as CollectionSettingsLike | undefined)?.mode || "enabled",
|
|
1898
|
+
backgroundWatch: status.background_watch as BackgroundWatchStatus | undefined,
|
|
1899
|
+
shellCaptureHealth: status.shell_capture_health as ShellCaptureHealth | undefined,
|
|
1900
|
+
distillationWorkerHealth:
|
|
1901
|
+
status.distillation_worker_health as DistillationWorkerHealth | undefined,
|
|
1902
|
+
});
|
|
1903
|
+
|
|
1904
|
+
return {
|
|
1905
|
+
semantic_hint: {
|
|
1906
|
+
activity_kind: "autonomous_collection_bootstrap",
|
|
1907
|
+
status_label: "enabled",
|
|
1908
|
+
operator_focus:
|
|
1909
|
+
"OpenSyn installed shell-hook capture and enabled automatic collection, review, and projection defaults.",
|
|
1910
|
+
},
|
|
1911
|
+
project_root: params.project_root,
|
|
1912
|
+
shell,
|
|
1913
|
+
shell_hook_fix: shellHookFix,
|
|
1914
|
+
background_watch: backgroundWatch,
|
|
1915
|
+
distillation_worker: distillationWorker,
|
|
1916
|
+
distillation_worker_health: distillationWorkerHealth,
|
|
1917
|
+
collection_settings: collectionSettings,
|
|
1918
|
+
distillation_settings: distillationSettings,
|
|
1919
|
+
review_policy: reviewPolicy,
|
|
1920
|
+
projection_settings: projectionSettings,
|
|
1921
|
+
status,
|
|
1922
|
+
next_step: nextStep,
|
|
1923
|
+
};
|
|
1924
|
+
}
|
|
1925
|
+
|
|
1926
|
+
async function getAutonomousCollectionStatus(
|
|
1927
|
+
config: ResolvedOpenSynConfig,
|
|
1928
|
+
params: { project_root: string; shell?: string },
|
|
1929
|
+
) {
|
|
1930
|
+
const shell = params.shell || config.defaultShell || "bash";
|
|
1931
|
+
const doctorArgs = ["shell-hook-doctor", "--db", config.dbPath, "--shell", shell];
|
|
1932
|
+
if (config.helperPath) {
|
|
1933
|
+
doctorArgs.push("--helper-path", config.helperPath);
|
|
1934
|
+
}
|
|
1935
|
+
if (config.runnerPath) {
|
|
1936
|
+
doctorArgs.push("--runner-path", config.runnerPath);
|
|
1937
|
+
}
|
|
1938
|
+
doctorArgs.push("--daemon-bin", config.daemonBin);
|
|
1939
|
+
|
|
1940
|
+
const shellHookDoctor = await runDaemonCommand(config, doctorArgs);
|
|
1941
|
+
const shellCaptureHealth = buildShellCaptureHealth(shellHookDoctor as ShellHookDoctorLike);
|
|
1942
|
+
const collectionSettings = await callDaemon(config, "get_collection_settings", {
|
|
1943
|
+
project_root: params.project_root,
|
|
1944
|
+
});
|
|
1945
|
+
const distillationSettings = await callDaemon(config, "get_distillation_settings", {
|
|
1946
|
+
project_root: params.project_root,
|
|
1947
|
+
});
|
|
1948
|
+
const reviewPolicy = await callDaemon(config, "get_review_policy_settings", {
|
|
1949
|
+
project_root: params.project_root,
|
|
1950
|
+
});
|
|
1951
|
+
const projectionSettings = await callDaemon(config, "get_projection_settings", {
|
|
1952
|
+
project_root: params.project_root,
|
|
1953
|
+
});
|
|
1954
|
+
const backgroundWatch = getBackgroundWatchStatus(config, params.project_root);
|
|
1955
|
+
const distillationWorker = getDistillationWorkerStatus(config, params.project_root);
|
|
1956
|
+
const distillationWorkerHealth = buildDistillationWorkerHealth(distillationWorker);
|
|
1957
|
+
const nextStep = buildUserNextStep({
|
|
1958
|
+
collectionMode: (collectionSettings as CollectionSettingsLike | undefined)?.mode,
|
|
1959
|
+
backgroundWatch,
|
|
1960
|
+
shellCaptureHealth,
|
|
1961
|
+
distillationWorkerHealth,
|
|
1962
|
+
});
|
|
1963
|
+
|
|
1964
|
+
return {
|
|
1965
|
+
semantic_hint: {
|
|
1966
|
+
activity_kind: "autonomous_collection_status",
|
|
1967
|
+
status_label: "inspected",
|
|
1968
|
+
operator_focus:
|
|
1969
|
+
"Review shell-hook health and the current collection, review, and projection settings.",
|
|
1970
|
+
},
|
|
1971
|
+
project_root: params.project_root,
|
|
1972
|
+
shell,
|
|
1973
|
+
shell_hook_doctor: shellHookDoctor,
|
|
1974
|
+
shell_capture_health: shellCaptureHealth,
|
|
1975
|
+
background_watch: backgroundWatch,
|
|
1976
|
+
distillation_worker: distillationWorker,
|
|
1977
|
+
distillation_worker_health: distillationWorkerHealth,
|
|
1978
|
+
collection_settings: collectionSettings,
|
|
1979
|
+
distillation_settings: distillationSettings,
|
|
1980
|
+
review_policy: reviewPolicy,
|
|
1981
|
+
projection_settings: projectionSettings,
|
|
1982
|
+
next_step: nextStep,
|
|
1983
|
+
};
|
|
1984
|
+
}
|
|
1985
|
+
|
|
1986
|
+
async function disableAutonomousCollection(
|
|
1987
|
+
config: ResolvedOpenSynConfig,
|
|
1988
|
+
params: { project_root: string; shell?: string; updated_by: string },
|
|
1989
|
+
) {
|
|
1990
|
+
const shell = params.shell || config.defaultShell || "bash";
|
|
1991
|
+
const shellHookUninstall = await runDaemonCommand(config, [
|
|
1992
|
+
"uninstall-shell-hook",
|
|
1993
|
+
"--shell",
|
|
1994
|
+
shell,
|
|
1995
|
+
]);
|
|
1996
|
+
const collectionSettings = await callDaemon(config, "set_collection_settings", {
|
|
1997
|
+
project_root: params.project_root,
|
|
1998
|
+
mode: "disabled",
|
|
1999
|
+
auto_distill: false,
|
|
2000
|
+
distill_on_flush: false,
|
|
2001
|
+
updated_by: params.updated_by,
|
|
2002
|
+
});
|
|
2003
|
+
const distillationSettings = await callDaemon(config, "set_distillation_settings", {
|
|
2004
|
+
project_root: params.project_root,
|
|
2005
|
+
backend: "openclaw_host_model",
|
|
2006
|
+
restricted_to_host_model: true,
|
|
2007
|
+
auto_enqueue_on_flush: false,
|
|
2008
|
+
auto_apply_submissions: false,
|
|
2009
|
+
updated_by: params.updated_by,
|
|
2010
|
+
});
|
|
2011
|
+
const backgroundWatch = stopBackgroundWatch(config, params.project_root);
|
|
2012
|
+
const distillationWorker = stopDistillationWorker(config, params.project_root);
|
|
2013
|
+
const distillationWorkerHealth = buildDistillationWorkerHealth(distillationWorker);
|
|
2014
|
+
const nextStep = buildUserNextStep({
|
|
2015
|
+
collectionMode: "disabled",
|
|
2016
|
+
backgroundWatch,
|
|
2017
|
+
distillationWorkerHealth,
|
|
2018
|
+
});
|
|
2019
|
+
|
|
2020
|
+
return {
|
|
2021
|
+
semantic_hint: {
|
|
2022
|
+
activity_kind: "autonomous_collection_bootstrap",
|
|
2023
|
+
status_label: "disabled",
|
|
2024
|
+
operator_focus:
|
|
2025
|
+
"OpenSyn removed persistent shell-hook collection and disabled background distillation for this project.",
|
|
2026
|
+
},
|
|
2027
|
+
project_root: params.project_root,
|
|
2028
|
+
shell,
|
|
2029
|
+
shell_hook_uninstall: shellHookUninstall,
|
|
2030
|
+
background_watch: backgroundWatch,
|
|
2031
|
+
distillation_worker: distillationWorker,
|
|
2032
|
+
distillation_worker_health: distillationWorkerHealth,
|
|
2033
|
+
collection_settings: collectionSettings,
|
|
2034
|
+
distillation_settings: distillationSettings,
|
|
2035
|
+
next_step: nextStep,
|
|
2036
|
+
};
|
|
2037
|
+
}
|
|
2038
|
+
|
|
2039
|
+
async function repairAutonomousCollection(
|
|
2040
|
+
config: ResolvedOpenSynConfig,
|
|
2041
|
+
params: { project_root: string; shell?: string },
|
|
2042
|
+
) {
|
|
2043
|
+
ensureProjectRoot(params.project_root);
|
|
2044
|
+
const shell = params.shell || config.defaultShell || "bash";
|
|
2045
|
+
await runCliCommand(config, ["init-db", "--db", config.dbPath]);
|
|
2046
|
+
const shellHookArgs = ["shell-hook-fix", "--db", config.dbPath, "--shell", shell];
|
|
2047
|
+
if (config.helperPath) {
|
|
2048
|
+
shellHookArgs.push("--helper-path", config.helperPath);
|
|
2049
|
+
}
|
|
2050
|
+
if (config.runnerPath) {
|
|
2051
|
+
shellHookArgs.push("--runner-path", config.runnerPath);
|
|
2052
|
+
}
|
|
2053
|
+
shellHookArgs.push("--daemon-bin", config.daemonBin);
|
|
2054
|
+
|
|
2055
|
+
const shellHookFix = await runDaemonCommand(config, shellHookArgs);
|
|
2056
|
+
const backgroundWatch = await ensureBackgroundWatch(config, params.project_root);
|
|
2057
|
+
const distillationWorker = await ensureDistillationWorker(config, params.project_root);
|
|
2058
|
+
const distillationWorkerHealth = buildDistillationWorkerHealth(distillationWorker);
|
|
2059
|
+
const status = await getAutonomousCollectionStatus(config, {
|
|
2060
|
+
project_root: params.project_root,
|
|
2061
|
+
shell,
|
|
2062
|
+
});
|
|
2063
|
+
const nextStep = buildUserNextStep({
|
|
2064
|
+
collectionMode:
|
|
2065
|
+
(status.collection_settings as CollectionSettingsLike | undefined)?.mode || "enabled",
|
|
2066
|
+
backgroundWatch: status.background_watch as BackgroundWatchStatus | undefined,
|
|
2067
|
+
shellCaptureHealth: status.shell_capture_health as ShellCaptureHealth | undefined,
|
|
2068
|
+
distillationWorkerHealth:
|
|
2069
|
+
status.distillation_worker_health as DistillationWorkerHealth | undefined,
|
|
2070
|
+
});
|
|
2071
|
+
|
|
2072
|
+
return {
|
|
2073
|
+
semantic_hint: {
|
|
2074
|
+
activity_kind: "autonomous_collection_bootstrap",
|
|
2075
|
+
status_label: "repaired",
|
|
2076
|
+
operator_focus:
|
|
2077
|
+
"OpenSyn refreshed the managed runtime, repaired the shell hook, and rechecked autonomous collection health.",
|
|
2078
|
+
},
|
|
2079
|
+
project_root: params.project_root,
|
|
2080
|
+
shell,
|
|
2081
|
+
shell_hook_fix: shellHookFix,
|
|
2082
|
+
background_watch: backgroundWatch,
|
|
2083
|
+
distillation_worker: distillationWorker,
|
|
2084
|
+
distillation_worker_health: distillationWorkerHealth,
|
|
2085
|
+
status,
|
|
2086
|
+
next_step: nextStep,
|
|
2087
|
+
};
|
|
2088
|
+
}
|
|
2089
|
+
|
|
2090
|
+
function printCliResult(result: unknown): void {
|
|
2091
|
+
const summary = summarizeResult(result);
|
|
2092
|
+
if (summary) {
|
|
2093
|
+
console.log(summary);
|
|
2094
|
+
}
|
|
2095
|
+
console.log(JSON.stringify(result, null, 2));
|
|
2096
|
+
}
|
|
2097
|
+
|
|
2098
|
+
export default {
|
|
2099
|
+
id: PLUGIN_ID,
|
|
2100
|
+
name: "OpenSyn",
|
|
2101
|
+
description: "Bridge local OpenSyn context into OpenClaw native plugin tools.",
|
|
2102
|
+
register(api: OpenClawPluginApi) {
|
|
2103
|
+
const config = getResolvedPluginConfig(api);
|
|
2104
|
+
|
|
2105
|
+
api.registerCli(
|
|
2106
|
+
({ program, workspaceDir }) => {
|
|
2107
|
+
const resolveCliProjectRoot = (input?: string): string =>
|
|
2108
|
+
path.resolve(input || workspaceDir || process.cwd());
|
|
2109
|
+
|
|
2110
|
+
const resolveCliConfig = async (
|
|
2111
|
+
projectRoot?: string,
|
|
2112
|
+
persist = false,
|
|
2113
|
+
) => {
|
|
2114
|
+
const stored = getStoredPluginConfig(api);
|
|
2115
|
+
const resolved = resolveOpenSynConfig(
|
|
2116
|
+
{
|
|
2117
|
+
...stored,
|
|
2118
|
+
defaultProjectRoot: stored.defaultProjectRoot || projectRoot,
|
|
2119
|
+
},
|
|
2120
|
+
{ defaultProjectRoot: projectRoot },
|
|
2121
|
+
);
|
|
2122
|
+
if (!persist) {
|
|
2123
|
+
return resolved;
|
|
2124
|
+
}
|
|
2125
|
+
const persisted = await upsertPluginConfig(api, resolved);
|
|
2126
|
+
Object.assign(config, persisted);
|
|
2127
|
+
return persisted;
|
|
2128
|
+
};
|
|
2129
|
+
|
|
2130
|
+
program
|
|
2131
|
+
.command("enable_opensyn")
|
|
2132
|
+
.description(
|
|
2133
|
+
"Enable OpenSyn background collection, secure host-model distillation, and host projection for the current workspace.",
|
|
2134
|
+
)
|
|
2135
|
+
.argument("[project_root]", "Optional project root. Defaults to the current OpenClaw workspace.")
|
|
2136
|
+
.option("--shell <shell>", "Shell to manage for command capture.")
|
|
2137
|
+
.action(async (projectRootArg?: string, opts?: { shell?: string }) => {
|
|
2138
|
+
const projectRoot = resolveCliProjectRoot(projectRootArg);
|
|
2139
|
+
const resolved = await resolveCliConfig(projectRoot, true);
|
|
2140
|
+
const result = await enableAutonomousCollection(resolved, {
|
|
2141
|
+
project_root: projectRoot,
|
|
2142
|
+
shell: opts?.shell,
|
|
2143
|
+
updated_by: "openclaw_cli",
|
|
2144
|
+
});
|
|
2145
|
+
printCliResult(result);
|
|
2146
|
+
});
|
|
2147
|
+
|
|
2148
|
+
program
|
|
2149
|
+
.command("disable_opensyn")
|
|
2150
|
+
.description(
|
|
2151
|
+
"Disable OpenSyn background collection for the current workspace and remove the managed shell hook.",
|
|
2152
|
+
)
|
|
2153
|
+
.argument("[project_root]", "Optional project root. Defaults to the current OpenClaw workspace.")
|
|
2154
|
+
.option("--shell <shell>", "Shell to stop managing for command capture.")
|
|
2155
|
+
.action(async (projectRootArg?: string, opts?: { shell?: string }) => {
|
|
2156
|
+
const projectRoot = resolveCliProjectRoot(projectRootArg);
|
|
2157
|
+
const resolved = await resolveCliConfig(projectRoot, true);
|
|
2158
|
+
const result = await disableAutonomousCollection(resolved, {
|
|
2159
|
+
project_root: projectRoot,
|
|
2160
|
+
shell: opts?.shell,
|
|
2161
|
+
updated_by: "openclaw_cli",
|
|
2162
|
+
});
|
|
2163
|
+
printCliResult(result);
|
|
2164
|
+
});
|
|
2165
|
+
|
|
2166
|
+
program
|
|
2167
|
+
.command("status_opensyn")
|
|
2168
|
+
.description(
|
|
2169
|
+
"Alias for openclaw opensyn_status.",
|
|
2170
|
+
)
|
|
2171
|
+
.argument("[project_root]", "Optional project root. Defaults to the current OpenClaw workspace.")
|
|
2172
|
+
.option("--shell <shell>", "Shell to inspect for command capture.")
|
|
2173
|
+
.action(async (projectRootArg?: string, opts?: { shell?: string }) => {
|
|
2174
|
+
const projectRoot = resolveCliProjectRoot(projectRootArg);
|
|
2175
|
+
const resolved = await resolveCliConfig(projectRoot);
|
|
2176
|
+
const result = await getAutonomousCollectionStatus(resolved, {
|
|
2177
|
+
project_root: projectRoot,
|
|
2178
|
+
shell: opts?.shell,
|
|
2179
|
+
});
|
|
2180
|
+
printCliResult(result);
|
|
2181
|
+
});
|
|
2182
|
+
|
|
2183
|
+
program
|
|
2184
|
+
.command("opensyn_status")
|
|
2185
|
+
.description(
|
|
2186
|
+
"Show OpenSyn autonomous collection status for the current workspace.",
|
|
2187
|
+
)
|
|
2188
|
+
.argument("[project_root]", "Optional project root. Defaults to the current OpenClaw workspace.")
|
|
2189
|
+
.option("--shell <shell>", "Shell to inspect for command capture.")
|
|
2190
|
+
.action(async (projectRootArg?: string, opts?: { shell?: string }) => {
|
|
2191
|
+
const projectRoot = resolveCliProjectRoot(projectRootArg);
|
|
2192
|
+
const resolved = await resolveCliConfig(projectRoot);
|
|
2193
|
+
const result = await getAutonomousCollectionStatus(resolved, {
|
|
2194
|
+
project_root: projectRoot,
|
|
2195
|
+
shell: opts?.shell,
|
|
2196
|
+
});
|
|
2197
|
+
printCliResult(result);
|
|
2198
|
+
});
|
|
2199
|
+
|
|
2200
|
+
program
|
|
2201
|
+
.command("doctor_opensyn")
|
|
2202
|
+
.description(
|
|
2203
|
+
"Inspect OpenSyn runtime, shell-hook, and collection health for the current workspace.",
|
|
2204
|
+
)
|
|
2205
|
+
.argument("[project_root]", "Optional project root. Defaults to the current OpenClaw workspace.")
|
|
2206
|
+
.option("--shell <shell>", "Shell to inspect for command capture.")
|
|
2207
|
+
.action(async (projectRootArg?: string, opts?: { shell?: string }) => {
|
|
2208
|
+
const projectRoot = resolveCliProjectRoot(projectRootArg);
|
|
2209
|
+
const resolved = await resolveCliConfig(projectRoot);
|
|
2210
|
+
const result = await getAutonomousCollectionStatus(resolved, {
|
|
2211
|
+
project_root: projectRoot,
|
|
2212
|
+
shell: opts?.shell,
|
|
2213
|
+
});
|
|
2214
|
+
printCliResult(result);
|
|
2215
|
+
});
|
|
2216
|
+
|
|
2217
|
+
program
|
|
2218
|
+
.command("repair_opensyn")
|
|
2219
|
+
.description(
|
|
2220
|
+
"Repair OpenSyn local runtime assets, refresh the managed shell hook, and re-check health for the current workspace.",
|
|
2221
|
+
)
|
|
2222
|
+
.argument(
|
|
2223
|
+
"[project_root]",
|
|
2224
|
+
"Optional project root. Defaults to the current OpenClaw workspace.",
|
|
2225
|
+
)
|
|
2226
|
+
.option("--shell <shell>", "Shell to repair for command capture.")
|
|
2227
|
+
.action(async (projectRootArg?: string, opts?: { shell?: string }) => {
|
|
2228
|
+
const projectRoot = resolveCliProjectRoot(projectRootArg);
|
|
2229
|
+
const resolved = await resolveCliConfig(projectRoot, true);
|
|
2230
|
+
const result = await repairAutonomousCollection(resolved, {
|
|
2231
|
+
project_root: projectRoot,
|
|
2232
|
+
shell: opts?.shell,
|
|
2233
|
+
});
|
|
2234
|
+
printCliResult(result);
|
|
2235
|
+
});
|
|
2236
|
+
|
|
2237
|
+
const opensyn = program
|
|
2238
|
+
.command("opensyn")
|
|
2239
|
+
.description("OpenSyn management commands.");
|
|
2240
|
+
|
|
2241
|
+
opensyn
|
|
2242
|
+
.command("enable")
|
|
2243
|
+
.description("Alias for openclaw enable_opensyn.")
|
|
2244
|
+
.argument("[project_root]")
|
|
2245
|
+
.option("--shell <shell>")
|
|
2246
|
+
.action(async (projectRootArg?: string, opts?: { shell?: string }) => {
|
|
2247
|
+
const projectRoot = resolveCliProjectRoot(projectRootArg);
|
|
2248
|
+
const resolved = await resolveCliConfig(projectRoot, true);
|
|
2249
|
+
const result = await enableAutonomousCollection(resolved, {
|
|
2250
|
+
project_root: projectRoot,
|
|
2251
|
+
shell: opts?.shell,
|
|
2252
|
+
updated_by: "openclaw_cli",
|
|
2253
|
+
});
|
|
2254
|
+
printCliResult(result);
|
|
2255
|
+
});
|
|
2256
|
+
|
|
2257
|
+
opensyn
|
|
2258
|
+
.command("disable")
|
|
2259
|
+
.description("Alias for openclaw disable_opensyn.")
|
|
2260
|
+
.argument("[project_root]")
|
|
2261
|
+
.option("--shell <shell>")
|
|
2262
|
+
.action(async (projectRootArg?: string, opts?: { shell?: string }) => {
|
|
2263
|
+
const projectRoot = resolveCliProjectRoot(projectRootArg);
|
|
2264
|
+
const resolved = await resolveCliConfig(projectRoot, true);
|
|
2265
|
+
const result = await disableAutonomousCollection(resolved, {
|
|
2266
|
+
project_root: projectRoot,
|
|
2267
|
+
shell: opts?.shell,
|
|
2268
|
+
updated_by: "openclaw_cli",
|
|
2269
|
+
});
|
|
2270
|
+
printCliResult(result);
|
|
2271
|
+
});
|
|
2272
|
+
|
|
2273
|
+
opensyn
|
|
2274
|
+
.command("status")
|
|
2275
|
+
.description("Alias for openclaw status_opensyn.")
|
|
2276
|
+
.argument("[project_root]")
|
|
2277
|
+
.option("--shell <shell>")
|
|
2278
|
+
.action(async (projectRootArg?: string, opts?: { shell?: string }) => {
|
|
2279
|
+
const projectRoot = resolveCliProjectRoot(projectRootArg);
|
|
2280
|
+
const resolved = await resolveCliConfig(projectRoot);
|
|
2281
|
+
const result = await getAutonomousCollectionStatus(resolved, {
|
|
2282
|
+
project_root: projectRoot,
|
|
2283
|
+
shell: opts?.shell,
|
|
2284
|
+
});
|
|
2285
|
+
printCliResult(result);
|
|
2286
|
+
});
|
|
2287
|
+
|
|
2288
|
+
opensyn
|
|
2289
|
+
.command("doctor")
|
|
2290
|
+
.description("Alias for openclaw doctor_opensyn.")
|
|
2291
|
+
.argument("[project_root]")
|
|
2292
|
+
.option("--shell <shell>")
|
|
2293
|
+
.action(async (projectRootArg?: string, opts?: { shell?: string }) => {
|
|
2294
|
+
const projectRoot = resolveCliProjectRoot(projectRootArg);
|
|
2295
|
+
const resolved = await resolveCliConfig(projectRoot);
|
|
2296
|
+
const result = await getAutonomousCollectionStatus(resolved, {
|
|
2297
|
+
project_root: projectRoot,
|
|
2298
|
+
shell: opts?.shell,
|
|
2299
|
+
});
|
|
2300
|
+
printCliResult(result);
|
|
2301
|
+
});
|
|
2302
|
+
|
|
2303
|
+
opensyn
|
|
2304
|
+
.command("repair")
|
|
2305
|
+
.description("Repair OpenSyn runtime and refresh the managed shell hook.")
|
|
2306
|
+
.argument("[project_root]")
|
|
2307
|
+
.option("--shell <shell>")
|
|
2308
|
+
.action(async (projectRootArg?: string, opts?: { shell?: string }) => {
|
|
2309
|
+
const projectRoot = resolveCliProjectRoot(projectRootArg);
|
|
2310
|
+
const resolved = await resolveCliConfig(projectRoot, true);
|
|
2311
|
+
const result = await repairAutonomousCollection(resolved, {
|
|
2312
|
+
project_root: projectRoot,
|
|
2313
|
+
shell: opts?.shell,
|
|
2314
|
+
});
|
|
2315
|
+
printCliResult(result);
|
|
2316
|
+
});
|
|
2317
|
+
},
|
|
2318
|
+
{
|
|
2319
|
+
commands: [
|
|
2320
|
+
"enable_opensyn",
|
|
2321
|
+
"disable_opensyn",
|
|
2322
|
+
"status_opensyn",
|
|
2323
|
+
"opensyn_status",
|
|
2324
|
+
"doctor_opensyn",
|
|
2325
|
+
"repair_opensyn",
|
|
2326
|
+
"opensyn",
|
|
2327
|
+
],
|
|
2328
|
+
},
|
|
2329
|
+
);
|
|
2330
|
+
|
|
2331
|
+
api.registerTool(
|
|
2332
|
+
{
|
|
2333
|
+
name: "opensyn_recent_failure",
|
|
2334
|
+
description:
|
|
2335
|
+
"Return the most recent OpenSyn build failure context. Omit project_root to use defaultProjectRoot from plugin config.",
|
|
2336
|
+
parameters: Type.Object({
|
|
2337
|
+
project_root: Type.Optional(
|
|
2338
|
+
Type.String({
|
|
2339
|
+
description:
|
|
2340
|
+
"Optional absolute project root. Leave unset to use defaultProjectRoot from plugin config.",
|
|
2341
|
+
}),
|
|
2342
|
+
),
|
|
2343
|
+
}),
|
|
2344
|
+
async execute(_id, params: { project_root?: string }) {
|
|
2345
|
+
const projectRoot = requireProjectRoot(params, config);
|
|
2346
|
+
if (!projectRoot) {
|
|
2347
|
+
throw new Error("project_root is required unless defaultProjectRoot is configured");
|
|
2348
|
+
}
|
|
2349
|
+
const result = await callDaemon(config, "get_recent_build_failure", {
|
|
2350
|
+
project_root: projectRoot,
|
|
2351
|
+
});
|
|
2352
|
+
return asText(result);
|
|
2353
|
+
},
|
|
2354
|
+
},
|
|
2355
|
+
{ optional: true },
|
|
2356
|
+
);
|
|
2357
|
+
|
|
2358
|
+
api.registerTool(
|
|
2359
|
+
{
|
|
2360
|
+
name: "opensyn_current_context",
|
|
2361
|
+
description:
|
|
2362
|
+
"Synthesize current OpenSyn context from recent raw events. Omit project_root to use defaultProjectRoot from plugin config.",
|
|
2363
|
+
parameters: Type.Object({
|
|
2364
|
+
project_root: Type.Optional(
|
|
2365
|
+
Type.String({
|
|
2366
|
+
description:
|
|
2367
|
+
"Optional absolute project root. Leave unset to use defaultProjectRoot from plugin config.",
|
|
2368
|
+
}),
|
|
2369
|
+
),
|
|
2370
|
+
window_secs: Type.Optional(Type.Integer({ minimum: 1 })),
|
|
2371
|
+
limit: Type.Optional(Type.Integer({ minimum: 1 })),
|
|
2372
|
+
}),
|
|
2373
|
+
async execute(
|
|
2374
|
+
_id,
|
|
2375
|
+
params: { project_root?: string; window_secs?: number; limit?: number },
|
|
2376
|
+
) {
|
|
2377
|
+
const projectRoot = requireProjectRoot(params, config);
|
|
2378
|
+
if (!projectRoot) {
|
|
2379
|
+
throw new Error("project_root is required unless defaultProjectRoot is configured");
|
|
2380
|
+
}
|
|
2381
|
+
const result = await callDaemon(config, "synthesize_recent_activity", {
|
|
2382
|
+
project_root: projectRoot,
|
|
2383
|
+
window_secs: params.window_secs ?? config.defaultWindowSecs ?? 300,
|
|
2384
|
+
limit: params.limit ?? 512,
|
|
2385
|
+
});
|
|
2386
|
+
return asText(result);
|
|
2387
|
+
},
|
|
2388
|
+
},
|
|
2389
|
+
{ optional: true },
|
|
2390
|
+
);
|
|
2391
|
+
|
|
2392
|
+
api.registerTool(
|
|
2393
|
+
{
|
|
2394
|
+
name: "opensyn_recent_android_debug",
|
|
2395
|
+
description:
|
|
2396
|
+
"Return the most recent Android debug context. Omit project_root to use defaultProjectRoot from plugin config.",
|
|
2397
|
+
parameters: Type.Object({
|
|
2398
|
+
project_root: Type.Optional(
|
|
2399
|
+
Type.String({
|
|
2400
|
+
description:
|
|
2401
|
+
"Optional absolute project root. Leave unset to use defaultProjectRoot from plugin config.",
|
|
2402
|
+
}),
|
|
2403
|
+
),
|
|
2404
|
+
}),
|
|
2405
|
+
async execute(_id, params: { project_root?: string }) {
|
|
2406
|
+
const projectRoot = requireProjectRoot(params, config);
|
|
2407
|
+
if (!projectRoot) {
|
|
2408
|
+
throw new Error("project_root is required unless defaultProjectRoot is configured");
|
|
2409
|
+
}
|
|
2410
|
+
const result = await callDaemon(config, "get_recent_android_debug_context", {
|
|
2411
|
+
project_root: projectRoot,
|
|
2412
|
+
});
|
|
2413
|
+
return asText(result);
|
|
2414
|
+
},
|
|
2415
|
+
},
|
|
2416
|
+
{ optional: true },
|
|
2417
|
+
);
|
|
2418
|
+
|
|
2419
|
+
api.registerTool(
|
|
2420
|
+
{
|
|
2421
|
+
name: "opensyn_search_history",
|
|
2422
|
+
description:
|
|
2423
|
+
"Search OpenSyn task episodes and context packs. Omit project_root to use defaultProjectRoot from plugin config.",
|
|
2424
|
+
parameters: Type.Object({
|
|
2425
|
+
project_root: Type.Optional(
|
|
2426
|
+
Type.String({
|
|
2427
|
+
description:
|
|
2428
|
+
"Optional absolute project root. Leave unset to use defaultProjectRoot from plugin config.",
|
|
2429
|
+
}),
|
|
2430
|
+
),
|
|
2431
|
+
query: Type.String(),
|
|
2432
|
+
limit: Type.Optional(Type.Integer({ minimum: 1 })),
|
|
2433
|
+
}),
|
|
2434
|
+
async execute(
|
|
2435
|
+
_id,
|
|
2436
|
+
params: { project_root?: string; query: string; limit?: number },
|
|
2437
|
+
) {
|
|
2438
|
+
const projectRoot = requireProjectRoot(params, config);
|
|
2439
|
+
if (!projectRoot) {
|
|
2440
|
+
throw new Error("project_root is required unless defaultProjectRoot is configured");
|
|
2441
|
+
}
|
|
2442
|
+
const result = await callDaemon(config, "search_project_history", {
|
|
2443
|
+
project_root: projectRoot,
|
|
2444
|
+
query: params.query,
|
|
2445
|
+
limit: params.limit ?? config.defaultSearchLimit ?? 10,
|
|
2446
|
+
});
|
|
2447
|
+
return asText(result);
|
|
2448
|
+
},
|
|
2449
|
+
},
|
|
2450
|
+
{ optional: true },
|
|
2451
|
+
);
|
|
2452
|
+
|
|
2453
|
+
api.registerTool(
|
|
2454
|
+
{
|
|
2455
|
+
name: "opensyn_distill_assets",
|
|
2456
|
+
description:
|
|
2457
|
+
"Distill candidate memory/skill/rule assets for the current project. Omit project_root to use defaultProjectRoot from plugin config.",
|
|
2458
|
+
parameters: Type.Object({
|
|
2459
|
+
project_root: Type.Optional(
|
|
2460
|
+
Type.String({
|
|
2461
|
+
description:
|
|
2462
|
+
"Optional absolute project root. Leave unset to use defaultProjectRoot from plugin config.",
|
|
2463
|
+
}),
|
|
2464
|
+
),
|
|
2465
|
+
limit: Type.Optional(Type.Integer({ minimum: 1 })),
|
|
2466
|
+
}),
|
|
2467
|
+
async execute(
|
|
2468
|
+
_id,
|
|
2469
|
+
params: { project_root?: string; limit?: number },
|
|
2470
|
+
) {
|
|
2471
|
+
const projectRoot = requireProjectRoot(params, config);
|
|
2472
|
+
if (!projectRoot) {
|
|
2473
|
+
throw new Error("project_root is required unless defaultProjectRoot is configured");
|
|
2474
|
+
}
|
|
2475
|
+
const result = await callDaemon(config, "distill_agent_assets", {
|
|
2476
|
+
project_root: projectRoot,
|
|
2477
|
+
limit: params.limit ?? 5,
|
|
2478
|
+
});
|
|
2479
|
+
return asText(result);
|
|
2480
|
+
},
|
|
2481
|
+
},
|
|
2482
|
+
{ optional: true },
|
|
2483
|
+
);
|
|
2484
|
+
|
|
2485
|
+
api.registerTool(
|
|
2486
|
+
{
|
|
2487
|
+
name: "opensyn_list_assets",
|
|
2488
|
+
description:
|
|
2489
|
+
"List stored agent assets such as memory, skill, and rule candidates. Omit project_root to use defaultProjectRoot from plugin config.",
|
|
2490
|
+
parameters: Type.Object({
|
|
2491
|
+
project_root: Type.Optional(
|
|
2492
|
+
Type.String({
|
|
2493
|
+
description:
|
|
2494
|
+
"Optional absolute project root. Leave unset to use defaultProjectRoot from plugin config.",
|
|
2495
|
+
}),
|
|
2496
|
+
),
|
|
2497
|
+
kind: Type.Optional(
|
|
2498
|
+
Type.String({
|
|
2499
|
+
description:
|
|
2500
|
+
"Optional asset kind filter: memory, skill, rule, role, preference, negative_example, soul.",
|
|
2501
|
+
}),
|
|
2502
|
+
),
|
|
2503
|
+
status: Type.Optional(
|
|
2504
|
+
Type.String({
|
|
2505
|
+
description:
|
|
2506
|
+
"Optional asset status filter: candidate, approved, rejected, archived.",
|
|
2507
|
+
}),
|
|
2508
|
+
),
|
|
2509
|
+
limit: Type.Optional(Type.Integer({ minimum: 1 })),
|
|
2510
|
+
}),
|
|
2511
|
+
async execute(
|
|
2512
|
+
_id,
|
|
2513
|
+
params: {
|
|
2514
|
+
project_root?: string;
|
|
2515
|
+
kind?: string;
|
|
2516
|
+
status?: string;
|
|
2517
|
+
limit?: number;
|
|
2518
|
+
},
|
|
2519
|
+
) {
|
|
2520
|
+
const projectRoot = requireProjectRoot(params, config);
|
|
2521
|
+
if (!projectRoot) {
|
|
2522
|
+
throw new Error("project_root is required unless defaultProjectRoot is configured");
|
|
2523
|
+
}
|
|
2524
|
+
const result = await callDaemon(config, "list_agent_assets", {
|
|
2525
|
+
project_root: projectRoot,
|
|
2526
|
+
kind: params.kind,
|
|
2527
|
+
status: params.status,
|
|
2528
|
+
limit: params.limit ?? config.defaultSearchLimit ?? 10,
|
|
2529
|
+
});
|
|
2530
|
+
return asText(result);
|
|
2531
|
+
},
|
|
2532
|
+
},
|
|
2533
|
+
{ optional: true },
|
|
2534
|
+
);
|
|
2535
|
+
|
|
2536
|
+
api.registerTool(
|
|
2537
|
+
{
|
|
2538
|
+
name: "opensyn_list_asset_history",
|
|
2539
|
+
description:
|
|
2540
|
+
"List revision history for one asset or one lineage. Omit project_root to use defaultProjectRoot from plugin config.",
|
|
2541
|
+
parameters: Type.Object({
|
|
2542
|
+
project_root: Type.Optional(
|
|
2543
|
+
Type.String({
|
|
2544
|
+
description:
|
|
2545
|
+
"Optional absolute project root. Leave unset to use defaultProjectRoot from plugin config.",
|
|
2546
|
+
}),
|
|
2547
|
+
),
|
|
2548
|
+
asset_id: Type.Optional(Type.String()),
|
|
2549
|
+
lineage_key: Type.Optional(Type.String()),
|
|
2550
|
+
limit: Type.Optional(Type.Integer({ minimum: 1 })),
|
|
2551
|
+
}),
|
|
2552
|
+
async execute(
|
|
2553
|
+
_id,
|
|
2554
|
+
params: {
|
|
2555
|
+
project_root?: string;
|
|
2556
|
+
asset_id?: string;
|
|
2557
|
+
lineage_key?: string;
|
|
2558
|
+
limit?: number;
|
|
2559
|
+
},
|
|
2560
|
+
) {
|
|
2561
|
+
const projectRoot = requireProjectRoot(params, config);
|
|
2562
|
+
if (!projectRoot) {
|
|
2563
|
+
throw new Error("project_root is required unless defaultProjectRoot is configured");
|
|
2564
|
+
}
|
|
2565
|
+
const result = await callDaemon(config, "list_agent_asset_history", {
|
|
2566
|
+
project_root: projectRoot,
|
|
2567
|
+
asset_id: params.asset_id,
|
|
2568
|
+
lineage_key: params.lineage_key,
|
|
2569
|
+
limit: params.limit ?? config.defaultSearchLimit ?? 10,
|
|
2570
|
+
});
|
|
2571
|
+
return asText(result);
|
|
2572
|
+
},
|
|
2573
|
+
},
|
|
2574
|
+
{ optional: true },
|
|
2575
|
+
);
|
|
2576
|
+
|
|
2577
|
+
api.registerTool(
|
|
2578
|
+
{
|
|
2579
|
+
name: "opensyn_diff_asset_revisions",
|
|
2580
|
+
description:
|
|
2581
|
+
"Compare two revisions of the same asset so the agent can review what changed before approving or rolling back.",
|
|
2582
|
+
parameters: Type.Object({
|
|
2583
|
+
asset_id: Type.String(),
|
|
2584
|
+
from_revision: Type.Integer({ minimum: 1 }),
|
|
2585
|
+
to_revision: Type.Integer({ minimum: 1 }),
|
|
2586
|
+
}),
|
|
2587
|
+
async execute(
|
|
2588
|
+
_id,
|
|
2589
|
+
params: { asset_id: string; from_revision: number; to_revision: number },
|
|
2590
|
+
) {
|
|
2591
|
+
const result = await callDaemon(config, "diff_agent_asset_revisions", {
|
|
2592
|
+
asset_id: params.asset_id,
|
|
2593
|
+
from_revision: params.from_revision,
|
|
2594
|
+
to_revision: params.to_revision,
|
|
2595
|
+
});
|
|
2596
|
+
return asText(result);
|
|
2597
|
+
},
|
|
2598
|
+
},
|
|
2599
|
+
{ optional: true },
|
|
2600
|
+
);
|
|
2601
|
+
|
|
2602
|
+
api.registerTool(
|
|
2603
|
+
{
|
|
2604
|
+
name: "opensyn_enable_autonomous_collection",
|
|
2605
|
+
description:
|
|
2606
|
+
"Bootstrap plugin-managed OpenSyn collection on this machine: install or refresh the shell hook, enable automatic distillation, and configure cautious review/projection defaults for the current project.",
|
|
2607
|
+
parameters: Type.Object({
|
|
2608
|
+
project_root: Type.Optional(
|
|
2609
|
+
Type.String({
|
|
2610
|
+
description:
|
|
2611
|
+
"Optional absolute project root. Leave unset to use defaultProjectRoot from plugin config.",
|
|
2612
|
+
}),
|
|
2613
|
+
),
|
|
2614
|
+
shell: Type.Optional(
|
|
2615
|
+
Type.String({
|
|
2616
|
+
description:
|
|
2617
|
+
"Shell to configure for persistent command collection, for example bash or zsh.",
|
|
2618
|
+
}),
|
|
2619
|
+
),
|
|
2620
|
+
auto_approve_memory: Type.Optional(Type.Boolean()),
|
|
2621
|
+
auto_approve_skill: Type.Optional(Type.Boolean()),
|
|
2622
|
+
auto_approve_rule: Type.Optional(Type.Boolean()),
|
|
2623
|
+
auto_approve_negative_example: Type.Optional(Type.Boolean()),
|
|
2624
|
+
min_confidence: Type.Optional(Type.Number({ minimum: 0, maximum: 1 })),
|
|
2625
|
+
max_auto_approve_revision: Type.Optional(Type.Integer({ minimum: 1 })),
|
|
2626
|
+
require_reapproval_on_revision_bump: Type.Optional(Type.Boolean()),
|
|
2627
|
+
projection_enabled: Type.Optional(Type.Boolean()),
|
|
2628
|
+
projection_target_root: Type.Optional(
|
|
2629
|
+
Type.String({
|
|
2630
|
+
description:
|
|
2631
|
+
"Optional projection root. Leave unset to use projectionBundleRoot from plugin config.",
|
|
2632
|
+
}),
|
|
2633
|
+
),
|
|
2634
|
+
projection_host: Type.Optional(
|
|
2635
|
+
Type.String({
|
|
2636
|
+
description:
|
|
2637
|
+
"Optional projection host: openclaw, claude_code, or generic. Leave unset to use projectionHost from plugin config.",
|
|
2638
|
+
}),
|
|
2639
|
+
),
|
|
2640
|
+
projection_auto_apply_approved: Type.Optional(Type.Boolean()),
|
|
2641
|
+
}),
|
|
2642
|
+
async execute(
|
|
2643
|
+
_id,
|
|
2644
|
+
params: {
|
|
2645
|
+
project_root?: string;
|
|
2646
|
+
shell?: string;
|
|
2647
|
+
auto_approve_memory?: boolean;
|
|
2648
|
+
auto_approve_skill?: boolean;
|
|
2649
|
+
auto_approve_rule?: boolean;
|
|
2650
|
+
auto_approve_negative_example?: boolean;
|
|
2651
|
+
min_confidence?: number;
|
|
2652
|
+
max_auto_approve_revision?: number;
|
|
2653
|
+
require_reapproval_on_revision_bump?: boolean;
|
|
2654
|
+
projection_enabled?: boolean;
|
|
2655
|
+
projection_target_root?: string;
|
|
2656
|
+
projection_host?: string;
|
|
2657
|
+
projection_auto_apply_approved?: boolean;
|
|
2658
|
+
},
|
|
2659
|
+
) {
|
|
2660
|
+
const projectRoot = requireProjectRoot(params, config);
|
|
2661
|
+
if (!projectRoot) {
|
|
2662
|
+
throw new Error("project_root is required unless defaultProjectRoot is configured");
|
|
2663
|
+
}
|
|
2664
|
+
const result = await enableAutonomousCollection(config, {
|
|
2665
|
+
project_root: projectRoot,
|
|
2666
|
+
shell: params.shell,
|
|
2667
|
+
auto_approve_memory: params.auto_approve_memory,
|
|
2668
|
+
auto_approve_skill: params.auto_approve_skill,
|
|
2669
|
+
auto_approve_rule: params.auto_approve_rule,
|
|
2670
|
+
auto_approve_negative_example: params.auto_approve_negative_example,
|
|
2671
|
+
min_confidence: params.min_confidence,
|
|
2672
|
+
max_auto_approve_revision: params.max_auto_approve_revision,
|
|
2673
|
+
require_reapproval_on_revision_bump:
|
|
2674
|
+
params.require_reapproval_on_revision_bump,
|
|
2675
|
+
projection_enabled: params.projection_enabled,
|
|
2676
|
+
projection_target_root: params.projection_target_root,
|
|
2677
|
+
projection_host: params.projection_host,
|
|
2678
|
+
projection_auto_apply_approved: params.projection_auto_apply_approved,
|
|
2679
|
+
updated_by: "openclaw_plugin",
|
|
2680
|
+
});
|
|
2681
|
+
return asText(result);
|
|
2682
|
+
},
|
|
2683
|
+
},
|
|
2684
|
+
{ optional: true },
|
|
2685
|
+
);
|
|
2686
|
+
|
|
2687
|
+
api.registerTool(
|
|
2688
|
+
{
|
|
2689
|
+
name: "opensyn_autonomous_collection_status",
|
|
2690
|
+
description:
|
|
2691
|
+
"Inspect plugin-managed OpenSyn collection on this machine: shell hook health plus collection, review, and projection settings for the current project.",
|
|
2692
|
+
parameters: Type.Object({
|
|
2693
|
+
project_root: Type.Optional(
|
|
2694
|
+
Type.String({
|
|
2695
|
+
description:
|
|
2696
|
+
"Optional absolute project root. Leave unset to use defaultProjectRoot from plugin config.",
|
|
2697
|
+
}),
|
|
2698
|
+
),
|
|
2699
|
+
shell: Type.Optional(
|
|
2700
|
+
Type.String({
|
|
2701
|
+
description:
|
|
2702
|
+
"Shell to inspect for persistent command collection, for example bash or zsh.",
|
|
2703
|
+
}),
|
|
2704
|
+
),
|
|
2705
|
+
}),
|
|
2706
|
+
async execute(
|
|
2707
|
+
_id,
|
|
2708
|
+
params: {
|
|
2709
|
+
project_root?: string;
|
|
2710
|
+
shell?: string;
|
|
2711
|
+
},
|
|
2712
|
+
) {
|
|
2713
|
+
const projectRoot = requireProjectRoot(params, config);
|
|
2714
|
+
if (!projectRoot) {
|
|
2715
|
+
throw new Error("project_root is required unless defaultProjectRoot is configured");
|
|
2716
|
+
}
|
|
2717
|
+
const result = await getAutonomousCollectionStatus(config, {
|
|
2718
|
+
project_root: projectRoot,
|
|
2719
|
+
shell: params.shell,
|
|
2720
|
+
});
|
|
2721
|
+
return asText(result);
|
|
2722
|
+
},
|
|
2723
|
+
},
|
|
2724
|
+
{ optional: true },
|
|
2725
|
+
);
|
|
2726
|
+
|
|
2727
|
+
api.registerTool(
|
|
2728
|
+
{
|
|
2729
|
+
name: "opensyn_get_collection_mode",
|
|
2730
|
+
description:
|
|
2731
|
+
"Read OpenSyn collection-mode settings for the current project. Omit project_root to use defaultProjectRoot from plugin config.",
|
|
2732
|
+
parameters: Type.Object({
|
|
2733
|
+
project_root: Type.Optional(
|
|
2734
|
+
Type.String({
|
|
2735
|
+
description:
|
|
2736
|
+
"Optional absolute project root. Leave unset to use defaultProjectRoot from plugin config.",
|
|
2737
|
+
}),
|
|
2738
|
+
),
|
|
2739
|
+
}),
|
|
2740
|
+
async execute(_id, params: { project_root?: string }) {
|
|
2741
|
+
const projectRoot = requireProjectRoot(params, config);
|
|
2742
|
+
if (!projectRoot) {
|
|
2743
|
+
throw new Error("project_root is required unless defaultProjectRoot is configured");
|
|
2744
|
+
}
|
|
2745
|
+
const result = await callDaemon(config, "get_collection_settings", {
|
|
2746
|
+
project_root: projectRoot,
|
|
2747
|
+
});
|
|
2748
|
+
return asText(result);
|
|
2749
|
+
},
|
|
2750
|
+
},
|
|
2751
|
+
{ optional: true },
|
|
2752
|
+
);
|
|
2753
|
+
|
|
2754
|
+
api.registerTool(
|
|
2755
|
+
{
|
|
2756
|
+
name: "opensyn_get_review_policy",
|
|
2757
|
+
description:
|
|
2758
|
+
"Read OpenSyn review-policy settings for the current project. Omit project_root to use defaultProjectRoot from plugin config.",
|
|
2759
|
+
parameters: Type.Object({
|
|
2760
|
+
project_root: Type.Optional(
|
|
2761
|
+
Type.String({
|
|
2762
|
+
description:
|
|
2763
|
+
"Optional absolute project root. Leave unset to use defaultProjectRoot from plugin config.",
|
|
2764
|
+
}),
|
|
2765
|
+
),
|
|
2766
|
+
}),
|
|
2767
|
+
async execute(_id, params: { project_root?: string }) {
|
|
2768
|
+
const projectRoot = requireProjectRoot(params, config);
|
|
2769
|
+
if (!projectRoot) {
|
|
2770
|
+
throw new Error("project_root is required unless defaultProjectRoot is configured");
|
|
2771
|
+
}
|
|
2772
|
+
const result = await callDaemon(config, "get_review_policy_settings", {
|
|
2773
|
+
project_root: projectRoot,
|
|
2774
|
+
});
|
|
2775
|
+
return asText(result);
|
|
2776
|
+
},
|
|
2777
|
+
},
|
|
2778
|
+
{ optional: true },
|
|
2779
|
+
);
|
|
2780
|
+
|
|
2781
|
+
api.registerTool(
|
|
2782
|
+
{
|
|
2783
|
+
name: "opensyn_get_distillation_mode",
|
|
2784
|
+
description:
|
|
2785
|
+
"Read OpenSyn secure distillation settings for the current project. This is the control surface that decides whether distillation stays local or is restricted to the OpenClaw host model.",
|
|
2786
|
+
parameters: Type.Object({
|
|
2787
|
+
project_root: Type.Optional(
|
|
2788
|
+
Type.String({
|
|
2789
|
+
description:
|
|
2790
|
+
"Optional absolute project root. Leave unset to use defaultProjectRoot from plugin config.",
|
|
2791
|
+
}),
|
|
2792
|
+
),
|
|
2793
|
+
}),
|
|
2794
|
+
async execute(_id, params: { project_root?: string }) {
|
|
2795
|
+
const projectRoot = requireProjectRoot(params, config);
|
|
2796
|
+
if (!projectRoot) {
|
|
2797
|
+
throw new Error("project_root is required unless defaultProjectRoot is configured");
|
|
2798
|
+
}
|
|
2799
|
+
const result = await callDaemon(config, "get_distillation_settings", {
|
|
2800
|
+
project_root: projectRoot,
|
|
2801
|
+
});
|
|
2802
|
+
return asText(result);
|
|
2803
|
+
},
|
|
2804
|
+
},
|
|
2805
|
+
{ optional: true },
|
|
2806
|
+
);
|
|
2807
|
+
|
|
2808
|
+
api.registerTool(
|
|
2809
|
+
{
|
|
2810
|
+
name: "opensyn_set_distillation_mode",
|
|
2811
|
+
description:
|
|
2812
|
+
"Set secure distillation settings for the current project. Recommended mode is openclaw_host_model with host-only restrictions and no network access.",
|
|
2813
|
+
parameters: Type.Object({
|
|
2814
|
+
project_root: Type.Optional(
|
|
2815
|
+
Type.String({
|
|
2816
|
+
description:
|
|
2817
|
+
"Optional absolute project root. Leave unset to use defaultProjectRoot from plugin config.",
|
|
2818
|
+
}),
|
|
2819
|
+
),
|
|
2820
|
+
backend: Type.Optional(
|
|
2821
|
+
Type.String({
|
|
2822
|
+
description:
|
|
2823
|
+
"Distillation backend: openclaw_host_model or local_heuristic.",
|
|
2824
|
+
}),
|
|
2825
|
+
),
|
|
2826
|
+
restricted_to_host_model: Type.Optional(Type.Boolean()),
|
|
2827
|
+
auto_enqueue_on_flush: Type.Optional(Type.Boolean()),
|
|
2828
|
+
auto_apply_submissions: Type.Optional(Type.Boolean()),
|
|
2829
|
+
}),
|
|
2830
|
+
async execute(
|
|
2831
|
+
_id,
|
|
2832
|
+
params: {
|
|
2833
|
+
project_root?: string;
|
|
2834
|
+
backend?: string;
|
|
2835
|
+
restricted_to_host_model?: boolean;
|
|
2836
|
+
auto_enqueue_on_flush?: boolean;
|
|
2837
|
+
auto_apply_submissions?: boolean;
|
|
2838
|
+
},
|
|
2839
|
+
) {
|
|
2840
|
+
const projectRoot = requireProjectRoot(params, config);
|
|
2841
|
+
if (!projectRoot) {
|
|
2842
|
+
throw new Error("project_root is required unless defaultProjectRoot is configured");
|
|
2843
|
+
}
|
|
2844
|
+
const result = await callDaemon(config, "set_distillation_settings", {
|
|
2845
|
+
project_root: projectRoot,
|
|
2846
|
+
backend: params.backend ?? "openclaw_host_model",
|
|
2847
|
+
restricted_to_host_model: params.restricted_to_host_model ?? true,
|
|
2848
|
+
auto_enqueue_on_flush: params.auto_enqueue_on_flush ?? true,
|
|
2849
|
+
auto_apply_submissions: params.auto_apply_submissions ?? true,
|
|
2850
|
+
updated_by: "openclaw_plugin",
|
|
2851
|
+
});
|
|
2852
|
+
return asText(result);
|
|
2853
|
+
},
|
|
2854
|
+
},
|
|
2855
|
+
{ optional: true },
|
|
2856
|
+
);
|
|
2857
|
+
|
|
2858
|
+
api.registerTool(
|
|
2859
|
+
{
|
|
2860
|
+
name: "opensyn_set_review_policy",
|
|
2861
|
+
description:
|
|
2862
|
+
"Set OpenSyn review-policy settings for the current project. Omit project_root to use defaultProjectRoot from plugin config.",
|
|
2863
|
+
parameters: Type.Object({
|
|
2864
|
+
project_root: Type.Optional(
|
|
2865
|
+
Type.String({
|
|
2866
|
+
description:
|
|
2867
|
+
"Optional absolute project root. Leave unset to use defaultProjectRoot from plugin config.",
|
|
2868
|
+
}),
|
|
2869
|
+
),
|
|
2870
|
+
auto_approve_memory: Type.Optional(Type.Boolean()),
|
|
2871
|
+
auto_approve_skill: Type.Optional(Type.Boolean()),
|
|
2872
|
+
auto_approve_rule: Type.Optional(Type.Boolean()),
|
|
2873
|
+
auto_approve_negative_example: Type.Optional(Type.Boolean()),
|
|
2874
|
+
min_confidence: Type.Optional(Type.Number({ minimum: 0, maximum: 1 })),
|
|
2875
|
+
max_auto_approve_revision: Type.Optional(Type.Integer({ minimum: 1 })),
|
|
2876
|
+
require_reapproval_on_revision_bump: Type.Optional(Type.Boolean()),
|
|
2877
|
+
}),
|
|
2878
|
+
async execute(
|
|
2879
|
+
_id,
|
|
2880
|
+
params: {
|
|
2881
|
+
project_root?: string;
|
|
2882
|
+
auto_approve_memory?: boolean;
|
|
2883
|
+
auto_approve_skill?: boolean;
|
|
2884
|
+
auto_approve_rule?: boolean;
|
|
2885
|
+
auto_approve_negative_example?: boolean;
|
|
2886
|
+
min_confidence?: number;
|
|
2887
|
+
max_auto_approve_revision?: number;
|
|
2888
|
+
require_reapproval_on_revision_bump?: boolean;
|
|
2889
|
+
},
|
|
2890
|
+
) {
|
|
2891
|
+
const projectRoot = requireProjectRoot(params, config);
|
|
2892
|
+
if (!projectRoot) {
|
|
2893
|
+
throw new Error("project_root is required unless defaultProjectRoot is configured");
|
|
2894
|
+
}
|
|
2895
|
+
const result = await callDaemon(config, "set_review_policy_settings", {
|
|
2896
|
+
project_root: projectRoot,
|
|
2897
|
+
auto_approve_memory: params.auto_approve_memory,
|
|
2898
|
+
auto_approve_skill: params.auto_approve_skill,
|
|
2899
|
+
auto_approve_rule: params.auto_approve_rule,
|
|
2900
|
+
auto_approve_negative_example: params.auto_approve_negative_example,
|
|
2901
|
+
min_confidence: params.min_confidence,
|
|
2902
|
+
max_auto_approve_revision: params.max_auto_approve_revision,
|
|
2903
|
+
require_reapproval_on_revision_bump:
|
|
2904
|
+
params.require_reapproval_on_revision_bump,
|
|
2905
|
+
updated_by: "openclaw_plugin",
|
|
2906
|
+
});
|
|
2907
|
+
return asText(result);
|
|
2908
|
+
},
|
|
2909
|
+
},
|
|
2910
|
+
{ optional: true },
|
|
2911
|
+
);
|
|
2912
|
+
|
|
2913
|
+
api.registerTool(
|
|
2914
|
+
{
|
|
2915
|
+
name: "opensyn_list_distillation_jobs",
|
|
2916
|
+
description:
|
|
2917
|
+
"List queued host-model distillation jobs for the current project. These jobs contain the grounded prompt that should be consumed only by the OpenClaw-configured model.",
|
|
2918
|
+
parameters: Type.Object({
|
|
2919
|
+
project_root: Type.Optional(
|
|
2920
|
+
Type.String({
|
|
2921
|
+
description:
|
|
2922
|
+
"Optional absolute project root. Leave unset to use defaultProjectRoot from plugin config.",
|
|
2923
|
+
}),
|
|
2924
|
+
),
|
|
2925
|
+
status: Type.Optional(
|
|
2926
|
+
Type.String({
|
|
2927
|
+
description:
|
|
2928
|
+
"Optional status filter: pending, submitted, completed, cancelled.",
|
|
2929
|
+
}),
|
|
2930
|
+
),
|
|
2931
|
+
limit: Type.Optional(Type.Integer({ minimum: 1 })),
|
|
2932
|
+
}),
|
|
2933
|
+
async execute(
|
|
2934
|
+
_id,
|
|
2935
|
+
params: { project_root?: string; status?: string; limit?: number },
|
|
2936
|
+
) {
|
|
2937
|
+
const projectRoot = requireProjectRoot(params, config);
|
|
2938
|
+
if (!projectRoot) {
|
|
2939
|
+
throw new Error("project_root is required unless defaultProjectRoot is configured");
|
|
2940
|
+
}
|
|
2941
|
+
const result = await callDaemon(config, "list_distillation_jobs", {
|
|
2942
|
+
project_root: projectRoot,
|
|
2943
|
+
status: params.status,
|
|
2944
|
+
limit: params.limit ?? config.defaultSearchLimit ?? 10,
|
|
2945
|
+
});
|
|
2946
|
+
return asText(result);
|
|
2947
|
+
},
|
|
2948
|
+
},
|
|
2949
|
+
{ optional: true },
|
|
2950
|
+
);
|
|
2951
|
+
|
|
2952
|
+
api.registerTool(
|
|
2953
|
+
{
|
|
2954
|
+
name: "opensyn_autonomous_tick",
|
|
2955
|
+
description:
|
|
2956
|
+
"Single polling entry-point for OpenSyn host automation. Returns either the next distillation work packet to process or an idle runtime state when there is no queued work.",
|
|
2957
|
+
parameters: Type.Object({
|
|
2958
|
+
project_root: Type.Optional(
|
|
2959
|
+
Type.String({
|
|
2960
|
+
description:
|
|
2961
|
+
"Optional absolute project root. Leave unset to use defaultProjectRoot from plugin config.",
|
|
2962
|
+
}),
|
|
2963
|
+
),
|
|
2964
|
+
}),
|
|
2965
|
+
async execute(_id, params: { project_root?: string }) {
|
|
2966
|
+
const projectRoot = requireProjectRoot(params, config);
|
|
2967
|
+
if (!projectRoot) {
|
|
2968
|
+
throw new Error("project_root is required unless defaultProjectRoot is configured");
|
|
2969
|
+
}
|
|
2970
|
+
const result = await callDaemon(config, "autonomous_tick", {
|
|
2971
|
+
project_root: projectRoot,
|
|
2972
|
+
claimed_by: "openclaw_plugin",
|
|
2973
|
+
});
|
|
2974
|
+
return asText(result);
|
|
2975
|
+
},
|
|
2976
|
+
},
|
|
2977
|
+
{ optional: true },
|
|
2978
|
+
);
|
|
2979
|
+
|
|
2980
|
+
api.registerTool(
|
|
2981
|
+
{
|
|
2982
|
+
name: "opensyn_next_distillation_job",
|
|
2983
|
+
description:
|
|
2984
|
+
"Fetch the next pending host-model distillation job for the current project so the OpenClaw-configured model can process one job at a time.",
|
|
2985
|
+
parameters: Type.Object({
|
|
2986
|
+
project_root: Type.Optional(
|
|
2987
|
+
Type.String({
|
|
2988
|
+
description:
|
|
2989
|
+
"Optional absolute project root. Leave unset to use defaultProjectRoot from plugin config.",
|
|
2990
|
+
}),
|
|
2991
|
+
),
|
|
2992
|
+
}),
|
|
2993
|
+
async execute(_id, params: { project_root?: string }) {
|
|
2994
|
+
const projectRoot = requireProjectRoot(params, config);
|
|
2995
|
+
if (!projectRoot) {
|
|
2996
|
+
throw new Error("project_root is required unless defaultProjectRoot is configured");
|
|
2997
|
+
}
|
|
2998
|
+
const result = await callDaemon(config, "get_next_distillation_job", {
|
|
2999
|
+
project_root: projectRoot,
|
|
3000
|
+
claimed_by: "openclaw_plugin",
|
|
3001
|
+
});
|
|
3002
|
+
return asText(result);
|
|
3003
|
+
},
|
|
3004
|
+
},
|
|
3005
|
+
{ optional: true },
|
|
3006
|
+
);
|
|
3007
|
+
|
|
3008
|
+
api.registerTool(
|
|
3009
|
+
{
|
|
3010
|
+
name: "opensyn_next_distillation_packet",
|
|
3011
|
+
description:
|
|
3012
|
+
"Claim the next queued host-model distillation task and return a single work packet containing the prompt, output schema, review policy, current runtime overlay, and the completion tool to call next.",
|
|
3013
|
+
parameters: Type.Object({
|
|
3014
|
+
project_root: Type.Optional(
|
|
3015
|
+
Type.String({
|
|
3016
|
+
description:
|
|
3017
|
+
"Optional absolute project root. Leave unset to use defaultProjectRoot from plugin config.",
|
|
3018
|
+
}),
|
|
3019
|
+
),
|
|
3020
|
+
}),
|
|
3021
|
+
async execute(_id, params: { project_root?: string }) {
|
|
3022
|
+
const projectRoot = requireProjectRoot(params, config);
|
|
3023
|
+
if (!projectRoot) {
|
|
3024
|
+
throw new Error("project_root is required unless defaultProjectRoot is configured");
|
|
3025
|
+
}
|
|
3026
|
+
const result = await callDaemon(config, "get_next_distillation_work_packet", {
|
|
3027
|
+
project_root: projectRoot,
|
|
3028
|
+
claimed_by: "openclaw_plugin",
|
|
3029
|
+
});
|
|
3030
|
+
return asText(result);
|
|
3031
|
+
},
|
|
3032
|
+
},
|
|
3033
|
+
{ optional: true },
|
|
3034
|
+
);
|
|
3035
|
+
|
|
3036
|
+
api.registerTool(
|
|
3037
|
+
{
|
|
3038
|
+
name: "opensyn_submit_distillation_job",
|
|
3039
|
+
description:
|
|
3040
|
+
"Submit host-model distilled assets for a queued job. Use only the current OpenClaw-configured model and only the evidence provided in the queued job prompt.",
|
|
3041
|
+
parameters: Type.Object({
|
|
3042
|
+
job_id: Type.String(),
|
|
3043
|
+
assets: Type.Array(
|
|
3044
|
+
Type.Object({
|
|
3045
|
+
kind: Type.String(),
|
|
3046
|
+
title: Type.String(),
|
|
3047
|
+
summary: Type.String(),
|
|
3048
|
+
content: Type.String(),
|
|
3049
|
+
tags: Type.Optional(Type.Array(Type.String())),
|
|
3050
|
+
confidence: Type.Number({ minimum: 0, maximum: 1 }),
|
|
3051
|
+
lineage_key: Type.Optional(Type.String()),
|
|
3052
|
+
projection_targets: Type.Optional(Type.Array(Type.String())),
|
|
3053
|
+
}),
|
|
3054
|
+
),
|
|
3055
|
+
}),
|
|
3056
|
+
async execute(
|
|
3057
|
+
_id,
|
|
3058
|
+
params: {
|
|
3059
|
+
job_id: string;
|
|
3060
|
+
assets: Array<{
|
|
3061
|
+
kind: string;
|
|
3062
|
+
title: string;
|
|
3063
|
+
summary: string;
|
|
3064
|
+
content: string;
|
|
3065
|
+
tags?: string[];
|
|
3066
|
+
confidence: number;
|
|
3067
|
+
lineage_key?: string;
|
|
3068
|
+
projection_targets?: string[];
|
|
3069
|
+
}>;
|
|
3070
|
+
},
|
|
3071
|
+
) {
|
|
3072
|
+
const result = await callDaemon(config, "submit_distillation_job_result", {
|
|
3073
|
+
job_id: params.job_id,
|
|
3074
|
+
assets: params.assets,
|
|
3075
|
+
});
|
|
3076
|
+
return asText(result);
|
|
3077
|
+
},
|
|
3078
|
+
},
|
|
3079
|
+
{ optional: true },
|
|
3080
|
+
);
|
|
3081
|
+
|
|
3082
|
+
api.registerTool(
|
|
3083
|
+
{
|
|
3084
|
+
name: "opensyn_complete_next_distillation_job",
|
|
3085
|
+
description:
|
|
3086
|
+
"Submit distilled assets for the next pending host-model job for the current project, so the agent does not need to carry the job_id manually.",
|
|
3087
|
+
parameters: Type.Object({
|
|
3088
|
+
project_root: Type.Optional(
|
|
3089
|
+
Type.String({
|
|
3090
|
+
description:
|
|
3091
|
+
"Optional absolute project root. Leave unset to use defaultProjectRoot from plugin config.",
|
|
3092
|
+
}),
|
|
3093
|
+
),
|
|
3094
|
+
assets: Type.Array(
|
|
3095
|
+
Type.Object({
|
|
3096
|
+
kind: Type.String(),
|
|
3097
|
+
title: Type.String(),
|
|
3098
|
+
summary: Type.String(),
|
|
3099
|
+
content: Type.String(),
|
|
3100
|
+
tags: Type.Optional(Type.Array(Type.String())),
|
|
3101
|
+
confidence: Type.Number({ minimum: 0, maximum: 1 }),
|
|
3102
|
+
lineage_key: Type.Optional(Type.String()),
|
|
3103
|
+
projection_targets: Type.Optional(Type.Array(Type.String())),
|
|
3104
|
+
}),
|
|
3105
|
+
),
|
|
3106
|
+
}),
|
|
3107
|
+
async execute(
|
|
3108
|
+
_id,
|
|
3109
|
+
params: {
|
|
3110
|
+
project_root?: string;
|
|
3111
|
+
assets: Array<{
|
|
3112
|
+
kind: string;
|
|
3113
|
+
title: string;
|
|
3114
|
+
summary: string;
|
|
3115
|
+
content: string;
|
|
3116
|
+
tags?: string[];
|
|
3117
|
+
confidence: number;
|
|
3118
|
+
lineage_key?: string;
|
|
3119
|
+
projection_targets?: string[];
|
|
3120
|
+
}>;
|
|
3121
|
+
},
|
|
3122
|
+
) {
|
|
3123
|
+
const projectRoot = requireProjectRoot(params, config);
|
|
3124
|
+
if (!projectRoot) {
|
|
3125
|
+
throw new Error("project_root is required unless defaultProjectRoot is configured");
|
|
3126
|
+
}
|
|
3127
|
+
const result = await callDaemon(config, "complete_next_distillation_job", {
|
|
3128
|
+
project_root: projectRoot,
|
|
3129
|
+
claimed_by: "openclaw_plugin",
|
|
3130
|
+
assets: params.assets,
|
|
3131
|
+
});
|
|
3132
|
+
return asText(result);
|
|
3133
|
+
},
|
|
3134
|
+
},
|
|
3135
|
+
{ optional: true },
|
|
3136
|
+
);
|
|
3137
|
+
|
|
3138
|
+
api.registerTool(
|
|
3139
|
+
{
|
|
3140
|
+
name: "opensyn_requeue_distillation_job",
|
|
3141
|
+
description:
|
|
3142
|
+
"Put a claimed host-model distillation job back into the pending queue if the current turn cannot finish it safely.",
|
|
3143
|
+
parameters: Type.Object({
|
|
3144
|
+
job_id: Type.String(),
|
|
3145
|
+
}),
|
|
3146
|
+
async execute(_id, params: { job_id: string }) {
|
|
3147
|
+
const result = await callDaemon(config, "requeue_distillation_job", {
|
|
3148
|
+
job_id: params.job_id,
|
|
3149
|
+
});
|
|
3150
|
+
return asText(result);
|
|
3151
|
+
},
|
|
3152
|
+
},
|
|
3153
|
+
{ optional: true },
|
|
3154
|
+
);
|
|
3155
|
+
|
|
3156
|
+
api.registerTool(
|
|
3157
|
+
{
|
|
3158
|
+
name: "opensyn_set_collection_mode",
|
|
3159
|
+
description:
|
|
3160
|
+
"Set OpenSyn collection-mode settings for the current project. Omit project_root to use defaultProjectRoot from plugin config.",
|
|
3161
|
+
parameters: Type.Object({
|
|
3162
|
+
project_root: Type.Optional(
|
|
3163
|
+
Type.String({
|
|
3164
|
+
description:
|
|
3165
|
+
"Optional absolute project root. Leave unset to use defaultProjectRoot from plugin config.",
|
|
3166
|
+
}),
|
|
3167
|
+
),
|
|
3168
|
+
mode: Type.String({
|
|
3169
|
+
description: "Collection mode: enabled or disabled.",
|
|
3170
|
+
}),
|
|
3171
|
+
auto_distill: Type.Optional(Type.Boolean()),
|
|
3172
|
+
distill_on_flush: Type.Optional(Type.Boolean()),
|
|
3173
|
+
}),
|
|
3174
|
+
async execute(
|
|
3175
|
+
_id,
|
|
3176
|
+
params: {
|
|
3177
|
+
project_root?: string;
|
|
3178
|
+
mode: string;
|
|
3179
|
+
auto_distill?: boolean;
|
|
3180
|
+
distill_on_flush?: boolean;
|
|
3181
|
+
},
|
|
3182
|
+
) {
|
|
3183
|
+
const projectRoot = requireProjectRoot(params, config);
|
|
3184
|
+
if (!projectRoot) {
|
|
3185
|
+
throw new Error("project_root is required unless defaultProjectRoot is configured");
|
|
3186
|
+
}
|
|
3187
|
+
const result = await callDaemon(config, "set_collection_settings", {
|
|
3188
|
+
project_root: projectRoot,
|
|
3189
|
+
mode: params.mode,
|
|
3190
|
+
auto_distill: params.auto_distill,
|
|
3191
|
+
distill_on_flush: params.distill_on_flush,
|
|
3192
|
+
updated_by: "openclaw_plugin",
|
|
3193
|
+
});
|
|
3194
|
+
return asText(result);
|
|
3195
|
+
},
|
|
3196
|
+
},
|
|
3197
|
+
{ optional: true },
|
|
3198
|
+
);
|
|
3199
|
+
|
|
3200
|
+
api.registerTool(
|
|
3201
|
+
{
|
|
3202
|
+
name: "opensyn_approve_asset",
|
|
3203
|
+
description:
|
|
3204
|
+
"Approve an OpenSyn agent asset candidate. If projection mode is configured with auto_apply_approved, the asset will also be synced immediately.",
|
|
3205
|
+
parameters: Type.Object({
|
|
3206
|
+
asset_id: Type.String(),
|
|
3207
|
+
}),
|
|
3208
|
+
async execute(_id, params: { asset_id: string }) {
|
|
3209
|
+
const result = await callDaemon(config, "approve_agent_asset", {
|
|
3210
|
+
asset_id: params.asset_id,
|
|
3211
|
+
});
|
|
3212
|
+
return asText(result);
|
|
3213
|
+
},
|
|
3214
|
+
},
|
|
3215
|
+
{ optional: true },
|
|
3216
|
+
);
|
|
3217
|
+
|
|
3218
|
+
api.registerTool(
|
|
3219
|
+
{
|
|
3220
|
+
name: "opensyn_get_projection_mode",
|
|
3221
|
+
description:
|
|
3222
|
+
"Read OpenSyn projection-mode settings for the current project. Omit project_root to use defaultProjectRoot from plugin config.",
|
|
3223
|
+
parameters: Type.Object({
|
|
3224
|
+
project_root: Type.Optional(
|
|
3225
|
+
Type.String({
|
|
3226
|
+
description:
|
|
3227
|
+
"Optional absolute project root. Leave unset to use defaultProjectRoot from plugin config.",
|
|
3228
|
+
}),
|
|
3229
|
+
),
|
|
3230
|
+
}),
|
|
3231
|
+
async execute(_id, params: { project_root?: string }) {
|
|
3232
|
+
const projectRoot = requireProjectRoot(params, config);
|
|
3233
|
+
if (!projectRoot) {
|
|
3234
|
+
throw new Error("project_root is required unless defaultProjectRoot is configured");
|
|
3235
|
+
}
|
|
3236
|
+
const result = await callDaemon(config, "get_projection_settings", {
|
|
3237
|
+
project_root: projectRoot,
|
|
3238
|
+
});
|
|
3239
|
+
return asText(result);
|
|
3240
|
+
},
|
|
3241
|
+
},
|
|
3242
|
+
{ optional: true },
|
|
3243
|
+
);
|
|
3244
|
+
|
|
3245
|
+
api.registerTool(
|
|
3246
|
+
{
|
|
3247
|
+
name: "opensyn_set_projection_mode",
|
|
3248
|
+
description:
|
|
3249
|
+
"Set OpenSyn projection-mode settings for the current project. Omit project_root to use defaultProjectRoot from plugin config.",
|
|
3250
|
+
parameters: Type.Object({
|
|
3251
|
+
project_root: Type.Optional(
|
|
3252
|
+
Type.String({
|
|
3253
|
+
description:
|
|
3254
|
+
"Optional absolute project root. Leave unset to use defaultProjectRoot from plugin config.",
|
|
3255
|
+
}),
|
|
3256
|
+
),
|
|
3257
|
+
enabled: Type.Optional(Type.Boolean()),
|
|
3258
|
+
target_root: Type.Optional(
|
|
3259
|
+
Type.String({
|
|
3260
|
+
description:
|
|
3261
|
+
"Optional projection target root. Leave unset to use projectionBundleRoot from plugin config.",
|
|
3262
|
+
}),
|
|
3263
|
+
),
|
|
3264
|
+
host: Type.Optional(
|
|
3265
|
+
Type.String({
|
|
3266
|
+
description:
|
|
3267
|
+
"Optional host type: openclaw, claude_code, or generic. Leave unset to use projectionHost from plugin config.",
|
|
3268
|
+
}),
|
|
3269
|
+
),
|
|
3270
|
+
auto_apply_approved: Type.Optional(Type.Boolean()),
|
|
3271
|
+
}),
|
|
3272
|
+
async execute(
|
|
3273
|
+
_id,
|
|
3274
|
+
params: {
|
|
3275
|
+
project_root?: string;
|
|
3276
|
+
enabled?: boolean;
|
|
3277
|
+
target_root?: string;
|
|
3278
|
+
host?: string;
|
|
3279
|
+
auto_apply_approved?: boolean;
|
|
3280
|
+
},
|
|
3281
|
+
) {
|
|
3282
|
+
const projectRoot = requireProjectRoot(params, config);
|
|
3283
|
+
if (!projectRoot) {
|
|
3284
|
+
throw new Error("project_root is required unless defaultProjectRoot is configured");
|
|
3285
|
+
}
|
|
3286
|
+
const targetRoot = params.target_root || config.projectionBundleRoot;
|
|
3287
|
+
const effectiveTargetRoot = targetRoot || defaultProjectionRoot(config);
|
|
3288
|
+
if (!effectiveTargetRoot) {
|
|
3289
|
+
throw new Error("target_root is required unless projectionBundleRoot is configured or the OpenClaw workspace default is available");
|
|
3290
|
+
}
|
|
3291
|
+
const result = await callDaemon(config, "set_projection_settings", {
|
|
3292
|
+
project_root: projectRoot,
|
|
3293
|
+
enabled: params.enabled ?? true,
|
|
3294
|
+
target_root: effectiveTargetRoot,
|
|
3295
|
+
host: params.host || config.projectionHost || "openclaw",
|
|
3296
|
+
auto_apply_approved:
|
|
3297
|
+
params.auto_apply_approved ?? config.projectionAutoApplyApproved ?? false,
|
|
3298
|
+
updated_by: "openclaw_plugin",
|
|
3299
|
+
});
|
|
3300
|
+
return asText(result);
|
|
3301
|
+
},
|
|
3302
|
+
},
|
|
3303
|
+
{ optional: true },
|
|
3304
|
+
);
|
|
3305
|
+
|
|
3306
|
+
api.registerTool(
|
|
3307
|
+
{
|
|
3308
|
+
name: "opensyn_agent_runtime_overlay",
|
|
3309
|
+
description:
|
|
3310
|
+
"Return the aggregated OpenClaw runtime overlay built from approved assets. This is the closest current view of what OpenClaw should consume as memory, rules, soul, and skills.",
|
|
3311
|
+
parameters: Type.Object({
|
|
3312
|
+
project_root: Type.Optional(
|
|
3313
|
+
Type.String({
|
|
3314
|
+
description:
|
|
3315
|
+
"Optional absolute project root. Leave unset to use defaultProjectRoot from plugin config.",
|
|
3316
|
+
}),
|
|
3317
|
+
),
|
|
3318
|
+
host: Type.Optional(
|
|
3319
|
+
Type.String({
|
|
3320
|
+
description:
|
|
3321
|
+
"Optional host override: openclaw, claude_code, or generic. Leave unset to use projectionHost or the stored projection settings.",
|
|
3322
|
+
}),
|
|
3323
|
+
),
|
|
3324
|
+
}),
|
|
3325
|
+
async execute(_id, params: { project_root?: string; host?: string }) {
|
|
3326
|
+
const projectRoot = requireProjectRoot(params, config);
|
|
3327
|
+
if (!projectRoot) {
|
|
3328
|
+
throw new Error("project_root is required unless defaultProjectRoot is configured");
|
|
3329
|
+
}
|
|
3330
|
+
const result = await callDaemon(config, "get_agent_runtime_overlay", {
|
|
3331
|
+
project_root: projectRoot,
|
|
3332
|
+
host: params.host || config.projectionHost || "openclaw",
|
|
3333
|
+
});
|
|
3334
|
+
return asText(result);
|
|
3335
|
+
},
|
|
3336
|
+
},
|
|
3337
|
+
{ optional: true },
|
|
3338
|
+
);
|
|
3339
|
+
|
|
3340
|
+
api.registerTool(
|
|
3341
|
+
{
|
|
3342
|
+
name: "opensyn_reject_asset",
|
|
3343
|
+
description:
|
|
3344
|
+
"Reject an OpenSyn agent asset candidate so it will not be projected.",
|
|
3345
|
+
parameters: Type.Object({
|
|
3346
|
+
asset_id: Type.String(),
|
|
3347
|
+
}),
|
|
3348
|
+
async execute(_id, params: { asset_id: string }) {
|
|
3349
|
+
const result = await callDaemon(config, "reject_agent_asset", {
|
|
3350
|
+
asset_id: params.asset_id,
|
|
3351
|
+
});
|
|
3352
|
+
return asText(result);
|
|
3353
|
+
},
|
|
3354
|
+
},
|
|
3355
|
+
{ optional: true },
|
|
3356
|
+
);
|
|
3357
|
+
|
|
3358
|
+
api.registerTool(
|
|
3359
|
+
{
|
|
3360
|
+
name: "opensyn_rollback_asset_revision",
|
|
3361
|
+
description:
|
|
3362
|
+
"Restore the current asset from one recorded revision of the same asset lineage.",
|
|
3363
|
+
parameters: Type.Object({
|
|
3364
|
+
asset_id: Type.String(),
|
|
3365
|
+
revision: Type.Integer({ minimum: 1 }),
|
|
3366
|
+
}),
|
|
3367
|
+
async execute(_id, params: { asset_id: string; revision: number }) {
|
|
3368
|
+
const result = await callDaemon(config, "rollback_agent_asset_revision", {
|
|
3369
|
+
asset_id: params.asset_id,
|
|
3370
|
+
revision: params.revision,
|
|
3371
|
+
});
|
|
3372
|
+
return asText(result);
|
|
3373
|
+
},
|
|
3374
|
+
},
|
|
3375
|
+
{ optional: true },
|
|
3376
|
+
);
|
|
3377
|
+
|
|
3378
|
+
api.registerTool(
|
|
3379
|
+
{
|
|
3380
|
+
name: "opensyn_project_asset",
|
|
3381
|
+
description:
|
|
3382
|
+
"Build projection previews for an approved asset so OpenClaw can inspect what would be written back into the host.",
|
|
3383
|
+
parameters: Type.Object({
|
|
3384
|
+
asset_id: Type.String(),
|
|
3385
|
+
}),
|
|
3386
|
+
async execute(_id, params: { asset_id: string }) {
|
|
3387
|
+
const result = await callDaemon(config, "project_agent_asset", {
|
|
3388
|
+
asset_id: params.asset_id,
|
|
3389
|
+
});
|
|
3390
|
+
return asText(result);
|
|
3391
|
+
},
|
|
3392
|
+
},
|
|
3393
|
+
{ optional: true },
|
|
3394
|
+
);
|
|
3395
|
+
|
|
3396
|
+
api.registerTool(
|
|
3397
|
+
{
|
|
3398
|
+
name: "opensyn_list_projections",
|
|
3399
|
+
description:
|
|
3400
|
+
"List recent projection records for the current project or for one asset. Omit project_root to use defaultProjectRoot from plugin config.",
|
|
3401
|
+
parameters: Type.Object({
|
|
3402
|
+
project_root: Type.Optional(
|
|
3403
|
+
Type.String({
|
|
3404
|
+
description:
|
|
3405
|
+
"Optional absolute project root. Leave unset to use defaultProjectRoot from plugin config.",
|
|
3406
|
+
}),
|
|
3407
|
+
),
|
|
3408
|
+
asset_id: Type.Optional(Type.String()),
|
|
3409
|
+
limit: Type.Optional(Type.Integer({ minimum: 1 })),
|
|
3410
|
+
}),
|
|
3411
|
+
async execute(
|
|
3412
|
+
_id,
|
|
3413
|
+
params: { project_root?: string; asset_id?: string; limit?: number },
|
|
3414
|
+
) {
|
|
3415
|
+
const projectRoot = requireProjectRoot(params, config);
|
|
3416
|
+
if (!projectRoot) {
|
|
3417
|
+
throw new Error("project_root is required unless defaultProjectRoot is configured");
|
|
3418
|
+
}
|
|
3419
|
+
const result = await callDaemon(config, "list_projection_records", {
|
|
3420
|
+
project_root: projectRoot,
|
|
3421
|
+
asset_id: params.asset_id,
|
|
3422
|
+
limit: params.limit ?? config.defaultSearchLimit ?? 10,
|
|
3423
|
+
});
|
|
3424
|
+
return asText(result);
|
|
3425
|
+
},
|
|
3426
|
+
},
|
|
3427
|
+
{ optional: true },
|
|
3428
|
+
);
|
|
3429
|
+
|
|
3430
|
+
api.registerTool(
|
|
3431
|
+
{
|
|
3432
|
+
name: "opensyn_apply_asset_projection",
|
|
3433
|
+
description:
|
|
3434
|
+
"Apply one approved asset using stored projection settings or the configured target root. In OpenClaw host mode this updates managed sections in the OpenClaw workspace; other hosts still use bundle-style output.",
|
|
3435
|
+
parameters: Type.Object({
|
|
3436
|
+
asset_id: Type.String(),
|
|
3437
|
+
bundle_root: Type.Optional(
|
|
3438
|
+
Type.String({
|
|
3439
|
+
description:
|
|
3440
|
+
"Optional projection bundle root. Leave unset to use projectionBundleRoot from plugin config.",
|
|
3441
|
+
}),
|
|
3442
|
+
),
|
|
3443
|
+
}),
|
|
3444
|
+
async execute(
|
|
3445
|
+
_id,
|
|
3446
|
+
params: { asset_id: string; bundle_root?: string },
|
|
3447
|
+
) {
|
|
3448
|
+
const bundleRoot = params.bundle_root || defaultProjectionRoot(config);
|
|
3449
|
+
if (!bundleRoot) {
|
|
3450
|
+
throw new Error("bundle_root is required unless projectionBundleRoot is configured or the OpenClaw workspace default is available");
|
|
3451
|
+
}
|
|
3452
|
+
const result = await callDaemon(config, "apply_asset_projection_bundle", {
|
|
3453
|
+
asset_id: params.asset_id,
|
|
3454
|
+
bundle_root: bundleRoot,
|
|
3455
|
+
host: config.projectionHost || "openclaw",
|
|
3456
|
+
});
|
|
3457
|
+
return asText(result);
|
|
3458
|
+
},
|
|
3459
|
+
},
|
|
3460
|
+
{ optional: true },
|
|
3461
|
+
);
|
|
3462
|
+
|
|
3463
|
+
api.registerTool(
|
|
3464
|
+
{
|
|
3465
|
+
name: "opensyn_sync_assets",
|
|
3466
|
+
description:
|
|
3467
|
+
"Apply all approved assets for the current project using stored projection settings. In OpenClaw host mode this syncs the managed workspace overlay. Omit project_root to use defaultProjectRoot from plugin config.",
|
|
3468
|
+
parameters: Type.Object({
|
|
3469
|
+
project_root: Type.Optional(
|
|
3470
|
+
Type.String({
|
|
3471
|
+
description:
|
|
3472
|
+
"Optional absolute project root. Leave unset to use defaultProjectRoot from plugin config.",
|
|
3473
|
+
}),
|
|
3474
|
+
),
|
|
3475
|
+
target_root: Type.Optional(
|
|
3476
|
+
Type.String({
|
|
3477
|
+
description:
|
|
3478
|
+
"Optional target root override. Leave unset to use stored projection settings or projectionBundleRoot from plugin config.",
|
|
3479
|
+
}),
|
|
3480
|
+
),
|
|
3481
|
+
host: Type.Optional(
|
|
3482
|
+
Type.String({
|
|
3483
|
+
description:
|
|
3484
|
+
"Optional host override. Leave unset to use stored projection settings or projectionHost from plugin config.",
|
|
3485
|
+
}),
|
|
3486
|
+
),
|
|
3487
|
+
}),
|
|
3488
|
+
async execute(
|
|
3489
|
+
_id,
|
|
3490
|
+
params: { project_root?: string; target_root?: string; host?: string },
|
|
3491
|
+
) {
|
|
3492
|
+
const projectRoot = requireProjectRoot(params, config);
|
|
3493
|
+
if (!projectRoot) {
|
|
3494
|
+
throw new Error("project_root is required unless defaultProjectRoot is configured");
|
|
3495
|
+
}
|
|
3496
|
+
const result = await callDaemon(config, "sync_approved_assets", {
|
|
3497
|
+
project_root: projectRoot,
|
|
3498
|
+
target_root: params.target_root || defaultProjectionRoot(config),
|
|
3499
|
+
host: params.host || config.projectionHost,
|
|
3500
|
+
});
|
|
3501
|
+
return asText(result);
|
|
3502
|
+
},
|
|
3503
|
+
},
|
|
3504
|
+
{ optional: true },
|
|
3505
|
+
);
|
|
3506
|
+
},
|
|
3507
|
+
};
|