opensyn 0.1.3 → 0.1.5
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 +18 -1
- package/README.zh-CN.md +18 -1
- package/index.ts +840 -82
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/runtime/opensyn-cli +7 -0
- package/runtime/opensyn-daemon +7 -0
- package/runtime/opensyn-host-distiller.mjs +24 -138
- package/runtime/postinstall.mjs +1 -9
package/index.ts
CHANGED
|
@@ -10,7 +10,6 @@ import {
|
|
|
10
10
|
readFileSync,
|
|
11
11
|
readdirSync,
|
|
12
12
|
renameSync,
|
|
13
|
-
realpathSync,
|
|
14
13
|
rmSync,
|
|
15
14
|
statSync,
|
|
16
15
|
writeFileSync,
|
|
@@ -19,7 +18,7 @@ import os from "node:os";
|
|
|
19
18
|
import path from "node:path";
|
|
20
19
|
import { fileURLToPath } from "node:url";
|
|
21
20
|
import { Type } from "@sinclair/typebox";
|
|
22
|
-
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
|
|
21
|
+
import type { OpenClawPluginApi, OpenClawPluginServiceContext } from "openclaw/plugin-sdk";
|
|
23
22
|
|
|
24
23
|
type ToolContent = { type: "text"; text: string };
|
|
25
24
|
|
|
@@ -122,6 +121,14 @@ type DistillationSettingsLike = {
|
|
|
122
121
|
semantic_hint?: SemanticHint;
|
|
123
122
|
};
|
|
124
123
|
|
|
124
|
+
type AuditStatsLike = {
|
|
125
|
+
raw_event_count?: number;
|
|
126
|
+
context_pack_count?: number;
|
|
127
|
+
task_episode_count?: number;
|
|
128
|
+
agent_asset_count?: number;
|
|
129
|
+
projects?: string[];
|
|
130
|
+
};
|
|
131
|
+
|
|
125
132
|
type DistillationJobLike = {
|
|
126
133
|
id?: string;
|
|
127
134
|
project_root?: string;
|
|
@@ -264,7 +271,7 @@ type DistillationWorkerStatus = {
|
|
|
264
271
|
enabled: boolean;
|
|
265
272
|
running: boolean;
|
|
266
273
|
pid?: number;
|
|
267
|
-
source: "openclaw_host_model_agent";
|
|
274
|
+
source: "openclaw_host_model_agent" | "openclaw_host_model_service";
|
|
268
275
|
project_root: string;
|
|
269
276
|
state_path: string;
|
|
270
277
|
log_path: string;
|
|
@@ -411,37 +418,20 @@ function ensureProjectRoot(projectRoot: string): void {
|
|
|
411
418
|
}
|
|
412
419
|
|
|
413
420
|
function defaultDbPath(): string {
|
|
414
|
-
const home =
|
|
421
|
+
const home = os.homedir();
|
|
415
422
|
return path.join(home, ".opensyn", "opensyn.db");
|
|
416
423
|
}
|
|
417
424
|
|
|
418
425
|
function defaultRuntimeRoot(): string {
|
|
419
|
-
const home =
|
|
426
|
+
const home = os.homedir();
|
|
420
427
|
return path.join(home, ".opensyn");
|
|
421
428
|
}
|
|
422
429
|
|
|
423
430
|
function defaultShell(): string {
|
|
424
|
-
return
|
|
431
|
+
return "bash";
|
|
425
432
|
}
|
|
426
433
|
|
|
427
434
|
function defaultOpenClawBin(): string {
|
|
428
|
-
if (process.env.OPENCLAW_BIN) {
|
|
429
|
-
return process.env.OPENCLAW_BIN;
|
|
430
|
-
}
|
|
431
|
-
const pathEnv = process.env.PATH || "";
|
|
432
|
-
for (const entry of pathEnv.split(path.delimiter)) {
|
|
433
|
-
if (!entry) {
|
|
434
|
-
continue;
|
|
435
|
-
}
|
|
436
|
-
const candidate = path.join(entry, "openclaw");
|
|
437
|
-
if (existsSync(candidate)) {
|
|
438
|
-
try {
|
|
439
|
-
return realpathSync(candidate);
|
|
440
|
-
} catch {
|
|
441
|
-
return candidate;
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
}
|
|
445
435
|
return "openclaw";
|
|
446
436
|
}
|
|
447
437
|
|
|
@@ -453,7 +443,7 @@ function stableNodeRunner(): string {
|
|
|
453
443
|
}
|
|
454
444
|
|
|
455
445
|
function defaultProjectionBundleRoot(): string {
|
|
456
|
-
const home =
|
|
446
|
+
const home = os.homedir();
|
|
457
447
|
return path.join(home, ".openclaw", "workspace");
|
|
458
448
|
}
|
|
459
449
|
|
|
@@ -505,7 +495,6 @@ function resolveOpenSynConfig(
|
|
|
505
495
|
const managedRuntimeDir = ensureRuntimeRootAssets(runtimeRoot);
|
|
506
496
|
const daemonBin = resolvePreferredPath([
|
|
507
497
|
config.daemonBin,
|
|
508
|
-
process.env.OPENSYN_DAEMON_BIN,
|
|
509
498
|
path.join(managedRuntimeDir, "opensyn-daemon"),
|
|
510
499
|
packageRuntimePath("opensyn-daemon"),
|
|
511
500
|
repoFallbackPath("target", "debug", "synapse-daemon"),
|
|
@@ -514,7 +503,6 @@ function resolveOpenSynConfig(
|
|
|
514
503
|
|
|
515
504
|
const cliBin = resolvePreferredPath([
|
|
516
505
|
config.cliBin,
|
|
517
|
-
process.env.OPENSYN_CLI_BIN,
|
|
518
506
|
path.join(managedRuntimeDir, "opensyn-cli"),
|
|
519
507
|
packageRuntimePath("opensyn-cli"),
|
|
520
508
|
repoFallbackPath("target", "debug", "synapse-cli"),
|
|
@@ -588,6 +576,37 @@ function distillationWorkerLogPath(
|
|
|
588
576
|
return path.join(backgroundWatchDir(config, projectRoot), "host-distiller.log");
|
|
589
577
|
}
|
|
590
578
|
|
|
579
|
+
function appendLogLine(logPath: string, message: string): void {
|
|
580
|
+
mkdirSync(path.dirname(logPath), { recursive: true });
|
|
581
|
+
writeFileSync(logPath, `${message.trimEnd()}\n`, { encoding: "utf8", flag: "a" });
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
function updateJsonStateFile(statePath: string, payload: Record<string, unknown>): void {
|
|
585
|
+
const previous = readJsonFile<Record<string, unknown>>(statePath) ?? {};
|
|
586
|
+
mkdirSync(path.dirname(statePath), { recursive: true });
|
|
587
|
+
writeFileSync(
|
|
588
|
+
statePath,
|
|
589
|
+
`${JSON.stringify({ ...previous, ...payload }, null, 2)}\n`,
|
|
590
|
+
"utf8",
|
|
591
|
+
);
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
function parseIsoTimestamp(value: unknown): number | null {
|
|
595
|
+
if (typeof value !== "string" || !value.trim()) {
|
|
596
|
+
return null;
|
|
597
|
+
}
|
|
598
|
+
const parsed = Date.parse(value);
|
|
599
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
function isRecentIsoTimestamp(value: unknown, maxAgeMs: number): boolean {
|
|
603
|
+
const parsed = parseIsoTimestamp(value);
|
|
604
|
+
if (parsed === null) {
|
|
605
|
+
return false;
|
|
606
|
+
}
|
|
607
|
+
return Date.now() - parsed <= maxAgeMs;
|
|
608
|
+
}
|
|
609
|
+
|
|
591
610
|
function isPidAlive(pid?: number): boolean {
|
|
592
611
|
if (!pid || pid <= 0) {
|
|
593
612
|
return false;
|
|
@@ -709,23 +728,45 @@ function getDistillationWorkerStatus(
|
|
|
709
728
|
const logPath = distillationWorkerLogPath(config, projectRoot);
|
|
710
729
|
const state = readJsonFile<{
|
|
711
730
|
pid?: number;
|
|
731
|
+
manager_pid?: number;
|
|
732
|
+
runtime?: string;
|
|
733
|
+
service_heartbeat_at?: string;
|
|
734
|
+
interval_secs?: number;
|
|
712
735
|
last_exit_code?: number | null;
|
|
713
736
|
last_error?: string;
|
|
714
737
|
last_finished_at?: string;
|
|
715
738
|
}>(statePath);
|
|
716
|
-
const
|
|
739
|
+
const runtime =
|
|
740
|
+
state?.runtime === "openclaw_plugin_service"
|
|
741
|
+
? state.runtime
|
|
742
|
+
: state?.runtime === "legacy" || typeof state?.pid === "number"
|
|
743
|
+
? "legacy"
|
|
744
|
+
: "openclaw_plugin_service";
|
|
745
|
+
const pid = runtime === "openclaw_plugin_service" ? state?.manager_pid : state?.pid;
|
|
746
|
+
const intervalSecs =
|
|
747
|
+
typeof state?.interval_secs === "number" && Number.isFinite(state.interval_secs)
|
|
748
|
+
? Math.max(5, Math.trunc(state.interval_secs))
|
|
749
|
+
: config.distillationWorkerIntervalSecs;
|
|
750
|
+
const serviceHealthy =
|
|
751
|
+
runtime === "openclaw_plugin_service" &&
|
|
752
|
+
isRecentIsoTimestamp(state?.service_heartbeat_at, Math.max(30_000, intervalSecs * 3_000));
|
|
717
753
|
return {
|
|
718
754
|
supported: true,
|
|
719
755
|
enabled: config.distillationWorkerEnabled,
|
|
720
|
-
running:
|
|
756
|
+
running:
|
|
757
|
+
config.distillationWorkerEnabled &&
|
|
758
|
+
(runtime === "openclaw_plugin_service" ? serviceHealthy : isPidAlive(pid)),
|
|
721
759
|
pid,
|
|
722
|
-
source:
|
|
760
|
+
source:
|
|
761
|
+
runtime === "openclaw_plugin_service"
|
|
762
|
+
? "openclaw_host_model_service"
|
|
763
|
+
: "openclaw_host_model_agent",
|
|
723
764
|
project_root: projectRoot,
|
|
724
765
|
state_path: statePath,
|
|
725
766
|
log_path: logPath,
|
|
726
767
|
openclaw_bin: config.openclawBin,
|
|
727
768
|
agent_id: config.distillationWorkerAgentId,
|
|
728
|
-
interval_secs:
|
|
769
|
+
interval_secs: intervalSecs,
|
|
729
770
|
last_exit_code: state?.last_exit_code,
|
|
730
771
|
last_error: state?.last_error,
|
|
731
772
|
last_finished_at: state?.last_finished_at,
|
|
@@ -733,6 +774,25 @@ function getDistillationWorkerStatus(
|
|
|
733
774
|
};
|
|
734
775
|
}
|
|
735
776
|
|
|
777
|
+
function stopLegacyDistillationWorker(
|
|
778
|
+
config: ResolvedOpenSynConfig,
|
|
779
|
+
projectRoot: string,
|
|
780
|
+
): void {
|
|
781
|
+
const statePath = distillationWorkerStatePath(config, projectRoot);
|
|
782
|
+
const state = readJsonFile<{ pid?: number; runtime?: string }>(statePath);
|
|
783
|
+
if (!state || state.runtime === "openclaw_plugin_service") {
|
|
784
|
+
return;
|
|
785
|
+
}
|
|
786
|
+
const pid = state.pid;
|
|
787
|
+
if (pid && isPidAlive(pid)) {
|
|
788
|
+
try {
|
|
789
|
+
process.kill(pid, "SIGTERM");
|
|
790
|
+
} catch {
|
|
791
|
+
// Ignore pid races; service mode will overwrite state below.
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
|
|
736
796
|
function summarizeDistillationWorkerError(error: string): string {
|
|
737
797
|
const trimmed = error.trim();
|
|
738
798
|
if (!trimmed) {
|
|
@@ -790,8 +850,14 @@ function buildDistillationWorkerHealth(
|
|
|
790
850
|
|
|
791
851
|
return {
|
|
792
852
|
status: "stopped",
|
|
793
|
-
summary:
|
|
794
|
-
|
|
853
|
+
summary:
|
|
854
|
+
status.source === "openclaw_host_model_service"
|
|
855
|
+
? "The plugin-managed host-model distillation service is not currently reporting healthy heartbeats."
|
|
856
|
+
: "The plugin-managed host-model distillation worker is not currently running.",
|
|
857
|
+
action:
|
|
858
|
+
status.source === "openclaw_host_model_service"
|
|
859
|
+
? "Restart the OpenClaw gateway if needed, then run openclaw repair_opensyn to refresh OpenSyn state."
|
|
860
|
+
: "Run openclaw repair_opensyn to restart the worker.",
|
|
795
861
|
log_path: status.log_path,
|
|
796
862
|
last_finished_at: status.last_finished_at,
|
|
797
863
|
};
|
|
@@ -906,6 +972,7 @@ function buildUserNextStep(params: {
|
|
|
906
972
|
status: "required",
|
|
907
973
|
code: "repair_distillation_worker",
|
|
908
974
|
summary:
|
|
975
|
+
distillationWorkerHealth.summary ||
|
|
909
976
|
"The plugin-managed host-model distillation worker is not healthy, so OpenSyn cannot keep refining new activity automatically.",
|
|
910
977
|
action:
|
|
911
978
|
distillationWorkerHealth.action ||
|
|
@@ -930,63 +997,350 @@ function buildUserNextStep(params: {
|
|
|
930
997
|
};
|
|
931
998
|
}
|
|
932
999
|
|
|
933
|
-
|
|
1000
|
+
function buildHostDistillationPrompt(projectRoot: string): string {
|
|
1001
|
+
return [
|
|
1002
|
+
`Run exactly one OpenSyn autonomous distillation cycle for project ${projectRoot}.`,
|
|
1003
|
+
"Rules:",
|
|
1004
|
+
"- Use only the OpenClaw-configured host model already available to this local runtime.",
|
|
1005
|
+
"- Do not browse, do not call external network tools, and do not use any model outside OpenClaw.",
|
|
1006
|
+
"- First call opensyn_autonomous_tick with no arguments.",
|
|
1007
|
+
"- If automation_state is idle_waiting_for_activity, stop immediately.",
|
|
1008
|
+
"- If automation_state is distillation_work_ready, follow cycle_plan.primary_steps in order.",
|
|
1009
|
+
"- If the primary path cannot complete safely, follow cycle_plan.fallback_steps.",
|
|
1010
|
+
"- Always execute cycle_plan.resume_steps before ending when they are available.",
|
|
1011
|
+
"- Complete at most one queued work packet in this run.",
|
|
1012
|
+
"- End with one short sentence describing the outcome.",
|
|
1013
|
+
].join("\n");
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
function resolveOpenClawCommand(
|
|
934
1017
|
config: ResolvedOpenSynConfig,
|
|
935
|
-
|
|
936
|
-
):
|
|
937
|
-
const
|
|
938
|
-
if (
|
|
939
|
-
return
|
|
1018
|
+
args: string[],
|
|
1019
|
+
): { command: string; commandArgs: string[] } {
|
|
1020
|
+
const openclawBin = config.openclawBin;
|
|
1021
|
+
if (openclawBin.endsWith(".mjs") || openclawBin.endsWith(".js")) {
|
|
1022
|
+
return {
|
|
1023
|
+
command: stableNodeRunner(),
|
|
1024
|
+
commandArgs: [openclawBin, ...args],
|
|
1025
|
+
};
|
|
940
1026
|
}
|
|
1027
|
+
return {
|
|
1028
|
+
command: openclawBin,
|
|
1029
|
+
commandArgs: args,
|
|
1030
|
+
};
|
|
1031
|
+
}
|
|
941
1032
|
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
"
|
|
953
|
-
|
|
954
|
-
"--interval-secs",
|
|
955
|
-
String(config.distillationWorkerIntervalSecs),
|
|
956
|
-
"--project-root",
|
|
957
|
-
projectRoot,
|
|
958
|
-
"--state-path",
|
|
959
|
-
statePath,
|
|
960
|
-
],
|
|
961
|
-
{
|
|
962
|
-
detached: true,
|
|
963
|
-
stdio: ["ignore", logFd, logFd],
|
|
964
|
-
env: process.env,
|
|
965
|
-
},
|
|
966
|
-
);
|
|
967
|
-
child.unref();
|
|
968
|
-
closeSync(logFd);
|
|
1033
|
+
async function runSpawnedCommand(params: {
|
|
1034
|
+
command: string;
|
|
1035
|
+
args: string[];
|
|
1036
|
+
cwd?: string;
|
|
1037
|
+
timeoutMs: number;
|
|
1038
|
+
onSpawn?: (pid?: number) => void;
|
|
1039
|
+
}): Promise<SpawnResponse> {
|
|
1040
|
+
return new Promise((resolve, reject) => {
|
|
1041
|
+
const child = spawn(params.command, params.args, {
|
|
1042
|
+
cwd: params.cwd,
|
|
1043
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
1044
|
+
});
|
|
969
1045
|
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
1046
|
+
params.onSpawn?.(child.pid);
|
|
1047
|
+
|
|
1048
|
+
let stdout = "";
|
|
1049
|
+
let stderr = "";
|
|
1050
|
+
let settled = false;
|
|
1051
|
+
const timeout = setTimeout(() => {
|
|
1052
|
+
if (settled) {
|
|
1053
|
+
return;
|
|
1054
|
+
}
|
|
1055
|
+
settled = true;
|
|
1056
|
+
child.kill("SIGTERM");
|
|
1057
|
+
reject(new Error(`${path.basename(params.command)} timed out after ${params.timeoutMs}ms`));
|
|
1058
|
+
}, params.timeoutMs);
|
|
1059
|
+
|
|
1060
|
+
function finish(fn: () => void) {
|
|
1061
|
+
if (settled) {
|
|
1062
|
+
return;
|
|
1063
|
+
}
|
|
1064
|
+
settled = true;
|
|
1065
|
+
clearTimeout(timeout);
|
|
1066
|
+
fn();
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
child.stdout.on("data", (chunk) => {
|
|
1070
|
+
stdout += chunk.toString();
|
|
1071
|
+
});
|
|
1072
|
+
child.stderr.on("data", (chunk) => {
|
|
1073
|
+
stderr += chunk.toString();
|
|
1074
|
+
});
|
|
1075
|
+
child.on("error", (error) => {
|
|
1076
|
+
finish(() => reject(error));
|
|
1077
|
+
});
|
|
1078
|
+
child.on("close", (code) => {
|
|
1079
|
+
finish(() =>
|
|
1080
|
+
resolve({
|
|
1081
|
+
stdout,
|
|
1082
|
+
stderr,
|
|
1083
|
+
exitCode: code,
|
|
1084
|
+
}),
|
|
1085
|
+
);
|
|
1086
|
+
});
|
|
1087
|
+
});
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
function deriveDistillationProjectRoot(
|
|
1091
|
+
config: ResolvedOpenSynConfig,
|
|
1092
|
+
ctx: OpenClawPluginServiceContext,
|
|
1093
|
+
): string | null {
|
|
1094
|
+
const projectRoot = config.defaultProjectRoot || ctx.workspaceDir;
|
|
1095
|
+
return projectRoot ? path.resolve(projectRoot) : null;
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
function createOpenSynDistillationService(api: OpenClawPluginApi) {
|
|
1099
|
+
let timer: NodeJS.Timeout | null = null;
|
|
1100
|
+
let currentChildPid: number | undefined;
|
|
1101
|
+
let runningCycle = false;
|
|
1102
|
+
let lastCycleStartedAt = 0;
|
|
1103
|
+
let lifecycleRevision = 0;
|
|
1104
|
+
|
|
1105
|
+
async function runCycle(
|
|
1106
|
+
ctx: OpenClawPluginServiceContext,
|
|
1107
|
+
config: ResolvedOpenSynConfig,
|
|
1108
|
+
projectRoot: string,
|
|
1109
|
+
): Promise<void> {
|
|
1110
|
+
if (runningCycle) {
|
|
1111
|
+
return;
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
runningCycle = true;
|
|
1115
|
+
const statePath = distillationWorkerStatePath(config, projectRoot);
|
|
1116
|
+
const logPath = distillationWorkerLogPath(config, projectRoot);
|
|
1117
|
+
const startedAt = new Date().toISOString();
|
|
1118
|
+
|
|
1119
|
+
try {
|
|
1120
|
+
stopLegacyDistillationWorker(config, projectRoot);
|
|
1121
|
+
updateJsonStateFile(statePath, {
|
|
1122
|
+
runtime: "openclaw_plugin_service",
|
|
1123
|
+
manager_pid: process.pid,
|
|
976
1124
|
project_root: projectRoot,
|
|
1125
|
+
source: "openclaw_host_model_service",
|
|
977
1126
|
openclaw_bin: config.openclawBin,
|
|
978
1127
|
agent_id: config.distillationWorkerAgentId,
|
|
979
1128
|
interval_secs: config.distillationWorkerIntervalSecs,
|
|
980
|
-
|
|
1129
|
+
state_path: statePath,
|
|
981
1130
|
log_path: logPath,
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
1131
|
+
service_started_at: startedAt,
|
|
1132
|
+
service_heartbeat_at: startedAt,
|
|
1133
|
+
running: true,
|
|
1134
|
+
});
|
|
1135
|
+
|
|
1136
|
+
if (!config.distillationWorkerEnabled) {
|
|
1137
|
+
updateJsonStateFile(statePath, {
|
|
1138
|
+
service_heartbeat_at: new Date().toISOString(),
|
|
1139
|
+
last_error: "",
|
|
1140
|
+
});
|
|
1141
|
+
return;
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
const prompt = buildHostDistillationPrompt(projectRoot);
|
|
1145
|
+
const { command, commandArgs } = resolveOpenClawCommand(config, [
|
|
1146
|
+
"agent",
|
|
1147
|
+
"--local",
|
|
1148
|
+
"--agent",
|
|
1149
|
+
config.distillationWorkerAgentId,
|
|
1150
|
+
"--thinking",
|
|
1151
|
+
"minimal",
|
|
1152
|
+
"--timeout",
|
|
1153
|
+
"120",
|
|
1154
|
+
"--message",
|
|
1155
|
+
prompt,
|
|
1156
|
+
"--json",
|
|
1157
|
+
]);
|
|
1158
|
+
|
|
1159
|
+
const result = await runSpawnedCommand({
|
|
1160
|
+
command,
|
|
1161
|
+
args: commandArgs,
|
|
1162
|
+
cwd: projectRoot,
|
|
1163
|
+
timeoutMs: Math.max(config.daemonTimeoutMs, 120_000),
|
|
1164
|
+
onSpawn(pid) {
|
|
1165
|
+
currentChildPid = pid;
|
|
1166
|
+
updateJsonStateFile(statePath, {
|
|
1167
|
+
current_child_pid: pid,
|
|
1168
|
+
last_started_at: startedAt,
|
|
1169
|
+
service_heartbeat_at: startedAt,
|
|
1170
|
+
});
|
|
1171
|
+
},
|
|
1172
|
+
});
|
|
1173
|
+
|
|
1174
|
+
const finishedAt = new Date().toISOString();
|
|
1175
|
+
const computedLastError =
|
|
1176
|
+
result.exitCode && result.exitCode !== 0
|
|
1177
|
+
? result.stderr.trim() || `openclaw agent exited with ${result.exitCode}`
|
|
1178
|
+
: "";
|
|
1179
|
+
const payload = {
|
|
1180
|
+
runtime: "openclaw_plugin_service",
|
|
1181
|
+
manager_pid: process.pid,
|
|
1182
|
+
source: "openclaw_host_model_service",
|
|
1183
|
+
current_child_pid: null,
|
|
1184
|
+
last_started_at: startedAt,
|
|
1185
|
+
last_finished_at: finishedAt,
|
|
1186
|
+
last_exit_code: result.exitCode,
|
|
1187
|
+
last_error: computedLastError,
|
|
1188
|
+
last_stdout: result.stdout.trim(),
|
|
1189
|
+
last_stderr: result.stderr.trim(),
|
|
1190
|
+
service_heartbeat_at: finishedAt,
|
|
1191
|
+
};
|
|
1192
|
+
updateJsonStateFile(statePath, payload);
|
|
1193
|
+
appendLogLine(
|
|
1194
|
+
logPath,
|
|
1195
|
+
JSON.stringify({
|
|
1196
|
+
started_at: startedAt,
|
|
1197
|
+
finished_at: finishedAt,
|
|
1198
|
+
command,
|
|
1199
|
+
command_args: commandArgs,
|
|
1200
|
+
exit_code: result.exitCode,
|
|
1201
|
+
error: computedLastError,
|
|
1202
|
+
stdout: result.stdout.trim(),
|
|
1203
|
+
stderr: result.stderr.trim(),
|
|
1204
|
+
}),
|
|
1205
|
+
);
|
|
1206
|
+
ctx.logger.info(
|
|
1207
|
+
computedLastError
|
|
1208
|
+
? `opensyn distillation cycle finished with error: ${summarizeDistillationWorkerError(computedLastError)}`
|
|
1209
|
+
: "opensyn distillation cycle completed",
|
|
1210
|
+
);
|
|
1211
|
+
} catch (error) {
|
|
1212
|
+
const finishedAt = new Date().toISOString();
|
|
1213
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1214
|
+
updateJsonStateFile(statePath, {
|
|
1215
|
+
runtime: "openclaw_plugin_service",
|
|
1216
|
+
manager_pid: process.pid,
|
|
1217
|
+
source: "openclaw_host_model_service",
|
|
1218
|
+
current_child_pid: null,
|
|
1219
|
+
last_started_at: startedAt,
|
|
1220
|
+
last_finished_at: finishedAt,
|
|
1221
|
+
last_error: message,
|
|
1222
|
+
service_heartbeat_at: finishedAt,
|
|
1223
|
+
});
|
|
1224
|
+
appendLogLine(
|
|
1225
|
+
logPath,
|
|
1226
|
+
JSON.stringify({
|
|
1227
|
+
started_at: startedAt,
|
|
1228
|
+
finished_at: finishedAt,
|
|
1229
|
+
error: message,
|
|
1230
|
+
}),
|
|
1231
|
+
);
|
|
1232
|
+
ctx.logger.warn(`opensyn distillation cycle failed: ${message}`);
|
|
1233
|
+
} finally {
|
|
1234
|
+
currentChildPid = undefined;
|
|
1235
|
+
runningCycle = false;
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
async function tick(ctx: OpenClawPluginServiceContext): Promise<void> {
|
|
1240
|
+
const config = getResolvedPluginConfig(api, {
|
|
1241
|
+
defaultProjectRoot: ctx.workspaceDir,
|
|
1242
|
+
});
|
|
1243
|
+
const projectRoot = deriveDistillationProjectRoot(config, ctx);
|
|
1244
|
+
if (!projectRoot) {
|
|
1245
|
+
return;
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
const intervalMs = Math.max(5, config.distillationWorkerIntervalSecs) * 1000;
|
|
1249
|
+
if (Date.now() - lastCycleStartedAt < intervalMs) {
|
|
1250
|
+
const statePath = distillationWorkerStatePath(config, projectRoot);
|
|
1251
|
+
updateJsonStateFile(statePath, {
|
|
1252
|
+
runtime: "openclaw_plugin_service",
|
|
1253
|
+
manager_pid: process.pid,
|
|
1254
|
+
source: "openclaw_host_model_service",
|
|
1255
|
+
project_root: projectRoot,
|
|
1256
|
+
interval_secs: config.distillationWorkerIntervalSecs,
|
|
1257
|
+
service_heartbeat_at: new Date().toISOString(),
|
|
1258
|
+
});
|
|
1259
|
+
return;
|
|
1260
|
+
}
|
|
1261
|
+
|
|
1262
|
+
lastCycleStartedAt = Date.now();
|
|
1263
|
+
await runCycle(ctx, config, projectRoot);
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
return {
|
|
1267
|
+
id: "opensyn-distillation-service",
|
|
1268
|
+
async start(ctx: OpenClawPluginServiceContext): Promise<void> {
|
|
1269
|
+
lifecycleRevision += 1;
|
|
1270
|
+
const currentRevision = lifecycleRevision;
|
|
1271
|
+
const config = getResolvedPluginConfig(api, {
|
|
1272
|
+
defaultProjectRoot: ctx.workspaceDir,
|
|
1273
|
+
});
|
|
1274
|
+
const projectRoot = deriveDistillationProjectRoot(config, ctx);
|
|
1275
|
+
if (projectRoot) {
|
|
1276
|
+
stopLegacyDistillationWorker(config, projectRoot);
|
|
1277
|
+
updateJsonStateFile(distillationWorkerStatePath(config, projectRoot), {
|
|
1278
|
+
runtime: "openclaw_plugin_service",
|
|
1279
|
+
manager_pid: process.pid,
|
|
1280
|
+
source: "openclaw_host_model_service",
|
|
1281
|
+
project_root: projectRoot,
|
|
1282
|
+
openclaw_bin: config.openclawBin,
|
|
1283
|
+
agent_id: config.distillationWorkerAgentId,
|
|
1284
|
+
interval_secs: config.distillationWorkerIntervalSecs,
|
|
1285
|
+
state_path: distillationWorkerStatePath(config, projectRoot),
|
|
1286
|
+
log_path: distillationWorkerLogPath(config, projectRoot),
|
|
1287
|
+
service_started_at: new Date().toISOString(),
|
|
1288
|
+
service_heartbeat_at: new Date().toISOString(),
|
|
1289
|
+
running: true,
|
|
1290
|
+
last_error: "",
|
|
1291
|
+
});
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1294
|
+
void tick(ctx);
|
|
1295
|
+
timer = setInterval(() => {
|
|
1296
|
+
if (currentRevision !== lifecycleRevision) {
|
|
1297
|
+
return;
|
|
1298
|
+
}
|
|
1299
|
+
void tick(ctx);
|
|
1300
|
+
}, 5_000);
|
|
1301
|
+
timer.unref?.();
|
|
1302
|
+
ctx.logger.info("opensyn distillation service started");
|
|
1303
|
+
},
|
|
1304
|
+
async stop(ctx: OpenClawPluginServiceContext): Promise<void> {
|
|
1305
|
+
lifecycleRevision += 1;
|
|
1306
|
+
if (timer) {
|
|
1307
|
+
clearInterval(timer);
|
|
1308
|
+
timer = null;
|
|
1309
|
+
}
|
|
1310
|
+
if (currentChildPid && isPidAlive(currentChildPid)) {
|
|
1311
|
+
try {
|
|
1312
|
+
process.kill(currentChildPid, "SIGTERM");
|
|
1313
|
+
} catch {
|
|
1314
|
+
// Ignore pid races during shutdown.
|
|
1315
|
+
}
|
|
1316
|
+
}
|
|
1317
|
+
const config = getResolvedPluginConfig(api, {
|
|
1318
|
+
defaultProjectRoot: ctx.workspaceDir,
|
|
1319
|
+
});
|
|
1320
|
+
const projectRoot = deriveDistillationProjectRoot(config, ctx);
|
|
1321
|
+
if (projectRoot) {
|
|
1322
|
+
updateJsonStateFile(distillationWorkerStatePath(config, projectRoot), {
|
|
1323
|
+
runtime: "openclaw_plugin_service",
|
|
1324
|
+
manager_pid: process.pid,
|
|
1325
|
+
source: "openclaw_host_model_service",
|
|
1326
|
+
current_child_pid: null,
|
|
1327
|
+
service_stopped_at: new Date().toISOString(),
|
|
1328
|
+
service_heartbeat_at: new Date().toISOString(),
|
|
1329
|
+
running: false,
|
|
1330
|
+
});
|
|
1331
|
+
}
|
|
1332
|
+
currentChildPid = undefined;
|
|
1333
|
+
runningCycle = false;
|
|
1334
|
+
ctx.logger.info("opensyn distillation service stopped");
|
|
1335
|
+
},
|
|
1336
|
+
};
|
|
1337
|
+
}
|
|
1338
|
+
|
|
1339
|
+
async function ensureDistillationWorker(
|
|
1340
|
+
config: ResolvedOpenSynConfig,
|
|
1341
|
+
projectRoot: string,
|
|
1342
|
+
): Promise<DistillationWorkerStatus> {
|
|
1343
|
+
stopLegacyDistillationWorker(config, projectRoot);
|
|
990
1344
|
return getDistillationWorkerStatus(config, projectRoot);
|
|
991
1345
|
}
|
|
992
1346
|
|
|
@@ -995,14 +1349,23 @@ function stopDistillationWorker(
|
|
|
995
1349
|
projectRoot: string,
|
|
996
1350
|
): DistillationWorkerStatus {
|
|
997
1351
|
const status = getDistillationWorkerStatus(config, projectRoot);
|
|
998
|
-
if (status.running && status.pid) {
|
|
1352
|
+
if (status.source === "openclaw_host_model_agent" && status.running && status.pid) {
|
|
999
1353
|
try {
|
|
1000
1354
|
process.kill(status.pid, "SIGTERM");
|
|
1001
1355
|
} catch {
|
|
1002
1356
|
// Ignore pid races; the state file is still cleaned up below.
|
|
1003
1357
|
}
|
|
1004
1358
|
}
|
|
1005
|
-
|
|
1359
|
+
updateJsonStateFile(status.state_path, {
|
|
1360
|
+
runtime:
|
|
1361
|
+
status.source === "openclaw_host_model_service"
|
|
1362
|
+
? "openclaw_plugin_service"
|
|
1363
|
+
: "legacy",
|
|
1364
|
+
running: false,
|
|
1365
|
+
current_child_pid: null,
|
|
1366
|
+
last_error: "",
|
|
1367
|
+
service_heartbeat_at: new Date().toISOString(),
|
|
1368
|
+
});
|
|
1006
1369
|
return getDistillationWorkerStatus(config, projectRoot);
|
|
1007
1370
|
}
|
|
1008
1371
|
|
|
@@ -1301,6 +1664,22 @@ function summarizeResult(result: unknown): string {
|
|
|
1301
1664
|
return summarizeContextObject(result as ContextLike);
|
|
1302
1665
|
}
|
|
1303
1666
|
|
|
1667
|
+
if (looksLikeCollectionReport(result)) {
|
|
1668
|
+
return summarizeCollectionReport(
|
|
1669
|
+
result as {
|
|
1670
|
+
project_root?: string;
|
|
1671
|
+
audit_stats?: AuditStatsLike;
|
|
1672
|
+
current_context?: ContextLike | null;
|
|
1673
|
+
recent_episodes?: ContextLike[];
|
|
1674
|
+
approved_assets?: AssetLike[];
|
|
1675
|
+
candidate_assets?: AssetLike[];
|
|
1676
|
+
pending_jobs?: DistillationJobLike[];
|
|
1677
|
+
runtime_overlay?: RuntimeOverlayLike | null;
|
|
1678
|
+
next_step?: UserNextStep;
|
|
1679
|
+
},
|
|
1680
|
+
);
|
|
1681
|
+
}
|
|
1682
|
+
|
|
1304
1683
|
if (looksLikeAutonomousCollectionBootstrap(result)) {
|
|
1305
1684
|
return summarizeAutonomousCollectionBootstrap(
|
|
1306
1685
|
result as {
|
|
@@ -1372,6 +1751,10 @@ function summarizeResult(result: unknown): string {
|
|
|
1372
1751
|
return summarizeRuntimeOverlay(result as RuntimeOverlayLike);
|
|
1373
1752
|
}
|
|
1374
1753
|
|
|
1754
|
+
if (looksLikeAuditStats(result)) {
|
|
1755
|
+
return summarizeAuditStats(result as AuditStatsLike);
|
|
1756
|
+
}
|
|
1757
|
+
|
|
1375
1758
|
if (looksLikeAssetDiff(result)) {
|
|
1376
1759
|
return summarizeAssetDiff(result as AssetDiffLike);
|
|
1377
1760
|
}
|
|
@@ -1593,6 +1976,35 @@ function summarizeAssetApprovalResult(result: {
|
|
|
1593
1976
|
return `${base} | synced_records=${String(sync.applied_record_count ?? 0)} | host=${sync.host || "unknown"}`;
|
|
1594
1977
|
}
|
|
1595
1978
|
|
|
1979
|
+
function summarizeAuditStats(result: AuditStatsLike): string {
|
|
1980
|
+
return `OpenSyn collected data | raw_events=${String(result.raw_event_count ?? 0)} | episodes=${String(result.task_episode_count ?? 0)} | context_packs=${String(result.context_pack_count ?? 0)} | assets=${String(result.agent_asset_count ?? 0)} | projects=${String(result.projects?.length ?? 0)}`;
|
|
1981
|
+
}
|
|
1982
|
+
|
|
1983
|
+
function summarizeCollectionReport(result: {
|
|
1984
|
+
project_root?: string;
|
|
1985
|
+
audit_stats?: AuditStatsLike;
|
|
1986
|
+
current_context?: ContextLike | null;
|
|
1987
|
+
recent_episodes?: ContextLike[];
|
|
1988
|
+
approved_assets?: AssetLike[];
|
|
1989
|
+
candidate_assets?: AssetLike[];
|
|
1990
|
+
pending_jobs?: DistillationJobLike[];
|
|
1991
|
+
runtime_overlay?: RuntimeOverlayLike | null;
|
|
1992
|
+
next_step?: UserNextStep;
|
|
1993
|
+
}): string {
|
|
1994
|
+
const rawEvents = result.audit_stats?.raw_event_count ?? 0;
|
|
1995
|
+
const episodes = result.audit_stats?.task_episode_count ?? result.recent_episodes?.length ?? 0;
|
|
1996
|
+
const assets = result.audit_stats?.agent_asset_count ?? 0;
|
|
1997
|
+
const approved = result.runtime_overlay?.approved_asset_count ?? result.approved_assets?.length ?? 0;
|
|
1998
|
+
const pendingJobs = result.pending_jobs?.length ?? result.runtime_overlay?.pending_distillation_jobs ?? 0;
|
|
1999
|
+
const leadContext =
|
|
2000
|
+
result.current_context?.title ||
|
|
2001
|
+
result.current_context?.summary ||
|
|
2002
|
+
result.current_context?.semantic_hint?.activity_kind ||
|
|
2003
|
+
"none";
|
|
2004
|
+
const nextStep = result.next_step?.code || "none";
|
|
2005
|
+
return `OpenSyn report | project=${result.project_root || "unknown"} | raw_events=${String(rawEvents)} | episodes=${String(episodes)} | assets=${String(assets)} | approved_assets=${String(approved)} | pending_jobs=${String(pendingJobs)} | current=${leadContext} | next=${nextStep}`;
|
|
2006
|
+
}
|
|
2007
|
+
|
|
1596
2008
|
function extractNextStep(result: unknown): UserNextStep | undefined {
|
|
1597
2009
|
if (typeof result !== "object" || result === null) {
|
|
1598
2010
|
return undefined;
|
|
@@ -1732,6 +2144,24 @@ function looksLikeRuntimeOverlay(result: unknown): boolean {
|
|
|
1732
2144
|
);
|
|
1733
2145
|
}
|
|
1734
2146
|
|
|
2147
|
+
function looksLikeAuditStats(result: unknown): boolean {
|
|
2148
|
+
return (
|
|
2149
|
+
typeof result === "object" &&
|
|
2150
|
+
result !== null &&
|
|
2151
|
+
("raw_event_count" in result || "context_pack_count" in result || "task_episode_count" in result)
|
|
2152
|
+
);
|
|
2153
|
+
}
|
|
2154
|
+
|
|
2155
|
+
function looksLikeCollectionReport(result: unknown): boolean {
|
|
2156
|
+
return (
|
|
2157
|
+
typeof result === "object" &&
|
|
2158
|
+
result !== null &&
|
|
2159
|
+
"audit_stats" in result &&
|
|
2160
|
+
"current_context" in result &&
|
|
2161
|
+
"runtime_overlay" in result
|
|
2162
|
+
);
|
|
2163
|
+
}
|
|
2164
|
+
|
|
1735
2165
|
function looksLikeAutonomousCollectionBootstrap(result: unknown): boolean {
|
|
1736
2166
|
return (
|
|
1737
2167
|
typeof result === "object" &&
|
|
@@ -2105,6 +2535,74 @@ async function repairAutonomousCollection(
|
|
|
2105
2535
|
};
|
|
2106
2536
|
}
|
|
2107
2537
|
|
|
2538
|
+
async function getOpenSynCollectionReport(
|
|
2539
|
+
config: ResolvedOpenSynConfig,
|
|
2540
|
+
params: { project_root: string; shell?: string },
|
|
2541
|
+
) {
|
|
2542
|
+
const status = await getAutonomousCollectionStatus(config, params);
|
|
2543
|
+
const auditStats = await callDaemon(config, "list_collected_event_stats", {});
|
|
2544
|
+
const currentContext = await callDaemon(config, "synthesize_recent_activity", {
|
|
2545
|
+
project_root: params.project_root,
|
|
2546
|
+
window_secs: config.defaultWindowSecs ?? 300,
|
|
2547
|
+
limit: 512,
|
|
2548
|
+
});
|
|
2549
|
+
const recentEpisodes = await callDaemon(config, "get_recent_task_episodes", {
|
|
2550
|
+
project_root: params.project_root,
|
|
2551
|
+
limit: 5,
|
|
2552
|
+
});
|
|
2553
|
+
const approvedAssets = await callDaemon(config, "list_agent_assets", {
|
|
2554
|
+
project_root: params.project_root,
|
|
2555
|
+
status: "approved",
|
|
2556
|
+
limit: 5,
|
|
2557
|
+
});
|
|
2558
|
+
const candidateAssets = await callDaemon(config, "list_agent_assets", {
|
|
2559
|
+
project_root: params.project_root,
|
|
2560
|
+
status: "candidate",
|
|
2561
|
+
limit: 5,
|
|
2562
|
+
});
|
|
2563
|
+
const pendingJobs = await callDaemon(config, "list_distillation_jobs", {
|
|
2564
|
+
project_root: params.project_root,
|
|
2565
|
+
status: "pending",
|
|
2566
|
+
limit: 5,
|
|
2567
|
+
});
|
|
2568
|
+
const runtimeOverlay = await callDaemon(config, "get_agent_runtime_overlay", {
|
|
2569
|
+
project_root: params.project_root,
|
|
2570
|
+
host: config.projectionHost || "openclaw",
|
|
2571
|
+
});
|
|
2572
|
+
|
|
2573
|
+
return {
|
|
2574
|
+
semantic_hint: {
|
|
2575
|
+
activity_kind: "collection_report",
|
|
2576
|
+
status_label: "inspected",
|
|
2577
|
+
operator_focus:
|
|
2578
|
+
"Review what OpenSyn has collected, the latest current context, asset distillation results, and pending host-model work.",
|
|
2579
|
+
},
|
|
2580
|
+
project_root: params.project_root,
|
|
2581
|
+
status,
|
|
2582
|
+
audit_stats: auditStats,
|
|
2583
|
+
current_context: currentContext,
|
|
2584
|
+
recent_episodes: recentEpisodes,
|
|
2585
|
+
approved_assets: approvedAssets,
|
|
2586
|
+
candidate_assets: candidateAssets,
|
|
2587
|
+
pending_jobs: pendingJobs,
|
|
2588
|
+
runtime_overlay: runtimeOverlay,
|
|
2589
|
+
next_step:
|
|
2590
|
+
extractNextStep(status) ||
|
|
2591
|
+
buildUserNextStep({
|
|
2592
|
+
collectionMode:
|
|
2593
|
+
(status as { collection_settings?: CollectionSettingsLike })?.collection_settings
|
|
2594
|
+
?.mode,
|
|
2595
|
+
backgroundWatch:
|
|
2596
|
+
(status as { background_watch?: BackgroundWatchStatus }).background_watch,
|
|
2597
|
+
shellCaptureHealth:
|
|
2598
|
+
(status as { shell_capture_health?: ShellCaptureHealth }).shell_capture_health,
|
|
2599
|
+
distillationWorkerHealth:
|
|
2600
|
+
(status as { distillation_worker_health?: DistillationWorkerHealth })
|
|
2601
|
+
.distillation_worker_health,
|
|
2602
|
+
}),
|
|
2603
|
+
};
|
|
2604
|
+
}
|
|
2605
|
+
|
|
2108
2606
|
function printCliResult(result: unknown, jsonOutput = false): void {
|
|
2109
2607
|
const summary = summarizeResult(result);
|
|
2110
2608
|
if (summary) {
|
|
@@ -2130,6 +2628,8 @@ export default {
|
|
|
2130
2628
|
register(api: OpenClawPluginApi) {
|
|
2131
2629
|
const config = getResolvedPluginConfig(api);
|
|
2132
2630
|
|
|
2631
|
+
api.registerService(createOpenSynDistillationService(api));
|
|
2632
|
+
|
|
2133
2633
|
api.registerCli(
|
|
2134
2634
|
({ program, workspaceDir }) => {
|
|
2135
2635
|
const resolveCliProjectRoot = (input?: string): string =>
|
|
@@ -2155,6 +2655,99 @@ export default {
|
|
|
2155
2655
|
return persisted;
|
|
2156
2656
|
};
|
|
2157
2657
|
|
|
2658
|
+
program
|
|
2659
|
+
.command("report_opensyn")
|
|
2660
|
+
.description(
|
|
2661
|
+
"Show an OpenSyn collection report for the current workspace, including captured data counts, current context, asset previews, and pending distillation jobs.",
|
|
2662
|
+
)
|
|
2663
|
+
.argument("[project_root]", "Optional project root. Defaults to the current OpenClaw workspace.")
|
|
2664
|
+
.option("--shell <shell>", "Shell to inspect for command capture.")
|
|
2665
|
+
.option("--json", "Print the full JSON result.")
|
|
2666
|
+
.action(async (projectRootArg?: string, opts?: { shell?: string; json?: boolean }) => {
|
|
2667
|
+
const projectRoot = resolveCliProjectRoot(projectRootArg);
|
|
2668
|
+
const resolved = await resolveCliConfig(projectRoot);
|
|
2669
|
+
const result = await getOpenSynCollectionReport(resolved, {
|
|
2670
|
+
project_root: projectRoot,
|
|
2671
|
+
shell: opts?.shell,
|
|
2672
|
+
});
|
|
2673
|
+
printCliResult(result, opts?.json);
|
|
2674
|
+
});
|
|
2675
|
+
|
|
2676
|
+
program
|
|
2677
|
+
.command("context_opensyn")
|
|
2678
|
+
.description(
|
|
2679
|
+
"Show the latest synthesized OpenSyn context for the current workspace.",
|
|
2680
|
+
)
|
|
2681
|
+
.argument("[project_root]", "Optional project root. Defaults to the current OpenClaw workspace.")
|
|
2682
|
+
.option("--json", "Print the full JSON result.")
|
|
2683
|
+
.action(async (projectRootArg?: string, opts?: { json?: boolean }) => {
|
|
2684
|
+
const projectRoot = resolveCliProjectRoot(projectRootArg);
|
|
2685
|
+
const resolved = await resolveCliConfig(projectRoot);
|
|
2686
|
+
const result = await callDaemon(resolved, "synthesize_recent_activity", {
|
|
2687
|
+
project_root: projectRoot,
|
|
2688
|
+
window_secs: resolved.defaultWindowSecs ?? 300,
|
|
2689
|
+
limit: 512,
|
|
2690
|
+
});
|
|
2691
|
+
printCliResult(result, opts?.json);
|
|
2692
|
+
});
|
|
2693
|
+
|
|
2694
|
+
program
|
|
2695
|
+
.command("assets_opensyn")
|
|
2696
|
+
.description(
|
|
2697
|
+
"List recent OpenSyn agent assets for the current workspace.",
|
|
2698
|
+
)
|
|
2699
|
+
.argument("[project_root]", "Optional project root. Defaults to the current OpenClaw workspace.")
|
|
2700
|
+
.option("--status <status>", "Optional asset status filter.")
|
|
2701
|
+
.option("--kind <kind>", "Optional asset kind filter.")
|
|
2702
|
+
.option("--limit <limit>", "Maximum assets to show.", (value) => Number.parseInt(value, 10))
|
|
2703
|
+
.option("--json", "Print the full JSON result.")
|
|
2704
|
+
.action(
|
|
2705
|
+
async (
|
|
2706
|
+
projectRootArg?: string,
|
|
2707
|
+
opts?: { status?: string; kind?: string; limit?: number; json?: boolean },
|
|
2708
|
+
) => {
|
|
2709
|
+
const projectRoot = resolveCliProjectRoot(projectRootArg);
|
|
2710
|
+
const resolved = await resolveCliConfig(projectRoot);
|
|
2711
|
+
const result = await callDaemon(resolved, "list_agent_assets", {
|
|
2712
|
+
project_root: projectRoot,
|
|
2713
|
+
status: opts?.status,
|
|
2714
|
+
kind: opts?.kind,
|
|
2715
|
+
limit: opts?.limit ?? resolved.defaultSearchLimit ?? 10,
|
|
2716
|
+
});
|
|
2717
|
+
printCliResult(result, opts?.json);
|
|
2718
|
+
},
|
|
2719
|
+
);
|
|
2720
|
+
|
|
2721
|
+
program
|
|
2722
|
+
.command("stats_opensyn")
|
|
2723
|
+
.description(
|
|
2724
|
+
"Show collected OpenSyn data counts for the current workspace.",
|
|
2725
|
+
)
|
|
2726
|
+
.option("--json", "Print the full JSON result.")
|
|
2727
|
+
.action(async (opts?: { json?: boolean }) => {
|
|
2728
|
+
const resolved = await resolveCliConfig(resolveCliProjectRoot());
|
|
2729
|
+
const result = await callDaemon(resolved, "list_collected_event_stats", {});
|
|
2730
|
+
printCliResult(result, opts?.json);
|
|
2731
|
+
});
|
|
2732
|
+
|
|
2733
|
+
program
|
|
2734
|
+
.command("episodes_opensyn")
|
|
2735
|
+
.description(
|
|
2736
|
+
"List recent OpenSyn task episodes for the current workspace.",
|
|
2737
|
+
)
|
|
2738
|
+
.argument("[project_root]", "Optional project root. Defaults to the current OpenClaw workspace.")
|
|
2739
|
+
.option("--limit <limit>", "Maximum episodes to show.", (value) => Number.parseInt(value, 10))
|
|
2740
|
+
.option("--json", "Print the full JSON result.")
|
|
2741
|
+
.action(async (projectRootArg?: string, opts?: { limit?: number; json?: boolean }) => {
|
|
2742
|
+
const projectRoot = resolveCliProjectRoot(projectRootArg);
|
|
2743
|
+
const resolved = await resolveCliConfig(projectRoot);
|
|
2744
|
+
const result = await callDaemon(resolved, "get_recent_task_episodes", {
|
|
2745
|
+
project_root: projectRoot,
|
|
2746
|
+
limit: opts?.limit ?? resolved.defaultSearchLimit ?? 10,
|
|
2747
|
+
});
|
|
2748
|
+
printCliResult(result, opts?.json);
|
|
2749
|
+
});
|
|
2750
|
+
|
|
2158
2751
|
program
|
|
2159
2752
|
.command("enable_opensyn")
|
|
2160
2753
|
.description(
|
|
@@ -2272,6 +2865,89 @@ export default {
|
|
|
2272
2865
|
.command("opensyn")
|
|
2273
2866
|
.description("OpenSyn management commands.");
|
|
2274
2867
|
|
|
2868
|
+
opensyn
|
|
2869
|
+
.command("report")
|
|
2870
|
+
.description("Alias for openclaw report_opensyn.")
|
|
2871
|
+
.argument("[project_root]")
|
|
2872
|
+
.option("--shell <shell>")
|
|
2873
|
+
.option("--json", "Print the full JSON result.")
|
|
2874
|
+
.action(async (projectRootArg?: string, opts?: { shell?: string; json?: boolean }) => {
|
|
2875
|
+
const projectRoot = resolveCliProjectRoot(projectRootArg);
|
|
2876
|
+
const resolved = await resolveCliConfig(projectRoot);
|
|
2877
|
+
const result = await getOpenSynCollectionReport(resolved, {
|
|
2878
|
+
project_root: projectRoot,
|
|
2879
|
+
shell: opts?.shell,
|
|
2880
|
+
});
|
|
2881
|
+
printCliResult(result, opts?.json);
|
|
2882
|
+
});
|
|
2883
|
+
|
|
2884
|
+
opensyn
|
|
2885
|
+
.command("context")
|
|
2886
|
+
.description("Alias for openclaw context_opensyn.")
|
|
2887
|
+
.argument("[project_root]")
|
|
2888
|
+
.option("--json", "Print the full JSON result.")
|
|
2889
|
+
.action(async (projectRootArg?: string, opts?: { json?: boolean }) => {
|
|
2890
|
+
const projectRoot = resolveCliProjectRoot(projectRootArg);
|
|
2891
|
+
const resolved = await resolveCliConfig(projectRoot);
|
|
2892
|
+
const result = await callDaemon(resolved, "synthesize_recent_activity", {
|
|
2893
|
+
project_root: projectRoot,
|
|
2894
|
+
window_secs: resolved.defaultWindowSecs ?? 300,
|
|
2895
|
+
limit: 512,
|
|
2896
|
+
});
|
|
2897
|
+
printCliResult(result, opts?.json);
|
|
2898
|
+
});
|
|
2899
|
+
|
|
2900
|
+
opensyn
|
|
2901
|
+
.command("assets")
|
|
2902
|
+
.description("Alias for openclaw assets_opensyn.")
|
|
2903
|
+
.argument("[project_root]")
|
|
2904
|
+
.option("--status <status>")
|
|
2905
|
+
.option("--kind <kind>")
|
|
2906
|
+
.option("--limit <limit>", "Maximum assets to show.", (value) => Number.parseInt(value, 10))
|
|
2907
|
+
.option("--json", "Print the full JSON result.")
|
|
2908
|
+
.action(
|
|
2909
|
+
async (
|
|
2910
|
+
projectRootArg?: string,
|
|
2911
|
+
opts?: { status?: string; kind?: string; limit?: number; json?: boolean },
|
|
2912
|
+
) => {
|
|
2913
|
+
const projectRoot = resolveCliProjectRoot(projectRootArg);
|
|
2914
|
+
const resolved = await resolveCliConfig(projectRoot);
|
|
2915
|
+
const result = await callDaemon(resolved, "list_agent_assets", {
|
|
2916
|
+
project_root: projectRoot,
|
|
2917
|
+
status: opts?.status,
|
|
2918
|
+
kind: opts?.kind,
|
|
2919
|
+
limit: opts?.limit ?? resolved.defaultSearchLimit ?? 10,
|
|
2920
|
+
});
|
|
2921
|
+
printCliResult(result, opts?.json);
|
|
2922
|
+
},
|
|
2923
|
+
);
|
|
2924
|
+
|
|
2925
|
+
opensyn
|
|
2926
|
+
.command("stats")
|
|
2927
|
+
.description("Alias for openclaw stats_opensyn.")
|
|
2928
|
+
.option("--json", "Print the full JSON result.")
|
|
2929
|
+
.action(async (opts?: { json?: boolean }) => {
|
|
2930
|
+
const resolved = await resolveCliConfig(resolveCliProjectRoot());
|
|
2931
|
+
const result = await callDaemon(resolved, "list_collected_event_stats", {});
|
|
2932
|
+
printCliResult(result, opts?.json);
|
|
2933
|
+
});
|
|
2934
|
+
|
|
2935
|
+
opensyn
|
|
2936
|
+
.command("episodes")
|
|
2937
|
+
.description("Alias for openclaw episodes_opensyn.")
|
|
2938
|
+
.argument("[project_root]")
|
|
2939
|
+
.option("--limit <limit>", "Maximum episodes to show.", (value) => Number.parseInt(value, 10))
|
|
2940
|
+
.option("--json", "Print the full JSON result.")
|
|
2941
|
+
.action(async (projectRootArg?: string, opts?: { limit?: number; json?: boolean }) => {
|
|
2942
|
+
const projectRoot = resolveCliProjectRoot(projectRootArg);
|
|
2943
|
+
const resolved = await resolveCliConfig(projectRoot);
|
|
2944
|
+
const result = await callDaemon(resolved, "get_recent_task_episodes", {
|
|
2945
|
+
project_root: projectRoot,
|
|
2946
|
+
limit: opts?.limit ?? resolved.defaultSearchLimit ?? 10,
|
|
2947
|
+
});
|
|
2948
|
+
printCliResult(result, opts?.json);
|
|
2949
|
+
});
|
|
2950
|
+
|
|
2275
2951
|
opensyn
|
|
2276
2952
|
.command("enable")
|
|
2277
2953
|
.description("Alias for openclaw enable_opensyn.")
|
|
@@ -2356,6 +3032,11 @@ export default {
|
|
|
2356
3032
|
},
|
|
2357
3033
|
{
|
|
2358
3034
|
commands: [
|
|
3035
|
+
"report_opensyn",
|
|
3036
|
+
"context_opensyn",
|
|
3037
|
+
"assets_opensyn",
|
|
3038
|
+
"stats_opensyn",
|
|
3039
|
+
"episodes_opensyn",
|
|
2359
3040
|
"enable_opensyn",
|
|
2360
3041
|
"disable_opensyn",
|
|
2361
3042
|
"status_opensyn",
|
|
@@ -2367,6 +3048,54 @@ export default {
|
|
|
2367
3048
|
},
|
|
2368
3049
|
);
|
|
2369
3050
|
|
|
3051
|
+
api.registerTool(
|
|
3052
|
+
{
|
|
3053
|
+
name: "opensyn_collection_report",
|
|
3054
|
+
description:
|
|
3055
|
+
"Return an aggregated OpenSyn report for the current project: collection health, captured data counts, current context, recent episodes, asset previews, runtime overlay, and pending distillation jobs.",
|
|
3056
|
+
parameters: Type.Object({
|
|
3057
|
+
project_root: Type.Optional(
|
|
3058
|
+
Type.String({
|
|
3059
|
+
description:
|
|
3060
|
+
"Optional absolute project root. Leave unset to use defaultProjectRoot from plugin config.",
|
|
3061
|
+
}),
|
|
3062
|
+
),
|
|
3063
|
+
shell: Type.Optional(
|
|
3064
|
+
Type.String({
|
|
3065
|
+
description:
|
|
3066
|
+
"Shell to inspect for persistent command collection, for example bash or zsh.",
|
|
3067
|
+
}),
|
|
3068
|
+
),
|
|
3069
|
+
}),
|
|
3070
|
+
async execute(_id, params: { project_root?: string; shell?: string }) {
|
|
3071
|
+
const projectRoot = requireProjectRoot(params, config);
|
|
3072
|
+
if (!projectRoot) {
|
|
3073
|
+
throw new Error("project_root is required unless defaultProjectRoot is configured");
|
|
3074
|
+
}
|
|
3075
|
+
const result = await getOpenSynCollectionReport(config, {
|
|
3076
|
+
project_root: projectRoot,
|
|
3077
|
+
shell: params.shell,
|
|
3078
|
+
});
|
|
3079
|
+
return asText(result);
|
|
3080
|
+
},
|
|
3081
|
+
},
|
|
3082
|
+
{ optional: true },
|
|
3083
|
+
);
|
|
3084
|
+
|
|
3085
|
+
api.registerTool(
|
|
3086
|
+
{
|
|
3087
|
+
name: "opensyn_collected_stats",
|
|
3088
|
+
description:
|
|
3089
|
+
"Return raw OpenSyn collection counts such as raw events, task episodes, context packs, and stored assets.",
|
|
3090
|
+
parameters: Type.Object({}),
|
|
3091
|
+
async execute() {
|
|
3092
|
+
const result = await callDaemon(config, "list_collected_event_stats", {});
|
|
3093
|
+
return asText(result);
|
|
3094
|
+
},
|
|
3095
|
+
},
|
|
3096
|
+
{ optional: true },
|
|
3097
|
+
);
|
|
3098
|
+
|
|
2370
3099
|
api.registerTool(
|
|
2371
3100
|
{
|
|
2372
3101
|
name: "opensyn_recent_failure",
|
|
@@ -2489,6 +3218,35 @@ export default {
|
|
|
2489
3218
|
{ optional: true },
|
|
2490
3219
|
);
|
|
2491
3220
|
|
|
3221
|
+
api.registerTool(
|
|
3222
|
+
{
|
|
3223
|
+
name: "opensyn_recent_episodes",
|
|
3224
|
+
description:
|
|
3225
|
+
"Return recent OpenSyn task episodes for the current project so the host can inspect what was just collected.",
|
|
3226
|
+
parameters: Type.Object({
|
|
3227
|
+
project_root: Type.Optional(
|
|
3228
|
+
Type.String({
|
|
3229
|
+
description:
|
|
3230
|
+
"Optional absolute project root. Leave unset to use defaultProjectRoot from plugin config.",
|
|
3231
|
+
}),
|
|
3232
|
+
),
|
|
3233
|
+
limit: Type.Optional(Type.Integer({ minimum: 1 })),
|
|
3234
|
+
}),
|
|
3235
|
+
async execute(_id, params: { project_root?: string; limit?: number }) {
|
|
3236
|
+
const projectRoot = requireProjectRoot(params, config);
|
|
3237
|
+
if (!projectRoot) {
|
|
3238
|
+
throw new Error("project_root is required unless defaultProjectRoot is configured");
|
|
3239
|
+
}
|
|
3240
|
+
const result = await callDaemon(config, "get_recent_task_episodes", {
|
|
3241
|
+
project_root: projectRoot,
|
|
3242
|
+
limit: params.limit ?? config.defaultSearchLimit ?? 10,
|
|
3243
|
+
});
|
|
3244
|
+
return asText(result);
|
|
3245
|
+
},
|
|
3246
|
+
},
|
|
3247
|
+
{ optional: true },
|
|
3248
|
+
);
|
|
3249
|
+
|
|
2492
3250
|
api.registerTool(
|
|
2493
3251
|
{
|
|
2494
3252
|
name: "opensyn_distill_assets",
|