ofiere-openclaw-plugin 4.56.8 → 4.56.10
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 +104 -104
- package/dist/src/prompt.js +130 -130
- package/dist/src/tools.js +49 -5
- package/index.ts +105 -105
- package/package.json +1 -1
- package/src/agent-resolver.ts +90 -90
- package/src/agent-tier.ts +192 -192
- package/src/attach-token.ts +106 -106
- package/src/attachments.ts +601 -601
- package/src/cli.ts +247 -247
- package/src/config.ts +78 -78
- package/src/prompt.ts +267 -267
- package/src/sop-render.ts +216 -216
- package/src/supabase.ts +13 -13
- package/src/tools.ts +48 -5
- package/src/types/openclaw.d.ts +8 -8
- package/src/types.ts +10 -10
package/src/sop-render.ts
CHANGED
|
@@ -1,216 +1,216 @@
|
|
|
1
|
-
// src/sop-render.ts — Plugin-side markdown renderer for SOPs and Frameworks.
|
|
2
|
-
// Mirrors dashboard/lib/sopRender.ts. Output is what gets prepended/appended to
|
|
3
|
-
// the system prompt during `before_prompt_build`.
|
|
4
|
-
|
|
5
|
-
interface SOPCheckItem { text?: string; checked?: boolean; }
|
|
6
|
-
interface SOPStep {
|
|
7
|
-
name?: string; action?: string; owner?: string; output?: string;
|
|
8
|
-
decision_logic?: string; failure_state?: string; fallback_action?: string;
|
|
9
|
-
estimated_duration?: string;
|
|
10
|
-
}
|
|
11
|
-
interface SOPEscalationRule { trigger?: string; escalateTo?: string; priority?: string; }
|
|
12
|
-
interface SOPPrerequisites {
|
|
13
|
-
conditions?: SOPCheckItem[]; required_tools?: string[];
|
|
14
|
-
required_permissions?: string[]; safety_warnings?: string[];
|
|
15
|
-
}
|
|
16
|
-
export interface SOPData {
|
|
17
|
-
title?: string;
|
|
18
|
-
purpose?: string; scope?: string; applicability?: string;
|
|
19
|
-
prerequisites?: SOPPrerequisites;
|
|
20
|
-
procedure_steps?: SOPStep[];
|
|
21
|
-
expected_outputs?: string[];
|
|
22
|
-
acceptance_criteria?: SOPCheckItem[];
|
|
23
|
-
escalation_rules?: SOPEscalationRule[];
|
|
24
|
-
rollback_procedure?: string;
|
|
25
|
-
notes?: string;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
interface FrameworkObjective {
|
|
29
|
-
objective?: string; target?: string; measurement?: string; frequency?: string;
|
|
30
|
-
}
|
|
31
|
-
interface FrameworkEscalation {
|
|
32
|
-
trigger?: string; escalate_to?: string; priority?: string; response_sla?: string;
|
|
33
|
-
}
|
|
34
|
-
export interface FrameworkData {
|
|
35
|
-
title?: string;
|
|
36
|
-
mission?: string; vision?: string;
|
|
37
|
-
core_principles?: string[];
|
|
38
|
-
decision_authority?: string; budget_constraints?: string;
|
|
39
|
-
resource_limits?: string; tool_stack?: string[];
|
|
40
|
-
strategic_objectives?: FrameworkObjective[];
|
|
41
|
-
risk_appetite?: string; compliance_requirements?: string[];
|
|
42
|
-
escalation_matrix?: FrameworkEscalation[];
|
|
43
|
-
team_composition?: string; integration_points?: string[];
|
|
44
|
-
review_frequency?: string; last_reviewed?: string; next_review?: string;
|
|
45
|
-
notes?: string;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function bullet(items: (string | undefined | null)[]): string {
|
|
49
|
-
const cleaned = items.map((s) => (s ?? "").toString().trim()).filter(Boolean);
|
|
50
|
-
return cleaned.length ? cleaned.map((s) => `- ${s}`).join("\n") : "";
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function section(label: string, body: string): string {
|
|
54
|
-
const trimmed = body.trim();
|
|
55
|
-
return trimmed ? `### ${label}\n${trimmed}` : "";
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
function joinSections(parts: string[]): string {
|
|
59
|
-
return parts.filter(Boolean).join("\n\n");
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
function safeParse<T = unknown>(s: string): T | null {
|
|
63
|
-
try { return JSON.parse(s) as T; } catch { return null; }
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export function renderSopMarkdown(input: string | SOPData | null | undefined): string {
|
|
67
|
-
if (input == null) return "";
|
|
68
|
-
const data: SOPData = typeof input === "string"
|
|
69
|
-
? (safeParse<SOPData>(input) || {})
|
|
70
|
-
: input;
|
|
71
|
-
|
|
72
|
-
const parts: string[] = [];
|
|
73
|
-
if (data.purpose) parts.push(section("Purpose", data.purpose));
|
|
74
|
-
if (data.scope) parts.push(section("Scope", data.scope));
|
|
75
|
-
if (data.applicability) parts.push(section("Applicability", data.applicability));
|
|
76
|
-
|
|
77
|
-
const prereq = data.prerequisites;
|
|
78
|
-
if (prereq) {
|
|
79
|
-
const pieces: string[] = [];
|
|
80
|
-
const cond = (prereq.conditions || []).map((c) => c?.text).filter(Boolean) as string[];
|
|
81
|
-
if (cond.length) pieces.push(`**Conditions:**\n${bullet(cond)}`);
|
|
82
|
-
if (prereq.required_tools?.length) pieces.push(`**Required tools:**\n${bullet(prereq.required_tools)}`);
|
|
83
|
-
if (prereq.required_permissions?.length) pieces.push(`**Required permissions:**\n${bullet(prereq.required_permissions)}`);
|
|
84
|
-
if (prereq.safety_warnings?.length) pieces.push(`**Safety warnings:**\n${bullet(prereq.safety_warnings)}`);
|
|
85
|
-
if (pieces.length) parts.push(section("Prerequisites", pieces.join("\n\n")));
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
if (data.procedure_steps?.length) {
|
|
89
|
-
const steps = data.procedure_steps.map((step, i) => {
|
|
90
|
-
const lines: string[] = [`${i + 1}. **${step.name || `Step ${i + 1}`}**`];
|
|
91
|
-
if (step.action) lines.push(` - Action: ${step.action}`);
|
|
92
|
-
if (step.owner) lines.push(` - Owner: ${step.owner}`);
|
|
93
|
-
if (step.output) lines.push(` - Output: ${step.output}`);
|
|
94
|
-
if (step.decision_logic) lines.push(` - Decision logic: ${step.decision_logic}`);
|
|
95
|
-
if (step.failure_state) lines.push(` - Failure state: ${step.failure_state}`);
|
|
96
|
-
if (step.fallback_action) lines.push(` - Fallback: ${step.fallback_action}`);
|
|
97
|
-
if (step.estimated_duration) lines.push(` - ETA: ${step.estimated_duration}`);
|
|
98
|
-
return lines.join("\n");
|
|
99
|
-
}).join("\n");
|
|
100
|
-
parts.push(section("Procedure", steps));
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
if (data.expected_outputs?.length) parts.push(section("Expected Outputs", bullet(data.expected_outputs)));
|
|
104
|
-
if (data.acceptance_criteria?.length) {
|
|
105
|
-
parts.push(section("Acceptance Criteria", bullet(data.acceptance_criteria.map((c) => c?.text || ""))));
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
if (data.escalation_rules?.length) {
|
|
109
|
-
const rules = data.escalation_rules
|
|
110
|
-
.filter((r) => r.trigger || r.escalateTo)
|
|
111
|
-
.map((r) => `- [${r.priority || "P2"}] ${r.trigger || "(unspecified trigger)"} → ${r.escalateTo || "(unspecified)"}`)
|
|
112
|
-
.join("\n");
|
|
113
|
-
if (rules) parts.push(section("Escalation", rules));
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
if (data.rollback_procedure) parts.push(section("Rollback", data.rollback_procedure));
|
|
117
|
-
if (data.notes) parts.push(section("Notes", data.notes));
|
|
118
|
-
|
|
119
|
-
return joinSections(parts);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
export function renderFrameworkMarkdown(input: string | FrameworkData | null | undefined): string {
|
|
123
|
-
if (input == null) return "";
|
|
124
|
-
const data: FrameworkData = typeof input === "string"
|
|
125
|
-
? (safeParse<FrameworkData>(input) || {})
|
|
126
|
-
: input;
|
|
127
|
-
|
|
128
|
-
const parts: string[] = [];
|
|
129
|
-
if (data.mission) parts.push(section("Mission", data.mission));
|
|
130
|
-
if (data.vision) parts.push(section("Vision", data.vision));
|
|
131
|
-
if (data.core_principles?.length) parts.push(section("Core Principles", bullet(data.core_principles)));
|
|
132
|
-
|
|
133
|
-
const authParts: string[] = [];
|
|
134
|
-
if (data.decision_authority) authParts.push(`**Decision authority:** ${data.decision_authority}`);
|
|
135
|
-
if (data.budget_constraints) authParts.push(`**Budget constraints:** ${data.budget_constraints}`);
|
|
136
|
-
if (data.resource_limits) authParts.push(`**Resource limits:** ${data.resource_limits}`);
|
|
137
|
-
if (data.tool_stack?.length) authParts.push(`**Tool stack:**\n${bullet(data.tool_stack)}`);
|
|
138
|
-
if (authParts.length) parts.push(section("Authority & Constraints", authParts.join("\n\n")));
|
|
139
|
-
|
|
140
|
-
if (data.strategic_objectives?.length) {
|
|
141
|
-
const rows = data.strategic_objectives
|
|
142
|
-
.filter((o) => o.objective || o.target)
|
|
143
|
-
.map((o) => {
|
|
144
|
-
const bits = [
|
|
145
|
-
o.objective ? `**${o.objective}**` : "",
|
|
146
|
-
o.target ? `target: ${o.target}` : "",
|
|
147
|
-
o.measurement ? `measure: ${o.measurement}` : "",
|
|
148
|
-
o.frequency ? `cadence: ${o.frequency}` : "",
|
|
149
|
-
].filter(Boolean);
|
|
150
|
-
return `- ${bits.join(" · ")}`;
|
|
151
|
-
})
|
|
152
|
-
.join("\n");
|
|
153
|
-
if (rows) parts.push(section("Strategic Objectives (KPIs)", rows));
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
const riskParts: string[] = [];
|
|
157
|
-
if (data.risk_appetite) riskParts.push(`**Risk appetite:** ${data.risk_appetite}`);
|
|
158
|
-
if (data.compliance_requirements?.length) riskParts.push(`**Compliance:**\n${bullet(data.compliance_requirements)}`);
|
|
159
|
-
if (riskParts.length) parts.push(section("Risk & Compliance", riskParts.join("\n\n")));
|
|
160
|
-
|
|
161
|
-
if (data.escalation_matrix?.length) {
|
|
162
|
-
const rows = data.escalation_matrix
|
|
163
|
-
.filter((e) => e.trigger || e.escalate_to)
|
|
164
|
-
.map((e) => {
|
|
165
|
-
const sla = e.response_sla ? ` (SLA ${e.response_sla})` : "";
|
|
166
|
-
return `- [${e.priority || "P2"}] ${e.trigger || "(unspecified)"} → ${e.escalate_to || "(unspecified)"}${sla}`;
|
|
167
|
-
})
|
|
168
|
-
.join("\n");
|
|
169
|
-
if (rows) parts.push(section("Escalation Matrix", rows));
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
const teamParts: string[] = [];
|
|
173
|
-
if (data.team_composition) teamParts.push(`**Team:** ${data.team_composition}`);
|
|
174
|
-
if (data.integration_points?.length) teamParts.push(`**Integration points:**\n${bullet(data.integration_points)}`);
|
|
175
|
-
if (teamParts.length) parts.push(section("Team & Integrations", teamParts.join("\n\n")));
|
|
176
|
-
|
|
177
|
-
if (data.review_frequency || data.next_review) {
|
|
178
|
-
const lines = [
|
|
179
|
-
data.review_frequency ? `**Review cadence:** ${data.review_frequency}` : "",
|
|
180
|
-
data.last_reviewed ? `**Last reviewed:** ${data.last_reviewed}` : "",
|
|
181
|
-
data.next_review ? `**Next review:** ${data.next_review}` : "",
|
|
182
|
-
].filter(Boolean);
|
|
183
|
-
if (lines.length) parts.push(section("Review Cycle", lines.join("\n")));
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
if (data.notes) parts.push(section("Notes", data.notes));
|
|
187
|
-
|
|
188
|
-
return joinSections(parts);
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
export function renderAttachmentBlock(args: {
|
|
192
|
-
sops?: Array<{ title: string; content: string }>;
|
|
193
|
-
frameworks?: Array<{ title: string; content: string }>;
|
|
194
|
-
}): string {
|
|
195
|
-
const blocks: string[] = [];
|
|
196
|
-
|
|
197
|
-
if (args.frameworks?.length) {
|
|
198
|
-
const sections = args.frameworks.map((fw) => {
|
|
199
|
-
const md = renderFrameworkMarkdown(fw.content);
|
|
200
|
-
if (!md) return `## ${fw.title}\n_(empty)_`;
|
|
201
|
-
return `## ${fw.title}\n${md}`;
|
|
202
|
-
}).join("\n\n");
|
|
203
|
-
blocks.push(`[FRAMEWORKS — ATTACHED]\nThese frameworks are active for this run. Orient strategic decisions toward them.\n\n${sections}`);
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
if (args.sops?.length) {
|
|
207
|
-
const sections = args.sops.map((sop) => {
|
|
208
|
-
const md = renderSopMarkdown(sop.content);
|
|
209
|
-
if (!md) return `## ${sop.title}\n_(empty)_`;
|
|
210
|
-
return `## ${sop.title}\n${md}`;
|
|
211
|
-
}).join("\n\n");
|
|
212
|
-
blocks.push(`[SOPS — ATTACHED]\nThese SOPs are active for this run. Follow the procedure steps and escalation rules.\n\n${sections}`);
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
return blocks.join("\n\n");
|
|
216
|
-
}
|
|
1
|
+
// src/sop-render.ts — Plugin-side markdown renderer for SOPs and Frameworks.
|
|
2
|
+
// Mirrors dashboard/lib/sopRender.ts. Output is what gets prepended/appended to
|
|
3
|
+
// the system prompt during `before_prompt_build`.
|
|
4
|
+
|
|
5
|
+
interface SOPCheckItem { text?: string; checked?: boolean; }
|
|
6
|
+
interface SOPStep {
|
|
7
|
+
name?: string; action?: string; owner?: string; output?: string;
|
|
8
|
+
decision_logic?: string; failure_state?: string; fallback_action?: string;
|
|
9
|
+
estimated_duration?: string;
|
|
10
|
+
}
|
|
11
|
+
interface SOPEscalationRule { trigger?: string; escalateTo?: string; priority?: string; }
|
|
12
|
+
interface SOPPrerequisites {
|
|
13
|
+
conditions?: SOPCheckItem[]; required_tools?: string[];
|
|
14
|
+
required_permissions?: string[]; safety_warnings?: string[];
|
|
15
|
+
}
|
|
16
|
+
export interface SOPData {
|
|
17
|
+
title?: string;
|
|
18
|
+
purpose?: string; scope?: string; applicability?: string;
|
|
19
|
+
prerequisites?: SOPPrerequisites;
|
|
20
|
+
procedure_steps?: SOPStep[];
|
|
21
|
+
expected_outputs?: string[];
|
|
22
|
+
acceptance_criteria?: SOPCheckItem[];
|
|
23
|
+
escalation_rules?: SOPEscalationRule[];
|
|
24
|
+
rollback_procedure?: string;
|
|
25
|
+
notes?: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface FrameworkObjective {
|
|
29
|
+
objective?: string; target?: string; measurement?: string; frequency?: string;
|
|
30
|
+
}
|
|
31
|
+
interface FrameworkEscalation {
|
|
32
|
+
trigger?: string; escalate_to?: string; priority?: string; response_sla?: string;
|
|
33
|
+
}
|
|
34
|
+
export interface FrameworkData {
|
|
35
|
+
title?: string;
|
|
36
|
+
mission?: string; vision?: string;
|
|
37
|
+
core_principles?: string[];
|
|
38
|
+
decision_authority?: string; budget_constraints?: string;
|
|
39
|
+
resource_limits?: string; tool_stack?: string[];
|
|
40
|
+
strategic_objectives?: FrameworkObjective[];
|
|
41
|
+
risk_appetite?: string; compliance_requirements?: string[];
|
|
42
|
+
escalation_matrix?: FrameworkEscalation[];
|
|
43
|
+
team_composition?: string; integration_points?: string[];
|
|
44
|
+
review_frequency?: string; last_reviewed?: string; next_review?: string;
|
|
45
|
+
notes?: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function bullet(items: (string | undefined | null)[]): string {
|
|
49
|
+
const cleaned = items.map((s) => (s ?? "").toString().trim()).filter(Boolean);
|
|
50
|
+
return cleaned.length ? cleaned.map((s) => `- ${s}`).join("\n") : "";
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function section(label: string, body: string): string {
|
|
54
|
+
const trimmed = body.trim();
|
|
55
|
+
return trimmed ? `### ${label}\n${trimmed}` : "";
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function joinSections(parts: string[]): string {
|
|
59
|
+
return parts.filter(Boolean).join("\n\n");
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function safeParse<T = unknown>(s: string): T | null {
|
|
63
|
+
try { return JSON.parse(s) as T; } catch { return null; }
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function renderSopMarkdown(input: string | SOPData | null | undefined): string {
|
|
67
|
+
if (input == null) return "";
|
|
68
|
+
const data: SOPData = typeof input === "string"
|
|
69
|
+
? (safeParse<SOPData>(input) || {})
|
|
70
|
+
: input;
|
|
71
|
+
|
|
72
|
+
const parts: string[] = [];
|
|
73
|
+
if (data.purpose) parts.push(section("Purpose", data.purpose));
|
|
74
|
+
if (data.scope) parts.push(section("Scope", data.scope));
|
|
75
|
+
if (data.applicability) parts.push(section("Applicability", data.applicability));
|
|
76
|
+
|
|
77
|
+
const prereq = data.prerequisites;
|
|
78
|
+
if (prereq) {
|
|
79
|
+
const pieces: string[] = [];
|
|
80
|
+
const cond = (prereq.conditions || []).map((c) => c?.text).filter(Boolean) as string[];
|
|
81
|
+
if (cond.length) pieces.push(`**Conditions:**\n${bullet(cond)}`);
|
|
82
|
+
if (prereq.required_tools?.length) pieces.push(`**Required tools:**\n${bullet(prereq.required_tools)}`);
|
|
83
|
+
if (prereq.required_permissions?.length) pieces.push(`**Required permissions:**\n${bullet(prereq.required_permissions)}`);
|
|
84
|
+
if (prereq.safety_warnings?.length) pieces.push(`**Safety warnings:**\n${bullet(prereq.safety_warnings)}`);
|
|
85
|
+
if (pieces.length) parts.push(section("Prerequisites", pieces.join("\n\n")));
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (data.procedure_steps?.length) {
|
|
89
|
+
const steps = data.procedure_steps.map((step, i) => {
|
|
90
|
+
const lines: string[] = [`${i + 1}. **${step.name || `Step ${i + 1}`}**`];
|
|
91
|
+
if (step.action) lines.push(` - Action: ${step.action}`);
|
|
92
|
+
if (step.owner) lines.push(` - Owner: ${step.owner}`);
|
|
93
|
+
if (step.output) lines.push(` - Output: ${step.output}`);
|
|
94
|
+
if (step.decision_logic) lines.push(` - Decision logic: ${step.decision_logic}`);
|
|
95
|
+
if (step.failure_state) lines.push(` - Failure state: ${step.failure_state}`);
|
|
96
|
+
if (step.fallback_action) lines.push(` - Fallback: ${step.fallback_action}`);
|
|
97
|
+
if (step.estimated_duration) lines.push(` - ETA: ${step.estimated_duration}`);
|
|
98
|
+
return lines.join("\n");
|
|
99
|
+
}).join("\n");
|
|
100
|
+
parts.push(section("Procedure", steps));
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (data.expected_outputs?.length) parts.push(section("Expected Outputs", bullet(data.expected_outputs)));
|
|
104
|
+
if (data.acceptance_criteria?.length) {
|
|
105
|
+
parts.push(section("Acceptance Criteria", bullet(data.acceptance_criteria.map((c) => c?.text || ""))));
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (data.escalation_rules?.length) {
|
|
109
|
+
const rules = data.escalation_rules
|
|
110
|
+
.filter((r) => r.trigger || r.escalateTo)
|
|
111
|
+
.map((r) => `- [${r.priority || "P2"}] ${r.trigger || "(unspecified trigger)"} → ${r.escalateTo || "(unspecified)"}`)
|
|
112
|
+
.join("\n");
|
|
113
|
+
if (rules) parts.push(section("Escalation", rules));
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (data.rollback_procedure) parts.push(section("Rollback", data.rollback_procedure));
|
|
117
|
+
if (data.notes) parts.push(section("Notes", data.notes));
|
|
118
|
+
|
|
119
|
+
return joinSections(parts);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function renderFrameworkMarkdown(input: string | FrameworkData | null | undefined): string {
|
|
123
|
+
if (input == null) return "";
|
|
124
|
+
const data: FrameworkData = typeof input === "string"
|
|
125
|
+
? (safeParse<FrameworkData>(input) || {})
|
|
126
|
+
: input;
|
|
127
|
+
|
|
128
|
+
const parts: string[] = [];
|
|
129
|
+
if (data.mission) parts.push(section("Mission", data.mission));
|
|
130
|
+
if (data.vision) parts.push(section("Vision", data.vision));
|
|
131
|
+
if (data.core_principles?.length) parts.push(section("Core Principles", bullet(data.core_principles)));
|
|
132
|
+
|
|
133
|
+
const authParts: string[] = [];
|
|
134
|
+
if (data.decision_authority) authParts.push(`**Decision authority:** ${data.decision_authority}`);
|
|
135
|
+
if (data.budget_constraints) authParts.push(`**Budget constraints:** ${data.budget_constraints}`);
|
|
136
|
+
if (data.resource_limits) authParts.push(`**Resource limits:** ${data.resource_limits}`);
|
|
137
|
+
if (data.tool_stack?.length) authParts.push(`**Tool stack:**\n${bullet(data.tool_stack)}`);
|
|
138
|
+
if (authParts.length) parts.push(section("Authority & Constraints", authParts.join("\n\n")));
|
|
139
|
+
|
|
140
|
+
if (data.strategic_objectives?.length) {
|
|
141
|
+
const rows = data.strategic_objectives
|
|
142
|
+
.filter((o) => o.objective || o.target)
|
|
143
|
+
.map((o) => {
|
|
144
|
+
const bits = [
|
|
145
|
+
o.objective ? `**${o.objective}**` : "",
|
|
146
|
+
o.target ? `target: ${o.target}` : "",
|
|
147
|
+
o.measurement ? `measure: ${o.measurement}` : "",
|
|
148
|
+
o.frequency ? `cadence: ${o.frequency}` : "",
|
|
149
|
+
].filter(Boolean);
|
|
150
|
+
return `- ${bits.join(" · ")}`;
|
|
151
|
+
})
|
|
152
|
+
.join("\n");
|
|
153
|
+
if (rows) parts.push(section("Strategic Objectives (KPIs)", rows));
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const riskParts: string[] = [];
|
|
157
|
+
if (data.risk_appetite) riskParts.push(`**Risk appetite:** ${data.risk_appetite}`);
|
|
158
|
+
if (data.compliance_requirements?.length) riskParts.push(`**Compliance:**\n${bullet(data.compliance_requirements)}`);
|
|
159
|
+
if (riskParts.length) parts.push(section("Risk & Compliance", riskParts.join("\n\n")));
|
|
160
|
+
|
|
161
|
+
if (data.escalation_matrix?.length) {
|
|
162
|
+
const rows = data.escalation_matrix
|
|
163
|
+
.filter((e) => e.trigger || e.escalate_to)
|
|
164
|
+
.map((e) => {
|
|
165
|
+
const sla = e.response_sla ? ` (SLA ${e.response_sla})` : "";
|
|
166
|
+
return `- [${e.priority || "P2"}] ${e.trigger || "(unspecified)"} → ${e.escalate_to || "(unspecified)"}${sla}`;
|
|
167
|
+
})
|
|
168
|
+
.join("\n");
|
|
169
|
+
if (rows) parts.push(section("Escalation Matrix", rows));
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const teamParts: string[] = [];
|
|
173
|
+
if (data.team_composition) teamParts.push(`**Team:** ${data.team_composition}`);
|
|
174
|
+
if (data.integration_points?.length) teamParts.push(`**Integration points:**\n${bullet(data.integration_points)}`);
|
|
175
|
+
if (teamParts.length) parts.push(section("Team & Integrations", teamParts.join("\n\n")));
|
|
176
|
+
|
|
177
|
+
if (data.review_frequency || data.next_review) {
|
|
178
|
+
const lines = [
|
|
179
|
+
data.review_frequency ? `**Review cadence:** ${data.review_frequency}` : "",
|
|
180
|
+
data.last_reviewed ? `**Last reviewed:** ${data.last_reviewed}` : "",
|
|
181
|
+
data.next_review ? `**Next review:** ${data.next_review}` : "",
|
|
182
|
+
].filter(Boolean);
|
|
183
|
+
if (lines.length) parts.push(section("Review Cycle", lines.join("\n")));
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (data.notes) parts.push(section("Notes", data.notes));
|
|
187
|
+
|
|
188
|
+
return joinSections(parts);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export function renderAttachmentBlock(args: {
|
|
192
|
+
sops?: Array<{ title: string; content: string }>;
|
|
193
|
+
frameworks?: Array<{ title: string; content: string }>;
|
|
194
|
+
}): string {
|
|
195
|
+
const blocks: string[] = [];
|
|
196
|
+
|
|
197
|
+
if (args.frameworks?.length) {
|
|
198
|
+
const sections = args.frameworks.map((fw) => {
|
|
199
|
+
const md = renderFrameworkMarkdown(fw.content);
|
|
200
|
+
if (!md) return `## ${fw.title}\n_(empty)_`;
|
|
201
|
+
return `## ${fw.title}\n${md}`;
|
|
202
|
+
}).join("\n\n");
|
|
203
|
+
blocks.push(`[FRAMEWORKS — ATTACHED]\nThese frameworks are active for this run. Orient strategic decisions toward them.\n\n${sections}`);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (args.sops?.length) {
|
|
207
|
+
const sections = args.sops.map((sop) => {
|
|
208
|
+
const md = renderSopMarkdown(sop.content);
|
|
209
|
+
if (!md) return `## ${sop.title}\n_(empty)_`;
|
|
210
|
+
return `## ${sop.title}\n${md}`;
|
|
211
|
+
}).join("\n\n");
|
|
212
|
+
blocks.push(`[SOPS — ATTACHED]\nThese SOPs are active for this run. Follow the procedure steps and escalation rules.\n\n${sections}`);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return blocks.join("\n\n");
|
|
216
|
+
}
|
package/src/supabase.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import { createClient, type SupabaseClient } from "@supabase/supabase-js";
|
|
2
|
-
|
|
3
|
-
let _client: SupabaseClient | null = null;
|
|
4
|
-
|
|
5
|
-
export function getSupabase(supabaseUrl: string, serviceRoleKey: string): SupabaseClient {
|
|
6
|
-
if (_client) return _client;
|
|
7
|
-
|
|
8
|
-
_client = createClient(supabaseUrl, serviceRoleKey, {
|
|
9
|
-
auth: { persistSession: false },
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
return _client;
|
|
13
|
-
}
|
|
1
|
+
import { createClient, type SupabaseClient } from "@supabase/supabase-js";
|
|
2
|
+
|
|
3
|
+
let _client: SupabaseClient | null = null;
|
|
4
|
+
|
|
5
|
+
export function getSupabase(supabaseUrl: string, serviceRoleKey: string): SupabaseClient {
|
|
6
|
+
if (_client) return _client;
|
|
7
|
+
|
|
8
|
+
_client = createClient(supabaseUrl, serviceRoleKey, {
|
|
9
|
+
auth: { persistSession: false },
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
return _client;
|
|
13
|
+
}
|
package/src/tools.ts
CHANGED
|
@@ -69,6 +69,20 @@ function err(message: string): ToolResult {
|
|
|
69
69
|
};
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
+
/**
|
|
73
|
+
* D1 fix (2026-05-23): true when an LLM-supplied value is an "empty echo" —
|
|
74
|
+
* a field the model parroted back from a prior read with no real new content
|
|
75
|
+
* (null/undefined, empty or whitespace-only string, or empty array). The task
|
|
76
|
+
* update path is LLM-only; without this guard an echoed blank overwrites a
|
|
77
|
+
* real column. Mirrors isEmptyEcho in dashboard/lib/ofie/tool-executor.ts.
|
|
78
|
+
*/
|
|
79
|
+
function isEmptyEcho(v: unknown): boolean {
|
|
80
|
+
if (v === null || v === undefined) return true;
|
|
81
|
+
if (typeof v === "string") return v.trim() === "";
|
|
82
|
+
if (Array.isArray(v)) return v.length === 0;
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
|
|
72
86
|
// ─── Subagent ↔ chief invariant ──────────────────────────────────────────────
|
|
73
87
|
// Mirrors dashboard/lib/subagentValidation.ts. Used by TASK_OPS + SCHEDULE_OPS
|
|
74
88
|
// when a chief delegates work to one of their staff via tool call.
|
|
@@ -1095,8 +1109,16 @@ async function handleUpdateTask(
|
|
|
1095
1109
|
// error instead of nulling the column. Coerce empty/whitespace → null here
|
|
1096
1110
|
// to match the dashboard native executor (see tool-executor.ts handleTaskOps).
|
|
1097
1111
|
const TIMESTAMP_FIELDS = ["start_date", "due_date"];
|
|
1112
|
+
// D1 parity fix (2026-05-23): this update path is LLM-only. A completing
|
|
1113
|
+
// agent routinely echoes the whole task object back with blank values for
|
|
1114
|
+
// fields it did not change; without this guard those blanks overwrite real
|
|
1115
|
+
// columns. An empty echo on a structural field means "leave unchanged".
|
|
1116
|
+
// status/priority/progress are excluded (0 is valid); start_date/due_date
|
|
1117
|
+
// keep the Fix #8 empty-string -> null coercion below.
|
|
1118
|
+
const STRUCTURE_FIELDS = ["title", "description", "agent_id", "tags"];
|
|
1098
1119
|
for (const f of fields) {
|
|
1099
1120
|
if (params[f] !== undefined) {
|
|
1121
|
+
if (STRUCTURE_FIELDS.includes(f) && isEmptyEcho(params[f])) continue;
|
|
1100
1122
|
if (TIMESTAMP_FIELDS.includes(f)) {
|
|
1101
1123
|
const v = params[f];
|
|
1102
1124
|
updates[f] = (typeof v === "string" && v.trim() === "") ? null : v;
|
|
@@ -1147,12 +1169,15 @@ async function handleUpdateTask(
|
|
|
1147
1169
|
}
|
|
1148
1170
|
}
|
|
1149
1171
|
|
|
1150
|
-
// If task is being marked DONE or FAILED, auto-complete any linked scheduler events
|
|
1172
|
+
// If task is being marked DONE or FAILED, auto-complete any linked scheduler events.
|
|
1173
|
+
// A-4 plugin parity (2026-05-18) — scope by user_id so cross-tenant task_id
|
|
1174
|
+
// can't trigger foreign scheduler flip via plugin tool path.
|
|
1151
1175
|
if (params.status === "DONE" || params.status === "FAILED") {
|
|
1152
1176
|
try {
|
|
1153
1177
|
await supabase
|
|
1154
1178
|
.from("scheduler_events")
|
|
1155
1179
|
.update({ status: "completed", next_run_at: null, updated_at: new Date().toISOString() })
|
|
1180
|
+
.eq("user_id", userId)
|
|
1156
1181
|
.eq("task_id", params.task_id as string);
|
|
1157
1182
|
} catch (_schedErr) {
|
|
1158
1183
|
// Non-fatal: task update should still proceed
|
|
@@ -2464,7 +2489,9 @@ function registerWorkflowOps(
|
|
|
2464
2489
|
case "delete": {
|
|
2465
2490
|
const wfId = (params.id || params.workflow_id) as string;
|
|
2466
2491
|
if (!wfId) return err("Missing required: id");
|
|
2467
|
-
|
|
2492
|
+
// A-4 plugin parity (2026-05-18) — scope cascade so cross-tenant
|
|
2493
|
+
// workflow_id can't trigger foreign workflow_runs wipe via plugin.
|
|
2494
|
+
await supabase.from("workflow_runs").delete().eq("user_id", userId).eq("workflow_id", wfId);
|
|
2468
2495
|
const { error } = await supabase.from("workflows").delete().eq("id", wfId).eq("user_id", userId);
|
|
2469
2496
|
if (error) return err(error.message);
|
|
2470
2497
|
return ok({ message: "Workflow and associated runs deleted", ok: true });
|
|
@@ -4931,7 +4958,7 @@ function registerPlanOps(
|
|
|
4931
4958
|
`- "delete": Remove plan. Required: plan_id\n` +
|
|
4932
4959
|
`- "add_nodes": Add nodes to plan. Required: plan_id, nodes[]. Optional: parent_node_id\n` +
|
|
4933
4960
|
`- "execute": Deploy plan to real PM tasks. Required: plan_id. Optional: create_folder, create_scheduler, space_id, folder_id\n\n` +
|
|
4934
|
-
`Node structure: { type, title, description?, agent_id?, priority?, status?, start_date?, due_date?, tags?, execution_steps?[{text}], goals?[{label,type?}], constraints?[{label,type?}], system_prompt?, children?[], parallel? }`,
|
|
4961
|
+
`Node structure: { type, title, description?, agent_id?, priority?, status?, start_date?, due_date?, tags?, execution_steps?[{text}], goals?[{label,type?}], constraints?[{label,type?}], system_prompt?, gate_condition?, children?[], parallel? }`,
|
|
4935
4962
|
parameters: {
|
|
4936
4963
|
type: "object",
|
|
4937
4964
|
required: ["action"],
|
|
@@ -4964,8 +4991,13 @@ function registerPlanOps(
|
|
|
4964
4991
|
goals: { type: "array", items: { type: "object", properties: { label: { type: "string" }, type: { type: "string" } }, required: ["label"] } },
|
|
4965
4992
|
constraints: { type: "array", items: { type: "object", properties: { label: { type: "string" }, type: { type: "string" } }, required: ["label"] } },
|
|
4966
4993
|
system_prompt: { type: "string" },
|
|
4994
|
+
gate_condition: {
|
|
4995
|
+
type: "string",
|
|
4996
|
+
enum: ["manual", "all_predecessors_complete", "any_predecessor_complete"],
|
|
4997
|
+
description: 'For type:"gate" nodes only. manual = human approval required; all_predecessors_complete = AND-join (default for gates); any_predecessor_complete = OR-join.',
|
|
4998
|
+
},
|
|
4967
4999
|
parallel: { type: "boolean" },
|
|
4968
|
-
children: { type: "array", description: "Nested child nodes (recursive)", items: { type: "object", properties: { title: { type: "string" }, type: { type: "string", enum: ["task", "gate", "milestone"] }, description: { type: "string" }, agent_id: { type: "string" }, priority: { type: "number" }, status: { type: "string" }, start_date: { type: "string" }, due_date: { type: "string" }, parallel: { type: "boolean" } }, required: ["title"] } },
|
|
5000
|
+
children: { type: "array", description: "Nested child nodes (recursive)", items: { type: "object", properties: { title: { type: "string" }, type: { type: "string", enum: ["task", "gate", "milestone"] }, description: { type: "string" }, agent_id: { type: "string" }, priority: { type: "number" }, status: { type: "string" }, start_date: { type: "string" }, due_date: { type: "string" }, parallel: { type: "boolean" }, gate_condition: { type: "string", enum: ["manual", "all_predecessors_complete", "any_predecessor_complete"] } }, required: ["title"] } },
|
|
4969
5001
|
},
|
|
4970
5002
|
required: ["title"],
|
|
4971
5003
|
},
|
|
@@ -4998,6 +5030,17 @@ function generatePlanId(): string {
|
|
|
4998
5030
|
return crypto.randomUUID();
|
|
4999
5031
|
}
|
|
5000
5032
|
|
|
5033
|
+
const VALID_GATE_CONDITIONS = ["manual", "all_predecessors_complete", "any_predecessor_complete"] as const;
|
|
5034
|
+
|
|
5035
|
+
/** Validate a plan node's gate_condition. Invalid/absent values fall back to the
|
|
5036
|
+
* existing default — gates get "all_predecessors_complete", non-gates get nothing. */
|
|
5037
|
+
function normalizeGateCondition(v: unknown, nodeType: unknown): string | undefined {
|
|
5038
|
+
if (typeof v === "string" && (VALID_GATE_CONDITIONS as readonly string[]).includes(v)) {
|
|
5039
|
+
return v;
|
|
5040
|
+
}
|
|
5041
|
+
return nodeType === "gate" ? "all_predecessors_complete" : undefined;
|
|
5042
|
+
}
|
|
5043
|
+
|
|
5001
5044
|
/** Normalize agent-provided node into the PlanNode shape stored in plan_data */
|
|
5002
5045
|
function normalizeNode(raw: any): any {
|
|
5003
5046
|
const id = raw.id || generatePlanNodeId();
|
|
@@ -5024,7 +5067,7 @@ function normalizeNode(raw: any): any {
|
|
|
5024
5067
|
? raw.constraints.map((c: any, i: number) => ({ id: `cstr-${Date.now()}-${i}`, type: c.type || "custom", label: typeof c === "string" ? c : c.label || String(c) }))
|
|
5025
5068
|
: undefined,
|
|
5026
5069
|
systemPrompt: raw.system_prompt || raw.systemPrompt || undefined,
|
|
5027
|
-
gateCondition: raw.gate_condition || raw.gateCondition
|
|
5070
|
+
gateCondition: normalizeGateCondition(raw.gate_condition || raw.gateCondition, raw.type),
|
|
5028
5071
|
children,
|
|
5029
5072
|
parallel: raw.parallel || false,
|
|
5030
5073
|
collapsed: false,
|
package/src/types/openclaw.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
// Ambient declaration for the OpenClaw plugin SDK. The gateway resolves this
|
|
2
|
-
// module at runtime; npm has no published types. Keep loose — the plugin only
|
|
3
|
-
// uses a small surface (logger, on, registerTool, registerCommand, etc.).
|
|
4
|
-
|
|
5
|
-
declare module "openclaw/plugin-sdk" {
|
|
6
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
7
|
-
export type OpenClawPluginApi = any;
|
|
8
|
-
}
|
|
1
|
+
// Ambient declaration for the OpenClaw plugin SDK. The gateway resolves this
|
|
2
|
+
// module at runtime; npm has no published types. Keep loose — the plugin only
|
|
3
|
+
// uses a small surface (logger, on, registerTool, registerCommand, etc.).
|
|
4
|
+
|
|
5
|
+
declare module "openclaw/plugin-sdk" {
|
|
6
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
7
|
+
export type OpenClawPluginApi = any;
|
|
8
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
export interface OfiereConfig {
|
|
2
|
-
enabled: boolean;
|
|
3
|
-
supabaseUrl: string;
|
|
4
|
-
serviceRoleKey: string;
|
|
5
|
-
userId: string;
|
|
6
|
-
/** Optional — if not set, agent identity is resolved at runtime from OpenClaw context */
|
|
7
|
-
agentId: string;
|
|
8
|
-
/** IANA timezone string for the user (e.g. 'Asia/Jakarta', 'America/New_York'). Default: 'Asia/Jakarta' */
|
|
9
|
-
timezone: string;
|
|
10
|
-
}
|
|
1
|
+
export interface OfiereConfig {
|
|
2
|
+
enabled: boolean;
|
|
3
|
+
supabaseUrl: string;
|
|
4
|
+
serviceRoleKey: string;
|
|
5
|
+
userId: string;
|
|
6
|
+
/** Optional — if not set, agent identity is resolved at runtime from OpenClaw context */
|
|
7
|
+
agentId: string;
|
|
8
|
+
/** IANA timezone string for the user (e.g. 'Asia/Jakarta', 'America/New_York'). Default: 'Asia/Jakarta' */
|
|
9
|
+
timezone: string;
|
|
10
|
+
}
|