@useorgx/openclaw-plugin 0.4.4 → 0.4.6
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 +85 -2
- package/dashboard/dist/assets/0tOC3wSN.js +214 -0
- package/dashboard/dist/assets/Bm8QnMJ_.js +1 -0
- package/dashboard/dist/assets/CpJsfbXo.js +9 -0
- package/dashboard/dist/assets/CyxZio4Y.js +1 -0
- package/dashboard/dist/assets/DaAIOik3.css +1 -0
- package/dashboard/dist/index.html +3 -3
- package/dist/activity-store.d.ts +28 -0
- package/dist/activity-store.js +250 -0
- package/dist/agent-context-store.d.ts +19 -0
- package/dist/agent-context-store.js +60 -3
- package/dist/agent-suite.d.ts +83 -0
- package/dist/agent-suite.js +615 -0
- package/dist/contracts/client.d.ts +22 -1
- package/dist/contracts/client.js +120 -3
- package/dist/contracts/types.d.ts +190 -1
- package/dist/entity-comment-store.d.ts +29 -0
- package/dist/entity-comment-store.js +190 -0
- package/dist/hooks/post-reporting-event.mjs +326 -0
- package/dist/http-handler.d.ts +7 -1
- package/dist/http-handler.js +3619 -585
- package/dist/index.js +1039 -80
- package/dist/mcp-client-setup.d.ts +30 -0
- package/dist/mcp-client-setup.js +347 -0
- package/dist/mcp-http-handler.d.ts +55 -0
- package/dist/mcp-http-handler.js +395 -0
- package/dist/next-up-queue-store.d.ts +31 -0
- package/dist/next-up-queue-store.js +169 -0
- package/dist/openclaw.plugin.json +1 -1
- package/dist/outbox.d.ts +1 -1
- package/dist/runtime-instance-store.d.ts +1 -1
- package/dist/runtime-instance-store.js +20 -3
- package/dist/skill-pack-state.d.ts +69 -0
- package/dist/skill-pack-state.js +232 -0
- package/dist/worker-supervisor.d.ts +25 -0
- package/dist/worker-supervisor.js +62 -0
- package/openclaw.plugin.json +1 -1
- package/package.json +10 -1
- package/skills/orgx-design-agent/SKILL.md +38 -0
- package/skills/orgx-engineering-agent/SKILL.md +55 -0
- package/skills/orgx-marketing-agent/SKILL.md +40 -0
- package/skills/orgx-operations-agent/SKILL.md +40 -0
- package/skills/orgx-orchestrator-agent/SKILL.md +45 -0
- package/skills/orgx-product-agent/SKILL.md +39 -0
- package/skills/orgx-sales-agent/SKILL.md +40 -0
- package/skills/ship/SKILL.md +63 -0
- package/dashboard/dist/assets/4hvaB0UC.js +0 -9
- package/dashboard/dist/assets/BgsfM2lz.js +0 -1
- package/dashboard/dist/assets/DCBlK4MX.js +0 -212
- package/dashboard/dist/assets/DEuY_RBN.js +0 -1
- package/dashboard/dist/assets/jyFhCND-.css +0 -1
|
@@ -0,0 +1,615 @@
|
|
|
1
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, statSync } from "node:fs";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { dirname, join } from "node:path";
|
|
5
|
+
import { writeFileAtomicSync } from "./fs-utils.js";
|
|
6
|
+
import { getOpenClawDir } from "./paths.js";
|
|
7
|
+
export const ORGX_AGENT_SUITE_PACK_ID = "orgx-agent-suite";
|
|
8
|
+
export const ORGX_AGENT_SUITE_AGENTS = [
|
|
9
|
+
{ id: "orgx-engineering", name: "OrgX Engineering", domain: "engineering" },
|
|
10
|
+
{ id: "orgx-product", name: "OrgX Product", domain: "product" },
|
|
11
|
+
{ id: "orgx-design", name: "OrgX Design", domain: "design" },
|
|
12
|
+
{ id: "orgx-marketing", name: "OrgX Marketing", domain: "marketing" },
|
|
13
|
+
{ id: "orgx-sales", name: "OrgX Sales", domain: "sales" },
|
|
14
|
+
{ id: "orgx-operations", name: "OrgX Operations", domain: "operations" },
|
|
15
|
+
{ id: "orgx-orchestrator", name: "OrgX Orchestrator", domain: "orchestration" },
|
|
16
|
+
];
|
|
17
|
+
const SUITE_WORKSPACE_DIRNAME = "agents";
|
|
18
|
+
const SUITE_MANAGED_DIR = join(".orgx", "managed");
|
|
19
|
+
const SUITE_LOCAL_DIR = join(".orgx", "local");
|
|
20
|
+
const SUITE_FILES = [
|
|
21
|
+
"AGENTS.md",
|
|
22
|
+
"TOOLS.md",
|
|
23
|
+
"IDENTITY.md",
|
|
24
|
+
"SKILL.md",
|
|
25
|
+
"SOUL.md",
|
|
26
|
+
"USER.md",
|
|
27
|
+
"HEARTBEAT.md",
|
|
28
|
+
];
|
|
29
|
+
function isRecord(value) {
|
|
30
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
31
|
+
}
|
|
32
|
+
function parseJsonSafe(raw) {
|
|
33
|
+
try {
|
|
34
|
+
return JSON.parse(raw);
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function isSafeAgentId(value) {
|
|
41
|
+
const trimmed = value.trim();
|
|
42
|
+
if (!trimmed)
|
|
43
|
+
return false;
|
|
44
|
+
return /^[a-z0-9][a-z0-9_-]*$/.test(trimmed);
|
|
45
|
+
}
|
|
46
|
+
function openclawConfigPath(openclawDir) {
|
|
47
|
+
return join(openclawDir, "openclaw.json");
|
|
48
|
+
}
|
|
49
|
+
function readOpenclawConfig(openclawDir) {
|
|
50
|
+
const path = openclawConfigPath(openclawDir);
|
|
51
|
+
try {
|
|
52
|
+
const mode = statSync(path).mode & 0o777;
|
|
53
|
+
const raw = readFileSync(path, "utf8");
|
|
54
|
+
const parsed = parseJsonSafe(raw);
|
|
55
|
+
return { path, parsed: parsed && typeof parsed === "object" ? parsed : null, fileMode: mode || 0o600 };
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
return { path, parsed: null, fileMode: 0o600 };
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
function resolveSuiteWorkspaceRoot(openclaw) {
|
|
62
|
+
const list = Array.isArray(openclaw?.agents?.list) ? openclaw?.agents?.list : [];
|
|
63
|
+
const orgx = list.find((entry) => String(entry?.id ?? "").trim() === "orgx") ?? null;
|
|
64
|
+
const configured = orgx && typeof orgx.workspace === "string" && orgx.workspace.trim().length > 0
|
|
65
|
+
? orgx.workspace.trim()
|
|
66
|
+
: "";
|
|
67
|
+
const base = configured || join(homedir(), "clawd", "workspaces", "orgx");
|
|
68
|
+
return join(base, SUITE_WORKSPACE_DIRNAME);
|
|
69
|
+
}
|
|
70
|
+
function ensureDir(path, mode) {
|
|
71
|
+
mkdirSync(path, { recursive: true, mode });
|
|
72
|
+
}
|
|
73
|
+
function sha256(content) {
|
|
74
|
+
return createHash("sha256").update(content).digest("hex");
|
|
75
|
+
}
|
|
76
|
+
function managedHeader(input) {
|
|
77
|
+
const { packId, packVersion, file, managedSha } = input;
|
|
78
|
+
return [
|
|
79
|
+
`# === ORGX MANAGED (pack: ${packId}@${packVersion}, file: ${file}, sha256: ${managedSha}) ===`,
|
|
80
|
+
"",
|
|
81
|
+
].join("\n");
|
|
82
|
+
}
|
|
83
|
+
function localHeader() {
|
|
84
|
+
return [
|
|
85
|
+
"",
|
|
86
|
+
"# === ORGX LOCAL OVERRIDES (appended verbatim; never overwritten) ===",
|
|
87
|
+
"",
|
|
88
|
+
].join("\n");
|
|
89
|
+
}
|
|
90
|
+
const LOCAL_OVERRIDE_MARKER = "# === ORGX LOCAL OVERRIDES";
|
|
91
|
+
function buildCompositeFile(input) {
|
|
92
|
+
if (!input.localOverride)
|
|
93
|
+
return input.managed;
|
|
94
|
+
return `${input.managed}${localHeader()}${input.localOverride.trimEnd()}\n`;
|
|
95
|
+
}
|
|
96
|
+
function extractLocalOverridesFromComposite(composite) {
|
|
97
|
+
const idx = composite.indexOf(LOCAL_OVERRIDE_MARKER);
|
|
98
|
+
if (idx < 0)
|
|
99
|
+
return null;
|
|
100
|
+
const after = composite.slice(idx);
|
|
101
|
+
const markerEnd = after.indexOf("\n\n");
|
|
102
|
+
const start = markerEnd >= 0 ? idx + markerEnd + 2 : idx;
|
|
103
|
+
const candidate = composite.slice(start).trim();
|
|
104
|
+
return candidate ? `${candidate}\n` : null;
|
|
105
|
+
}
|
|
106
|
+
function loadTextFile(path) {
|
|
107
|
+
try {
|
|
108
|
+
if (!existsSync(path))
|
|
109
|
+
return null;
|
|
110
|
+
return readFileSync(path, "utf8");
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
function normalizeNewlines(value) {
|
|
117
|
+
return value.replace(/\r\n/g, "\n");
|
|
118
|
+
}
|
|
119
|
+
function domainPersona(domain) {
|
|
120
|
+
switch (domain) {
|
|
121
|
+
case "engineering":
|
|
122
|
+
return {
|
|
123
|
+
headline: "Build correct software with proof.",
|
|
124
|
+
voice: ["Direct, calm, technical.", "Prefer concrete evidence over confidence."],
|
|
125
|
+
autonomy: ["Default to implementing the fix.", "Escalate only when a decision is truly required."],
|
|
126
|
+
care: ["Respect time: minimize churn and surprises.", "Explain tradeoffs without lecturing."],
|
|
127
|
+
defaults: ["Reproduce before fixing.", "Add tests when feasible.", "Keep diffs small."],
|
|
128
|
+
};
|
|
129
|
+
case "product":
|
|
130
|
+
return {
|
|
131
|
+
headline: "Turn ambiguity into shippable outcomes.",
|
|
132
|
+
voice: ["Clear, structured, user-centered.", "Make decisions explicit; avoid fuzzy scope."],
|
|
133
|
+
autonomy: ["Propose a smallest viable slice.", "Write acceptance criteria before building."],
|
|
134
|
+
care: ["Call out risks and non-goals early.", "Optimize for the user's confidence and clarity."],
|
|
135
|
+
defaults: ["Define success metrics.", "Document assumptions.", "Keep language concrete."],
|
|
136
|
+
};
|
|
137
|
+
case "design":
|
|
138
|
+
return {
|
|
139
|
+
headline: "Make it feel inevitable and usable.",
|
|
140
|
+
voice: ["Precise, opinionated, kind.", "Avoid generic UI patterns and 'AI slop'."],
|
|
141
|
+
autonomy: ["Iterate fast with constraints.", "Verify mobile + critical states."],
|
|
142
|
+
care: ["Protect coherence of the design system.", "Prioritize accessibility as a baseline."],
|
|
143
|
+
defaults: ["Use tokens.", "Avoid new visual language.", "Capture QA evidence."],
|
|
144
|
+
};
|
|
145
|
+
case "marketing":
|
|
146
|
+
return {
|
|
147
|
+
headline: "Position, prove, and ship to channels.",
|
|
148
|
+
voice: ["Specific, energetic, grounded in reality.", "No generic claims without proof."],
|
|
149
|
+
autonomy: ["Pick a target audience and promise.", "Deliver channel-ready outputs."],
|
|
150
|
+
care: ["Avoid hype that creates trust debt.", "Respect brand voice; keep it crisp."],
|
|
151
|
+
defaults: ["Audience -> promise -> proof -> CTA.", "Include measurement hooks."],
|
|
152
|
+
};
|
|
153
|
+
case "sales":
|
|
154
|
+
return {
|
|
155
|
+
headline: "Help buyers decide with clarity.",
|
|
156
|
+
voice: ["Concise, empathetic, commercially sharp.", "Anticipate objections; answer plainly."],
|
|
157
|
+
autonomy: ["Start with ICP + disqualifiers.", "Write talk tracks that sound human."],
|
|
158
|
+
care: ["Never overclaim.", "Optimize for trust and next steps."],
|
|
159
|
+
defaults: ["MEDDIC-style qualification.", "Objection handling + CTA."],
|
|
160
|
+
};
|
|
161
|
+
case "operations":
|
|
162
|
+
return {
|
|
163
|
+
headline: "Keep systems safe, reliable, and reversible.",
|
|
164
|
+
voice: ["Cautious, thorough, pragmatic.", "Prefer runbooks over heroics."],
|
|
165
|
+
autonomy: ["Default to reversible changes.", "Add guardrails before speed."],
|
|
166
|
+
care: ["Assume production is fragile unless proven otherwise.", "Reduce on-call burden."],
|
|
167
|
+
defaults: ["Rollback paths.", "Detection + alerting.", "Post-incident learning."],
|
|
168
|
+
};
|
|
169
|
+
case "orchestration":
|
|
170
|
+
return {
|
|
171
|
+
headline: "Coordinate workstreams into finished outcomes.",
|
|
172
|
+
voice: ["Structured, decisive, transparent.", "Keep boundaries straight (OrgX vs OpenClaw vs plugin)."],
|
|
173
|
+
autonomy: ["Decompose into verifiable tasks.", "Sequence work to keep momentum."],
|
|
174
|
+
care: ["Minimize context switching.", "Keep stakeholders informed."],
|
|
175
|
+
defaults: ["One unverified item at a time.", "Reference the canonical plan.", "Update statuses with proof."],
|
|
176
|
+
};
|
|
177
|
+
default:
|
|
178
|
+
return {
|
|
179
|
+
headline: "Execute with clarity.",
|
|
180
|
+
voice: ["Direct, pragmatic."],
|
|
181
|
+
autonomy: ["Proceed by default."],
|
|
182
|
+
care: ["Respect time and context."],
|
|
183
|
+
defaults: ["Verify work."],
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
function buildManagedFileContent(input) {
|
|
188
|
+
const persona = domainPersona(input.agent.domain);
|
|
189
|
+
const baseBody = (() => {
|
|
190
|
+
if (input.file === "IDENTITY.md") {
|
|
191
|
+
return [
|
|
192
|
+
`# ${input.agent.name}`,
|
|
193
|
+
"",
|
|
194
|
+
`Domain: ${input.agent.domain}`,
|
|
195
|
+
"",
|
|
196
|
+
`Headline: ${persona.headline}`,
|
|
197
|
+
"",
|
|
198
|
+
"## Voice",
|
|
199
|
+
...persona.voice.map((line) => `- ${line}`),
|
|
200
|
+
"",
|
|
201
|
+
"## Autonomy",
|
|
202
|
+
...persona.autonomy.map((line) => `- ${line}`),
|
|
203
|
+
"",
|
|
204
|
+
"## Consideration",
|
|
205
|
+
...persona.care.map((line) => `- ${line}`),
|
|
206
|
+
"",
|
|
207
|
+
"## Defaults",
|
|
208
|
+
...persona.defaults.map((line) => `- ${line}`),
|
|
209
|
+
"",
|
|
210
|
+
"## Universal Rules",
|
|
211
|
+
"- Use OrgX as source of truth for tasks/decisions/artifacts when present.",
|
|
212
|
+
"- Verify before claiming done (commands/tests/evidence).",
|
|
213
|
+
"- Keep scope tight; do not over-engineer.",
|
|
214
|
+
"- If blocked, propose options and ask for a decision.",
|
|
215
|
+
"",
|
|
216
|
+
].join("\n");
|
|
217
|
+
}
|
|
218
|
+
if (input.file === "TOOLS.md") {
|
|
219
|
+
const scopedTools = {
|
|
220
|
+
engineering: [
|
|
221
|
+
"orgx_status",
|
|
222
|
+
"orgx_sync",
|
|
223
|
+
"orgx_emit_activity",
|
|
224
|
+
"orgx_report_progress",
|
|
225
|
+
"orgx_register_artifact",
|
|
226
|
+
"orgx_request_decision",
|
|
227
|
+
"orgx_spawn_check",
|
|
228
|
+
],
|
|
229
|
+
product: [
|
|
230
|
+
"orgx_status",
|
|
231
|
+
"orgx_sync",
|
|
232
|
+
"orgx_emit_activity",
|
|
233
|
+
"orgx_report_progress",
|
|
234
|
+
"orgx_register_artifact",
|
|
235
|
+
"orgx_request_decision",
|
|
236
|
+
"orgx_spawn_check",
|
|
237
|
+
],
|
|
238
|
+
design: [
|
|
239
|
+
"orgx_status",
|
|
240
|
+
"orgx_sync",
|
|
241
|
+
"orgx_emit_activity",
|
|
242
|
+
"orgx_report_progress",
|
|
243
|
+
"orgx_register_artifact",
|
|
244
|
+
"orgx_request_decision",
|
|
245
|
+
"orgx_spawn_check",
|
|
246
|
+
],
|
|
247
|
+
marketing: [
|
|
248
|
+
"orgx_status",
|
|
249
|
+
"orgx_sync",
|
|
250
|
+
"orgx_emit_activity",
|
|
251
|
+
"orgx_report_progress",
|
|
252
|
+
"orgx_register_artifact",
|
|
253
|
+
"orgx_request_decision",
|
|
254
|
+
"orgx_spawn_check",
|
|
255
|
+
],
|
|
256
|
+
sales: [
|
|
257
|
+
"orgx_status",
|
|
258
|
+
"orgx_sync",
|
|
259
|
+
"orgx_emit_activity",
|
|
260
|
+
"orgx_report_progress",
|
|
261
|
+
"orgx_register_artifact",
|
|
262
|
+
"orgx_request_decision",
|
|
263
|
+
"orgx_spawn_check",
|
|
264
|
+
],
|
|
265
|
+
operations: [
|
|
266
|
+
"orgx_status",
|
|
267
|
+
"orgx_sync",
|
|
268
|
+
"orgx_emit_activity",
|
|
269
|
+
"orgx_report_progress",
|
|
270
|
+
"orgx_register_artifact",
|
|
271
|
+
"orgx_request_decision",
|
|
272
|
+
"orgx_spawn_check",
|
|
273
|
+
"orgx_apply_changeset",
|
|
274
|
+
],
|
|
275
|
+
orchestration: [
|
|
276
|
+
"orgx_status",
|
|
277
|
+
"orgx_sync",
|
|
278
|
+
"orgx_emit_activity",
|
|
279
|
+
"orgx_report_progress",
|
|
280
|
+
"orgx_register_artifact",
|
|
281
|
+
"orgx_request_decision",
|
|
282
|
+
"orgx_spawn_check",
|
|
283
|
+
"orgx_apply_changeset",
|
|
284
|
+
],
|
|
285
|
+
};
|
|
286
|
+
const scopeKey = `orgx-openclaw-${input.agent.domain}`;
|
|
287
|
+
return [
|
|
288
|
+
"# Tools",
|
|
289
|
+
"",
|
|
290
|
+
"Primary tool surface (OrgX MCP tools exposed by this plugin):",
|
|
291
|
+
"- orgx_status",
|
|
292
|
+
"- orgx_sync",
|
|
293
|
+
"- orgx_emit_activity",
|
|
294
|
+
"- orgx_apply_changeset",
|
|
295
|
+
"- orgx_register_artifact",
|
|
296
|
+
"- orgx_request_decision",
|
|
297
|
+
"- orgx_spawn_check",
|
|
298
|
+
"",
|
|
299
|
+
"## Scoped MCP (Recommended)",
|
|
300
|
+
`If your client supports MCP server selection, prefer the scoped server key: \`${scopeKey}\`.`,
|
|
301
|
+
"This enforces a default-safe allowlist for your domain (high-risk tools are hidden/blocked).",
|
|
302
|
+
"",
|
|
303
|
+
"Scoped allowlist:",
|
|
304
|
+
...scopedTools[input.agent.domain].map((tool) => `- ${tool}`),
|
|
305
|
+
"",
|
|
306
|
+
"Rules:",
|
|
307
|
+
"- Return structured JSON for tool outputs when applicable.",
|
|
308
|
+
"- Do not print secrets (API keys, tokens, cookies). Mask as `oxk_...abcd`.",
|
|
309
|
+
"- If a tool fails, capture the exact error and fix root cause.",
|
|
310
|
+
"- Prefer dry-run/previews when writing to user config.",
|
|
311
|
+
"",
|
|
312
|
+
].join("\n");
|
|
313
|
+
}
|
|
314
|
+
if (input.file === "AGENTS.md") {
|
|
315
|
+
return [
|
|
316
|
+
"# Agent Guardrails",
|
|
317
|
+
"",
|
|
318
|
+
"These rules exist to prevent repeat failures: wrong repo/branch, unverified “done”, tool substitution, and shipping without evidence.",
|
|
319
|
+
"",
|
|
320
|
+
"## Humanity",
|
|
321
|
+
"- Be direct and respectful. No shame, no fluff.",
|
|
322
|
+
"- When the user is stressed or blocked, reduce cognitive load: summarize, propose, decide.",
|
|
323
|
+
"",
|
|
324
|
+
"## Read Before You Write",
|
|
325
|
+
"- Read relevant source files before implementing.",
|
|
326
|
+
"- Read primary docs/specs before coding against an integration.",
|
|
327
|
+
"",
|
|
328
|
+
"## Verification Standards",
|
|
329
|
+
"- Run typecheck and the most relevant tests before claiming a fix is verified.",
|
|
330
|
+
"- UI changes: verify desktop + mobile (375px) and key states (loading/error/empty).",
|
|
331
|
+
"",
|
|
332
|
+
"## Repo Hygiene",
|
|
333
|
+
"- Confirm `pwd` and `git status -sb` before edits.",
|
|
334
|
+
"- Prefer feature branches for non-trivial changes.",
|
|
335
|
+
"",
|
|
336
|
+
].join("\n");
|
|
337
|
+
}
|
|
338
|
+
if (input.file === "HEARTBEAT.md") {
|
|
339
|
+
return [
|
|
340
|
+
"# Heartbeat",
|
|
341
|
+
"",
|
|
342
|
+
"Cadence:",
|
|
343
|
+
"- Emit OrgX activity at natural checkpoints: intent, execution, review, completed.",
|
|
344
|
+
"- When blocked: request a decision with options, tradeoffs, and a recommendation.",
|
|
345
|
+
"- When you change direction: explain why in one sentence before switching.",
|
|
346
|
+
"",
|
|
347
|
+
].join("\n");
|
|
348
|
+
}
|
|
349
|
+
if (input.file === "USER.md") {
|
|
350
|
+
return [
|
|
351
|
+
"# User Preferences",
|
|
352
|
+
"",
|
|
353
|
+
"Default assumptions:",
|
|
354
|
+
"- Prefer concise, actionable updates.",
|
|
355
|
+
"- Ask only when necessary; otherwise proceed and show proof.",
|
|
356
|
+
"- Surface assumptions and risks early (before time-consuming work).",
|
|
357
|
+
"- End with next-step options when multiple paths exist.",
|
|
358
|
+
"",
|
|
359
|
+
].join("\n");
|
|
360
|
+
}
|
|
361
|
+
if (input.file === "SKILL.md") {
|
|
362
|
+
const override = input.skillPack?.openclaw_skills?.[input.agent.domain] ?? null;
|
|
363
|
+
const provenance = input.skillPack
|
|
364
|
+
? `SkillPack: ${input.skillPack.name}@${input.skillPack.version} (${input.skillPack.source}, sha256:${input.skillPack.checksum.slice(0, 12)}...)`
|
|
365
|
+
: "SkillPack: builtin (no server pack applied)";
|
|
366
|
+
const generated = [
|
|
367
|
+
`# ${input.agent.name} — Skill`,
|
|
368
|
+
"",
|
|
369
|
+
`Domain: ${input.agent.domain}`,
|
|
370
|
+
"",
|
|
371
|
+
"## Purpose",
|
|
372
|
+
`- ${persona.headline}`,
|
|
373
|
+
"",
|
|
374
|
+
"## Persona",
|
|
375
|
+
"Voice:",
|
|
376
|
+
...persona.voice.map((line) => `- ${line}`),
|
|
377
|
+
"",
|
|
378
|
+
"Autonomy:",
|
|
379
|
+
...persona.autonomy.map((line) => `- ${line}`),
|
|
380
|
+
"",
|
|
381
|
+
"Consideration:",
|
|
382
|
+
...persona.care.map((line) => `- ${line}`),
|
|
383
|
+
"",
|
|
384
|
+
"Defaults:",
|
|
385
|
+
...persona.defaults.map((line) => `- ${line}`),
|
|
386
|
+
"",
|
|
387
|
+
"## Operating Loop",
|
|
388
|
+
"- Clarify the goal and constraints (one sentence each).",
|
|
389
|
+
"- Propose the next 1-3 steps with an explicit recommendation.",
|
|
390
|
+
"- Execute with proof: commands run, files changed, tests/evidence captured.",
|
|
391
|
+
"- When blocked: show the exact error, then offer options with tradeoffs.",
|
|
392
|
+
"",
|
|
393
|
+
"## Reporting",
|
|
394
|
+
"- Post progress at natural checkpoints: intent, execution, review, completed.",
|
|
395
|
+
"- Prefer concrete updates over vibes (what changed, where, how verified).",
|
|
396
|
+
"- If you made a decision, record it as a decision request/result upstream (OrgX).",
|
|
397
|
+
"",
|
|
398
|
+
"## Boundaries",
|
|
399
|
+
"- Do not print secrets. Mask keys as `oxk_...abcd`.",
|
|
400
|
+
"- Avoid destructive git ops unless explicitly requested.",
|
|
401
|
+
"- Keep scope tight: do the asked work, then stop.",
|
|
402
|
+
"",
|
|
403
|
+
"## Provenance",
|
|
404
|
+
`- ${provenance}`,
|
|
405
|
+
"",
|
|
406
|
+
].join("\n");
|
|
407
|
+
// If a server pack provides a SKILL.md, prefer it; otherwise use the generated baseline.
|
|
408
|
+
return override ? String(override).trimEnd() + "\n" : generated + "\n";
|
|
409
|
+
}
|
|
410
|
+
if (input.file === "SOUL.md") {
|
|
411
|
+
return [
|
|
412
|
+
"# Soul",
|
|
413
|
+
"",
|
|
414
|
+
"OrgX agents are spirits/light entities: responsible + fun, never juvenile.",
|
|
415
|
+
"Avoid cartoonish mascots. Keep tone professional, direct, and pragmatic.",
|
|
416
|
+
"",
|
|
417
|
+
"Metaphor:",
|
|
418
|
+
"- Threads, prisms, workstreams, light, and organizational flow.",
|
|
419
|
+
"- Enhance the claw: armor on top of the claw, not replacement.",
|
|
420
|
+
"",
|
|
421
|
+
].join("\n");
|
|
422
|
+
}
|
|
423
|
+
return `# ${input.agent.name}\n`;
|
|
424
|
+
})();
|
|
425
|
+
const normalized = normalizeNewlines(baseBody).trimEnd() + "\n";
|
|
426
|
+
const bodySha = sha256(normalized);
|
|
427
|
+
return `${managedHeader({
|
|
428
|
+
packId: input.packId,
|
|
429
|
+
packVersion: input.packVersion,
|
|
430
|
+
file: input.file,
|
|
431
|
+
managedSha: bodySha,
|
|
432
|
+
})}${normalized}`;
|
|
433
|
+
}
|
|
434
|
+
function upsertSuiteAgentsIntoConfig(input) {
|
|
435
|
+
const openclaw = input.openclaw && typeof input.openclaw === "object" ? input.openclaw : {};
|
|
436
|
+
const agentsObj = isRecord(openclaw.agents) ? openclaw.agents : {};
|
|
437
|
+
const currentListRaw = Array.isArray(agentsObj.list) ? agentsObj.list : [];
|
|
438
|
+
const currentList = currentListRaw
|
|
439
|
+
.map((entry) => (entry && typeof entry === "object" ? entry : null))
|
|
440
|
+
.filter((entry) => Boolean(entry));
|
|
441
|
+
const byId = new Map();
|
|
442
|
+
for (const entry of currentList) {
|
|
443
|
+
const id = typeof entry.id === "string" ? entry.id.trim() : "";
|
|
444
|
+
if (!id)
|
|
445
|
+
continue;
|
|
446
|
+
byId.set(id, entry);
|
|
447
|
+
}
|
|
448
|
+
const nextList = [...currentList];
|
|
449
|
+
const added = [];
|
|
450
|
+
for (const agent of ORGX_AGENT_SUITE_AGENTS) {
|
|
451
|
+
if (!isSafeAgentId(agent.id))
|
|
452
|
+
continue;
|
|
453
|
+
if (byId.has(agent.id))
|
|
454
|
+
continue;
|
|
455
|
+
const workspace = join(input.suiteWorkspaceRoot, agent.id);
|
|
456
|
+
nextList.push({
|
|
457
|
+
id: agent.id,
|
|
458
|
+
name: agent.name,
|
|
459
|
+
workspace,
|
|
460
|
+
});
|
|
461
|
+
added.push(agent.id);
|
|
462
|
+
}
|
|
463
|
+
if (added.length === 0) {
|
|
464
|
+
return { updated: false, next: openclaw, addedAgentIds: [] };
|
|
465
|
+
}
|
|
466
|
+
const nextAgents = { ...agentsObj, list: nextList };
|
|
467
|
+
const next = { ...openclaw, agents: nextAgents };
|
|
468
|
+
return { updated: true, next, addedAgentIds: added };
|
|
469
|
+
}
|
|
470
|
+
export function computeOrgxAgentSuitePlan(input) {
|
|
471
|
+
const packVersion = input.packVersion.trim() || "0.0.0";
|
|
472
|
+
const openclawDir = input.openclawDir ?? getOpenClawDir();
|
|
473
|
+
const { path: cfgPath, parsed } = readOpenclawConfig(openclawDir);
|
|
474
|
+
const suiteWorkspaceRoot = resolveSuiteWorkspaceRoot(parsed);
|
|
475
|
+
const upsert = upsertSuiteAgentsIntoConfig({ openclaw: parsed, suiteWorkspaceRoot });
|
|
476
|
+
const agents = ORGX_AGENT_SUITE_AGENTS.map((agent) => {
|
|
477
|
+
const workspace = join(suiteWorkspaceRoot, agent.id);
|
|
478
|
+
const list = Array.isArray(parsed?.agents?.list) ? parsed?.agents?.list : [];
|
|
479
|
+
const configured = list.some((entry) => String(entry?.id ?? "").trim() === agent.id);
|
|
480
|
+
return {
|
|
481
|
+
...agent,
|
|
482
|
+
workspace,
|
|
483
|
+
configuredInOpenclaw: configured || upsert.addedAgentIds.includes(agent.id),
|
|
484
|
+
workspaceExists: existsSync(workspace),
|
|
485
|
+
};
|
|
486
|
+
});
|
|
487
|
+
const workspaceFiles = [];
|
|
488
|
+
for (const agent of agents) {
|
|
489
|
+
for (const file of SUITE_FILES) {
|
|
490
|
+
const managedPath = join(agent.workspace, SUITE_MANAGED_DIR, file);
|
|
491
|
+
const localPath = join(agent.workspace, SUITE_LOCAL_DIR, file);
|
|
492
|
+
const compositePath = join(agent.workspace, file);
|
|
493
|
+
const managedContent = buildManagedFileContent({
|
|
494
|
+
agent,
|
|
495
|
+
file,
|
|
496
|
+
packId: ORGX_AGENT_SUITE_PACK_ID,
|
|
497
|
+
packVersion,
|
|
498
|
+
skillPack: input.skillPack ?? null,
|
|
499
|
+
});
|
|
500
|
+
const existingComposite = loadTextFile(compositePath);
|
|
501
|
+
const embeddedOverride = existingComposite ? extractLocalOverridesFromComposite(existingComposite) : null;
|
|
502
|
+
const localOverride = loadTextFile(localPath) ?? embeddedOverride;
|
|
503
|
+
const compositeContent = buildCompositeFile({ managed: managedContent, localOverride });
|
|
504
|
+
const action = !existsSync(compositePath)
|
|
505
|
+
? "create"
|
|
506
|
+
: normalizeNewlines(existingComposite ?? "") !== normalizeNewlines(compositeContent)
|
|
507
|
+
? localOverride
|
|
508
|
+
? "update"
|
|
509
|
+
: "conflict"
|
|
510
|
+
: "noop";
|
|
511
|
+
workspaceFiles.push({
|
|
512
|
+
agentId: agent.id,
|
|
513
|
+
file,
|
|
514
|
+
managedPath,
|
|
515
|
+
localPath,
|
|
516
|
+
compositePath,
|
|
517
|
+
action,
|
|
518
|
+
});
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
return {
|
|
522
|
+
packId: ORGX_AGENT_SUITE_PACK_ID,
|
|
523
|
+
packVersion,
|
|
524
|
+
openclawConfigPath: cfgPath,
|
|
525
|
+
suiteWorkspaceRoot,
|
|
526
|
+
skillPack: input.skillPack
|
|
527
|
+
? {
|
|
528
|
+
source: input.skillPack.source,
|
|
529
|
+
name: input.skillPack.name,
|
|
530
|
+
version: input.skillPack.version,
|
|
531
|
+
checksum: input.skillPack.checksum,
|
|
532
|
+
etag: input.skillPack.etag ?? null,
|
|
533
|
+
updated_at: input.skillPack.updated_at ?? null,
|
|
534
|
+
}
|
|
535
|
+
: null,
|
|
536
|
+
skillPackRemote: input.skillPackRemote ?? null,
|
|
537
|
+
skillPackPolicy: input.skillPackPolicy ?? null,
|
|
538
|
+
skillPackUpdateAvailable: Boolean(input.skillPackUpdateAvailable),
|
|
539
|
+
agents,
|
|
540
|
+
openclawConfigWouldUpdate: upsert.updated,
|
|
541
|
+
openclawConfigAddedAgents: upsert.addedAgentIds,
|
|
542
|
+
workspaceFiles,
|
|
543
|
+
};
|
|
544
|
+
}
|
|
545
|
+
export function applyOrgxAgentSuitePlan(input) {
|
|
546
|
+
const dryRun = input.dryRun ?? false;
|
|
547
|
+
if (dryRun)
|
|
548
|
+
return { ok: true, applied: false, plan: input.plan };
|
|
549
|
+
const openclawDir = input.openclawDir ?? getOpenClawDir();
|
|
550
|
+
const read = readOpenclawConfig(openclawDir);
|
|
551
|
+
const suiteWorkspaceRoot = input.plan.suiteWorkspaceRoot;
|
|
552
|
+
const upsert = upsertSuiteAgentsIntoConfig({
|
|
553
|
+
openclaw: read.parsed,
|
|
554
|
+
suiteWorkspaceRoot,
|
|
555
|
+
});
|
|
556
|
+
if (upsert.updated) {
|
|
557
|
+
// Preserve the original file mode when possible.
|
|
558
|
+
writeFileAtomicSync(read.path, `${JSON.stringify(upsert.next, null, 2)}\n`, { mode: read.fileMode || 0o600, encoding: "utf8" });
|
|
559
|
+
}
|
|
560
|
+
// Workspaces + files
|
|
561
|
+
const actionByFileKey = new Map();
|
|
562
|
+
for (const entry of input.plan.workspaceFiles ?? []) {
|
|
563
|
+
actionByFileKey.set(`${entry.agentId}:${entry.file}`, entry.action);
|
|
564
|
+
}
|
|
565
|
+
for (const agent of input.plan.agents) {
|
|
566
|
+
ensureDir(agent.workspace, 0o700);
|
|
567
|
+
ensureDir(join(agent.workspace, SUITE_MANAGED_DIR), 0o700);
|
|
568
|
+
ensureDir(join(agent.workspace, SUITE_LOCAL_DIR), 0o700);
|
|
569
|
+
for (const file of SUITE_FILES) {
|
|
570
|
+
const action = actionByFileKey.get(`${agent.id}:${file}`) ?? "update";
|
|
571
|
+
if (action === "conflict") {
|
|
572
|
+
// Do not clobber files that appear to have out-of-band edits.
|
|
573
|
+
continue;
|
|
574
|
+
}
|
|
575
|
+
const managedPath = join(agent.workspace, SUITE_MANAGED_DIR, file);
|
|
576
|
+
const localPath = join(agent.workspace, SUITE_LOCAL_DIR, file);
|
|
577
|
+
const compositePath = join(agent.workspace, file);
|
|
578
|
+
const managed = buildManagedFileContent({
|
|
579
|
+
agent,
|
|
580
|
+
file,
|
|
581
|
+
packId: ORGX_AGENT_SUITE_PACK_ID,
|
|
582
|
+
packVersion: input.plan.packVersion,
|
|
583
|
+
skillPack: input.skillPack ?? null,
|
|
584
|
+
});
|
|
585
|
+
let localOverride = loadTextFile(localPath);
|
|
586
|
+
if (!localOverride) {
|
|
587
|
+
const existingComposite = loadTextFile(compositePath);
|
|
588
|
+
const embedded = existingComposite
|
|
589
|
+
? extractLocalOverridesFromComposite(existingComposite)
|
|
590
|
+
: null;
|
|
591
|
+
if (embedded) {
|
|
592
|
+
// Preserve user edits that were appended to the composite but never moved into `.orgx/local/*`.
|
|
593
|
+
ensureDir(dirname(localPath), 0o700);
|
|
594
|
+
writeFileAtomicSync(localPath, embedded, { mode: 0o600, encoding: "utf8" });
|
|
595
|
+
localOverride = embedded;
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
const composite = buildCompositeFile({ managed, localOverride });
|
|
599
|
+
// Managed file always updated to match current pack content.
|
|
600
|
+
ensureDir(dirname(managedPath), 0o700);
|
|
601
|
+
writeFileAtomicSync(managedPath, managed, { mode: 0o600, encoding: "utf8" });
|
|
602
|
+
// Composite file updated iff needed.
|
|
603
|
+
const existing = loadTextFile(compositePath);
|
|
604
|
+
if (!existing || normalizeNewlines(existing) !== normalizeNewlines(composite)) {
|
|
605
|
+
writeFileAtomicSync(compositePath, composite, { mode: 0o600, encoding: "utf8" });
|
|
606
|
+
}
|
|
607
|
+
// Ensure local override file exists only if user created it; do not create it.
|
|
608
|
+
void localPath;
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
return { ok: true, applied: true, plan: input.plan };
|
|
612
|
+
}
|
|
613
|
+
export function generateAgentSuiteOperationId() {
|
|
614
|
+
return `suite:${Date.now()}:${randomUUID().slice(0, 8)}`;
|
|
615
|
+
}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*
|
|
7
7
|
* Uses native fetch — no external dependencies.
|
|
8
8
|
*/
|
|
9
|
-
import type { OrgSnapshot, SyncPayload, SyncResponse, SpawnGuardResult, QualityScore, Entity, EntityListFilters, EmitActivityRequest, EmitActivityResponse, ApplyChangesetRequest, ApplyChangesetResponse, LiveActivityItem, SessionTreeResponse, HandoffSummary, CheckpointSummary, RestoreRequest, DelegationPreflightResult, BillingStatus, BillingCheckoutRequest, BillingUrlResult } from "./types.js";
|
|
9
|
+
import type { OrgSnapshot, SyncPayload, SyncResponse, SpawnGuardResult, QualityScore, Entity, EntityListFilters, EmitActivityRequest, EmitActivityResponse, ApplyChangesetRequest, ApplyChangesetResponse, RecordRunOutcomeRequest, RecordRunOutcomeResponse, RecordRunRetroRequest, RecordRunRetroResponse, LiveActivityItem, SessionTreeResponse, HandoffSummary, CheckpointSummary, RestoreRequest, DelegationPreflightResult, BillingStatus, BillingCheckoutRequest, BillingUrlResult, KickoffContextRequest, KickoffContextResponse, SkillPack } from "./types.js";
|
|
10
10
|
export type DecisionAction = "approve" | "reject";
|
|
11
11
|
export type RunAction = "pause" | "resume" | "cancel" | "rollback";
|
|
12
12
|
export interface DecisionActionResult {
|
|
@@ -41,6 +41,25 @@ export declare class OrgXClient {
|
|
|
41
41
|
private buildQuery;
|
|
42
42
|
getOrgSnapshot(): Promise<OrgSnapshot>;
|
|
43
43
|
syncMemory(payload: SyncPayload): Promise<SyncResponse>;
|
|
44
|
+
getKickoffContext(payload: KickoffContextRequest): Promise<KickoffContextResponse>;
|
|
45
|
+
getSkillPack(input?: {
|
|
46
|
+
name?: string;
|
|
47
|
+
ifNoneMatch?: string | null;
|
|
48
|
+
}): Promise<{
|
|
49
|
+
ok: true;
|
|
50
|
+
notModified: true;
|
|
51
|
+
etag: string | null;
|
|
52
|
+
pack: null;
|
|
53
|
+
} | {
|
|
54
|
+
ok: true;
|
|
55
|
+
notModified: false;
|
|
56
|
+
etag: string | null;
|
|
57
|
+
pack: SkillPack;
|
|
58
|
+
} | {
|
|
59
|
+
ok: false;
|
|
60
|
+
status: number;
|
|
61
|
+
error: string;
|
|
62
|
+
}>;
|
|
44
63
|
delegationPreflight(payload: {
|
|
45
64
|
intent: string;
|
|
46
65
|
acceptanceCriteria?: string[];
|
|
@@ -80,6 +99,8 @@ export declare class OrgXClient {
|
|
|
80
99
|
createBillingPortal(): Promise<BillingUrlResult>;
|
|
81
100
|
emitActivity(payload: EmitActivityRequest): Promise<EmitActivityResponse>;
|
|
82
101
|
applyChangeset(payload: ApplyChangesetRequest): Promise<ApplyChangesetResponse>;
|
|
102
|
+
recordRunOutcome(payload: RecordRunOutcomeRequest): Promise<RecordRunOutcomeResponse>;
|
|
103
|
+
recordRunRetro(payload: RecordRunRetroRequest): Promise<RecordRunRetroResponse>;
|
|
83
104
|
getLiveSessions(params?: {
|
|
84
105
|
limit?: number;
|
|
85
106
|
initiative?: string | null;
|