Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,8 @@ The plugin stores its own auth and local state under:
- `~/.config/useorgx/openclaw-plugin/installation.json`
- `~/.config/useorgx/openclaw-plugin/snapshot.json`
- `~/.openclaw/orgx-outbox/`
- `~/.config/useorgx/wizard/hooks/events.jsonl` for compact, redacted Work
Graph hook events when runtime hooks are installed

If you explicitly enable MCP client auto-configuration, the plugin may update supported client config files and create timestamped backups before writing:

Expand Down Expand Up @@ -177,6 +179,9 @@ Agent turns, terminal opens, and other `openclaw` CLI child-process actions are
- It does not auto-install the managed OrgX agent suite unless you enable that behavior.
- It does not patch Claude, Cursor, or Codex MCP config files unless you enable that behavior.
- It does not send product telemetry unless you explicitly enable it.
- It does not send raw transcripts through runtime hooks. Hook scripts write
redacted event summaries locally so the OrgX wizard can reconcile Work Graph
fingerprints, missed OrgX writeback, and safe public readouts later.

---

Expand Down
2 changes: 1 addition & 1 deletion openclaw.plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"id": "orgx",
"name": "OrgX for OpenClaw",
"version": "0.7.33",
"version": "0.7.34",
"description": "Persistent organizational memory and coordinated execution for OpenClaw agents",
"entry": "./dist/index.js",
"author": "OrgX Team",
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@useorgx/openclaw-plugin",
"version": "0.7.33",
"version": "0.7.34",
"description": "Persistent organizational memory and coordination for OpenClaw agents",
"type": "module",
"main": "./dist/index.js",
Expand Down
132 changes: 129 additions & 3 deletions templates/hooks/scripts/post-reporting-event.mjs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
#!/usr/bin/env node

import { appendFileSync, mkdirSync } from "node:fs";
import { homedir } from "node:os";
import { dirname, join } from "node:path";
import process from "node:process";
import { pathToFileURL } from "node:url";

Expand All @@ -26,6 +29,37 @@ export function pickString(...values) {
return undefined;
}

async function readStdin() {
const chunks = [];
for await (const chunk of process.stdin) {
chunks.push(Buffer.from(chunk));
}
return Buffer.concat(chunks).toString("utf8");
}

export function parseJsonRecord(value) {
try {
const parsed = JSON.parse(value || "{}");
return parsed && typeof parsed === "object" && !Array.isArray(parsed)
? parsed
: {};
} catch {
return {};
}
}

export function sanitizeArgs(args) {
const redacted = {};
for (const [key, value] of Object.entries(args ?? {})) {
if (/token|api[_-]?key|authorization|cookie|secret/i.test(key)) {
redacted[key] = "[redacted]";
} else {
redacted[key] = value;
}
}
return redacted;
}

async function postJson(url, payload, headers, fetchImpl = fetch) {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 8000);
Expand All @@ -49,6 +83,72 @@ async function postJson(url, payload, headers, fetchImpl = fetch) {
}
}

export function buildWorkGraphHookRecord({
args,
payload,
sourceClient,
event,
cwd = process.cwd(),
timestamp = new Date().toISOString(),
}) {
const toolName = pickString(
payload.tool_name,
payload.toolName,
payload.tool?.name,
payload.name
);
const prompt = pickString(payload.prompt, payload.user_prompt, payload.userPrompt);

return {
schema_version: "2026-05-07",
source: "orgx_openclaw_plugin_runtime_hook",
source_client: sourceClient,
event,
session_id: pickString(
payload.session_id,
payload.sessionId,
payload.conversation_id,
payload.conversationId,
args.session_id,
args.sessionId,
args.run_id
),
turn_id: pickString(payload.turn_id, payload.turnId, args.turn_id, args.turnId),
cwd: pickString(
payload.cwd,
payload.working_directory,
payload.workspace,
args.cwd,
cwd
),
transcript_path: pickString(payload.transcript_path, payload.transcriptPath),
timestamp,
summary: {
tool_name: toolName,
prompt_chars: prompt ? prompt.length : undefined,
payload_keys: Object.keys(payload).slice(0, 40),
initiative_id: pickString(args.initiative, args.initiative_id),
workstream_id: pickString(args.workstream_id),
task_id: pickString(args.task_id),
run_id: pickString(args.run_id),
correlation_id: pickString(args.correlation_id),
},
};
}

export function appendWorkGraphHookRecord(record, outbox) {
try {
mkdirSync(dirname(outbox), { recursive: true, mode: 0o700 });
appendFileSync(outbox, `${JSON.stringify(record)}\n`, {
encoding: "utf8",
mode: 0o600,
});
return true;
} catch {
return false;
}
}

export function buildRuntimePayload({
initiativeId,
runId,
Expand Down Expand Up @@ -79,7 +179,7 @@ export function buildRuntimePayload({
message,
metadata: {
source: "hook_runtime_relay",
raw_args: args,
raw_args: sanitizeArgs(args),
},
timestamp: new Date().toISOString(),
};
Expand All @@ -106,7 +206,7 @@ export function buildActivityPayload({
metadata: {
source: "hook_backstop",
hook_event: event,
raw_args: args,
raw_args: sanitizeArgs(args),
},
};
}
Expand Down Expand Up @@ -140,8 +240,11 @@ export async function main({
env = process.env,
fetchImpl = fetch,
now = () => Date.now(),
stdinText = "",
cwd = process.cwd(),
} = {}) {
const args = parseArgs(argv);
const stdinPayload = parseJsonRecord(stdinText);

const runtimeHookUrl = pickString(
args.runtime_hook_url,
Expand Down Expand Up @@ -182,6 +285,22 @@ export async function main({
args.message,
`Hook event: ${event}`
);
const outbox = pickString(
args.outbox,
env.ORGX_WIZARD_HOOK_OUTBOX,
join(homedir(), ".config", "useorgx", "wizard", "hooks", "events.jsonl")
);
const workGraphSpooled = appendWorkGraphHookRecord(
buildWorkGraphHookRecord({
args,
payload: stdinPayload,
sourceClient,
event,
cwd,
timestamp: new Date(now()).toISOString(),
}),
outbox
);

let runtimePosted = false;
let runtimePostFailed = false;
Expand Down Expand Up @@ -218,6 +337,7 @@ export async function main({
return {
ok: true,
runtime_posted: runtimePosted,
work_graph_spooled: workGraphSpooled,
skipped: "missing_api_key",
...(runtimePostFailed ? { runtime_skipped: "runtime_post_failed" } : {}),
};
Expand All @@ -226,6 +346,7 @@ export async function main({
return {
ok: true,
runtime_posted: runtimePosted,
work_graph_spooled: workGraphSpooled,
skipped: "missing_initiative_id",
...(runtimePostFailed ? { runtime_skipped: "runtime_post_failed" } : {}),
};
Expand Down Expand Up @@ -262,6 +383,7 @@ export async function main({
return {
ok: true,
runtime_posted: runtimePosted,
work_graph_spooled: workGraphSpooled,
skipped: "activity_post_failed",
};
}
Expand All @@ -272,6 +394,7 @@ export async function main({
return {
ok: true,
runtime_posted: runtimePosted,
work_graph_spooled: workGraphSpooled,
activity_posted: true,
changeset_posted: false,
};
Expand All @@ -297,6 +420,7 @@ export async function main({
return {
ok: true,
runtime_posted: runtimePosted,
work_graph_spooled: workGraphSpooled,
activity_posted: true,
changeset_posted: false,
skipped: "changeset_post_failed",
Expand All @@ -306,13 +430,15 @@ export async function main({
return {
ok: true,
runtime_posted: runtimePosted,
work_graph_spooled: workGraphSpooled,
activity_posted: true,
changeset_posted: true,
};
}

if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
main()
readStdin()
.then((stdinText) => main({ stdinText }))
.then(() => {
process.exit(0);
})
Expand Down
Loading
Loading