forge-openclaw-plugin 0.2.20 → 0.2.22

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.
Files changed (33) hide show
  1. package/dist/assets/{board-DGbXWEuu.js → board-_C6oMy5w.js} +2 -2
  2. package/dist/assets/{board-DGbXWEuu.js.map → board-_C6oMy5w.js.map} +1 -1
  3. package/dist/assets/index-Ch_xeZ2u.js +63 -0
  4. package/dist/assets/index-Ch_xeZ2u.js.map +1 -0
  5. package/dist/assets/index-DvVM7K6j.css +1 -0
  6. package/dist/assets/{motion-B5Qoz2Ci.js → motion-D4sZgCHd.js} +2 -2
  7. package/dist/assets/{motion-B5Qoz2Ci.js.map → motion-D4sZgCHd.js.map} +1 -1
  8. package/dist/assets/{table-D_iurDQu.js → table-BWzTaky1.js} +2 -2
  9. package/dist/assets/{table-D_iurDQu.js.map → table-BWzTaky1.js.map} +1 -1
  10. package/dist/assets/{ui-D5QUYUq4.js → ui-BzK4azQb.js} +2 -2
  11. package/dist/assets/{ui-D5QUYUq4.js.map → ui-BzK4azQb.js.map} +1 -1
  12. package/dist/assets/vendor-De38P6YR.js +729 -0
  13. package/dist/assets/vendor-De38P6YR.js.map +1 -0
  14. package/dist/assets/{viz-BD9WSxHz.js → viz-C6hfyqzu.js} +2 -2
  15. package/dist/assets/{viz-BD9WSxHz.js.map → viz-C6hfyqzu.js.map} +1 -1
  16. package/dist/index.html +8 -8
  17. package/dist/server/app.js +328 -19
  18. package/dist/server/health.js +82 -21
  19. package/dist/server/managers/platform/background-job-manager.js +103 -8
  20. package/dist/server/managers/platform/llm-manager.js +91 -5
  21. package/dist/server/managers/platform/openai-responses-provider.js +683 -70
  22. package/dist/server/repositories/diagnostic-logs.js +243 -0
  23. package/dist/server/repositories/wiki-memory.js +619 -66
  24. package/dist/server/types.js +56 -0
  25. package/openclaw.plugin.json +1 -1
  26. package/package.json +1 -1
  27. package/server/migrations/023_diagnostic_logs.sql +28 -0
  28. package/skills/forge-openclaw/SKILL.md +14 -0
  29. package/dist/assets/index-4-1WI9i7.css +0 -1
  30. package/dist/assets/index-BZbHajNK.js +0 -63
  31. package/dist/assets/index-BZbHajNK.js.map +0 -1
  32. package/dist/assets/vendor-KARp8LAR.js +0 -706
  33. package/dist/assets/vendor-KARp8LAR.js.map +0 -1
@@ -1,19 +1,49 @@
1
1
  import { AbstractManager } from "../base.js";
2
+ import { recordDiagnosticLog } from "../../repositories/diagnostic-logs.js";
2
3
  export class BackgroundJobManager extends AbstractManager {
4
+ maxConcurrentJobs;
3
5
  name = "BackgroundJobManager";
4
6
  queue = [];
5
7
  active = new Set();
6
8
  draining = false;
9
+ constructor(maxConcurrentJobs = 3) {
10
+ super();
11
+ this.maxConcurrentJobs = maxConcurrentJobs;
12
+ }
7
13
  start() {
8
14
  return;
9
15
  }
10
16
  enqueue(input) {
17
+ if (this.has(input.id)) {
18
+ this.recordLifecycleLog("info", "background_job_enqueue_skipped", {
19
+ task: input,
20
+ message: `Skipped duplicate background job ${input.label}.`,
21
+ details: {
22
+ queueDepth: this.queue.length,
23
+ activeCount: this.active.size
24
+ },
25
+ functionName: "enqueue"
26
+ });
27
+ return;
28
+ }
11
29
  this.queue.push(input);
30
+ this.recordLifecycleLog("info", "background_job_enqueued", {
31
+ task: input,
32
+ message: `Enqueued background job ${input.label}.`,
33
+ details: {
34
+ queueDepth: this.queue.length,
35
+ activeCount: this.active.size
36
+ },
37
+ functionName: "enqueue"
38
+ });
12
39
  this.scheduleDrain();
13
40
  }
14
41
  isActive(jobId) {
15
42
  return this.active.has(jobId);
16
43
  }
44
+ has(jobId) {
45
+ return (this.active.has(jobId) || this.queue.some((task) => task.id === jobId));
46
+ }
17
47
  async stop() {
18
48
  this.draining = true;
19
49
  while (this.active.size > 0) {
@@ -21,26 +51,69 @@ export class BackgroundJobManager extends AbstractManager {
21
51
  }
22
52
  }
23
53
  scheduleDrain() {
24
- if (this.draining || this.queue.length === 0) {
54
+ if (this.draining ||
55
+ this.queue.length === 0 ||
56
+ this.active.size >= this.maxConcurrentJobs) {
25
57
  return;
26
58
  }
27
59
  queueMicrotask(() => {
28
- void this.drainNext();
60
+ void this.drainAvailable();
29
61
  });
30
62
  }
31
- async drainNext() {
32
- if (this.draining || this.active.size > 0) {
63
+ async drainAvailable() {
64
+ if (this.draining) {
33
65
  return;
34
66
  }
35
- const next = this.queue.shift();
36
- if (!next) {
37
- return;
67
+ while (!this.draining &&
68
+ this.queue.length > 0 &&
69
+ this.active.size < this.maxConcurrentJobs) {
70
+ const next = this.queue.shift();
71
+ if (!next) {
72
+ return;
73
+ }
74
+ this.active.add(next.id);
75
+ void this.runTask(next);
38
76
  }
39
- this.active.add(next.id);
77
+ }
78
+ async runTask(next) {
79
+ const startedAt = Date.now();
80
+ this.recordLifecycleLog("info", "background_job_started", {
81
+ task: next,
82
+ message: `Started background job ${next.label}.`,
83
+ details: {
84
+ queueDepth: this.queue.length,
85
+ activeCount: this.active.size,
86
+ maxConcurrentJobs: this.maxConcurrentJobs
87
+ },
88
+ functionName: "runTask"
89
+ });
40
90
  try {
41
91
  await next.handler();
92
+ this.recordLifecycleLog("info", "background_job_completed", {
93
+ task: next,
94
+ message: `Completed background job ${next.label}.`,
95
+ details: {
96
+ durationMs: Date.now() - startedAt,
97
+ queueDepth: this.queue.length,
98
+ activeCount: this.active.size,
99
+ maxConcurrentJobs: this.maxConcurrentJobs
100
+ },
101
+ functionName: "runTask"
102
+ });
42
103
  }
43
104
  catch (error) {
105
+ this.recordLifecycleLog("error", "background_job_failed", {
106
+ task: next,
107
+ message: `Background job failed for ${next.label}.`,
108
+ details: {
109
+ durationMs: Date.now() - startedAt,
110
+ queueDepth: this.queue.length,
111
+ activeCount: this.active.size,
112
+ maxConcurrentJobs: this.maxConcurrentJobs,
113
+ error
114
+ },
115
+ functionName: "runTask"
116
+ });
44
117
  console.error(`[${this.name}] background job failed for ${next.label}:`, error);
45
118
  }
46
119
  finally {
@@ -48,4 +121,26 @@ export class BackgroundJobManager extends AbstractManager {
48
121
  this.scheduleDrain();
49
122
  }
50
123
  }
124
+ recordLifecycleLog(level, eventKey, input) {
125
+ try {
126
+ recordDiagnosticLog({
127
+ level,
128
+ source: "system",
129
+ scope: "background_job",
130
+ eventKey,
131
+ message: input.message,
132
+ functionName: input.functionName,
133
+ entityType: "background_job",
134
+ entityId: input.task.id,
135
+ jobId: input.task.id,
136
+ details: {
137
+ label: input.task.label,
138
+ ...(input.details ?? {})
139
+ }
140
+ });
141
+ }
142
+ catch {
143
+ // Diagnostics should never block job execution.
144
+ }
145
+ }
51
146
  }
@@ -1,5 +1,8 @@
1
1
  import { AbstractManager } from "../base.js";
2
2
  import { readEncryptedSecret } from "../../repositories/calendar.js";
3
+ function emitDiagnostic(logger, input) {
4
+ logger?.(input);
5
+ }
3
6
  export class LlmManager extends AbstractManager {
4
7
  secretsManager;
5
8
  name = "LlmManager";
@@ -13,18 +16,101 @@ export class LlmManager extends AbstractManager {
13
16
  this.providers.set(alias, provider);
14
17
  }
15
18
  }
16
- async compileWikiIngest(profile, input) {
17
- const provider = this.providers.get(profile.provider) ??
18
- this.providers.get("openai-responses") ??
19
- null;
19
+ async compileWikiIngest(profile, input, options = {}, logger) {
20
+ const provider = this.resolveProvider(profile.provider);
20
21
  if (!provider) {
22
+ emitDiagnostic(logger, {
23
+ level: "error",
24
+ message: "Wiki ingest LLM provider is not supported.",
25
+ details: {
26
+ scope: "wiki_llm",
27
+ eventKey: "llm_provider_missing",
28
+ provider: profile.provider,
29
+ baseUrl: profile.baseUrl,
30
+ model: profile.model
31
+ }
32
+ });
21
33
  return null;
22
34
  }
23
35
  const apiKey = this.readApiKey(profile.secretId);
24
36
  if (!apiKey) {
37
+ emitDiagnostic(logger, {
38
+ level: "error",
39
+ message: "Wiki ingest LLM profile is missing an API key.",
40
+ details: {
41
+ scope: "wiki_llm",
42
+ eventKey: "llm_api_key_missing",
43
+ provider: profile.provider,
44
+ baseUrl: profile.baseUrl,
45
+ model: profile.model,
46
+ secretId: profile.secretId ?? null
47
+ }
48
+ });
25
49
  return null;
26
50
  }
27
- return provider.compile({ apiKey, profile, input });
51
+ return provider.compile({
52
+ apiKey,
53
+ profile,
54
+ input,
55
+ resumeResponseId: options.resumeResponseId ?? null,
56
+ logger
57
+ });
58
+ }
59
+ async testWikiConnection(profile, explicitApiKey, logger) {
60
+ const provider = this.resolveProvider(profile.provider);
61
+ if (!provider) {
62
+ emitDiagnostic(logger, {
63
+ level: "error",
64
+ message: "Wiki LLM connection test requested an unsupported provider.",
65
+ details: {
66
+ scope: "wiki_llm",
67
+ eventKey: "llm_provider_missing",
68
+ provider: profile.provider,
69
+ baseUrl: profile.baseUrl,
70
+ model: profile.model
71
+ }
72
+ });
73
+ throw new Error("Unsupported LLM provider.");
74
+ }
75
+ const apiKey = explicitApiKey?.trim() || this.readApiKey(profile.secretId);
76
+ if (!apiKey) {
77
+ emitDiagnostic(logger, {
78
+ level: "error",
79
+ message: "Wiki LLM connection test is missing an API key.",
80
+ details: {
81
+ scope: "wiki_llm",
82
+ eventKey: "llm_api_key_missing",
83
+ provider: profile.provider,
84
+ baseUrl: profile.baseUrl,
85
+ model: profile.model,
86
+ secretId: profile.secretId ?? null
87
+ }
88
+ });
89
+ throw new Error("Save an OpenAI API key first.");
90
+ }
91
+ const result = await provider.testConnection({
92
+ apiKey,
93
+ profile,
94
+ logger
95
+ });
96
+ return {
97
+ provider: profile.provider,
98
+ model: profile.model,
99
+ baseUrl: profile.baseUrl,
100
+ reasoningEffort: typeof profile.metadata.reasoningEffort === "string"
101
+ ? profile.metadata.reasoningEffort
102
+ : null,
103
+ verbosity: typeof profile.metadata.verbosity === "string"
104
+ ? profile.metadata.verbosity
105
+ : null,
106
+ usingStoredKey: !explicitApiKey?.trim(),
107
+ outputPreview: result.outputPreview
108
+ };
109
+ }
110
+ resolveProvider(providerName) {
111
+ return (this.providers.get(providerName) ??
112
+ this.providers.get("openai-responses") ??
113
+ null);
28
114
  }
29
115
  readApiKey(secretId) {
30
116
  if (!secretId) {