@useorgx/openclaw-plugin 0.4.8 → 0.7.0
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 +35 -0
- package/dashboard/dist/assets/BJgZIVUQ.js +53 -0
- package/dashboard/dist/assets/BJgZIVUQ.js.br +0 -0
- package/dashboard/dist/assets/BJgZIVUQ.js.gz +0 -0
- package/dashboard/dist/assets/BXWDRGm-.js +1 -0
- package/dashboard/dist/assets/BXWDRGm-.js.br +0 -0
- package/dashboard/dist/assets/BXWDRGm-.js.gz +0 -0
- package/dashboard/dist/assets/BgOYB78t.js +4 -0
- package/dashboard/dist/assets/BgOYB78t.js.br +0 -0
- package/dashboard/dist/assets/BgOYB78t.js.gz +0 -0
- package/dashboard/dist/assets/C-KIc3Wc.js.br +0 -0
- package/dashboard/dist/assets/C-KIc3Wc.js.gz +0 -0
- package/dashboard/dist/assets/CE38zU4U.js +1 -0
- package/dashboard/dist/assets/CE38zU4U.js.br +0 -0
- package/dashboard/dist/assets/CE38zU4U.js.gz +0 -0
- package/dashboard/dist/assets/CFGKRAzG.js +1 -0
- package/dashboard/dist/assets/CFGKRAzG.js.br +0 -0
- package/dashboard/dist/assets/CFGKRAzG.js.gz +0 -0
- package/dashboard/dist/assets/CGGR2GZh.js +1 -0
- package/dashboard/dist/assets/CGGR2GZh.js.br +0 -0
- package/dashboard/dist/assets/CGGR2GZh.js.gz +0 -0
- package/dashboard/dist/assets/CL_wXqR7.js +1 -0
- package/dashboard/dist/assets/CL_wXqR7.js.br +0 -0
- package/dashboard/dist/assets/CL_wXqR7.js.gz +0 -0
- package/dashboard/dist/assets/CPFiTmlw.js +8 -0
- package/dashboard/dist/assets/CPFiTmlw.js.br +0 -0
- package/dashboard/dist/assets/CPFiTmlw.js.gz +0 -0
- package/dashboard/dist/assets/CZZTvkQZ.js +1 -0
- package/dashboard/dist/assets/CZZTvkQZ.js.br +0 -0
- package/dashboard/dist/assets/CZZTvkQZ.js.gz +0 -0
- package/dashboard/dist/assets/{CpJsfbXo.js → CxQ08qFN.js} +2 -2
- package/dashboard/dist/assets/CxQ08qFN.js.br +0 -0
- package/dashboard/dist/assets/CxQ08qFN.js.gz +0 -0
- package/dashboard/dist/assets/D-bf6hEI.js +213 -0
- package/dashboard/dist/assets/D-bf6hEI.js.br +0 -0
- package/dashboard/dist/assets/D-bf6hEI.js.gz +0 -0
- package/dashboard/dist/assets/DG6y9wJI.js +2 -0
- package/dashboard/dist/assets/DG6y9wJI.js.br +0 -0
- package/dashboard/dist/assets/DG6y9wJI.js.gz +0 -0
- package/dashboard/dist/assets/DNxKz-GV.js +1 -0
- package/dashboard/dist/assets/DNxKz-GV.js.br +0 -0
- package/dashboard/dist/assets/DNxKz-GV.js.gz +0 -0
- package/dashboard/dist/assets/DW_rKUic.js +11 -0
- package/dashboard/dist/assets/DW_rKUic.js.br +0 -0
- package/dashboard/dist/assets/DW_rKUic.js.gz +0 -0
- package/dashboard/dist/assets/DbNoijHm.js +1 -0
- package/dashboard/dist/assets/DbNoijHm.js.br +0 -0
- package/dashboard/dist/assets/DbNoijHm.js.gz +0 -0
- package/dashboard/dist/assets/DjcdE6jC.js +2 -0
- package/dashboard/dist/assets/DjcdE6jC.js.br +0 -0
- package/dashboard/dist/assets/DjcdE6jC.js.gz +0 -0
- package/dashboard/dist/assets/FZYuCDnt.js +1 -0
- package/dashboard/dist/assets/FZYuCDnt.js.br +0 -0
- package/dashboard/dist/assets/FZYuCDnt.js.gz +0 -0
- package/dashboard/dist/assets/PAUiij_z.js +1 -0
- package/dashboard/dist/assets/PAUiij_z.js.br +0 -0
- package/dashboard/dist/assets/PAUiij_z.js.gz +0 -0
- package/dashboard/dist/assets/cNrhgGc1.js +8 -0
- package/dashboard/dist/assets/cNrhgGc1.js.br +0 -0
- package/dashboard/dist/assets/cNrhgGc1.js.gz +0 -0
- package/dashboard/dist/assets/h5biQs2I.css +1 -0
- package/dashboard/dist/assets/h5biQs2I.css.br +0 -0
- package/dashboard/dist/assets/h5biQs2I.css.gz +0 -0
- package/dashboard/dist/assets/ic2FaMnh.js +1 -0
- package/dashboard/dist/assets/ic2FaMnh.js.br +0 -0
- package/dashboard/dist/assets/ic2FaMnh.js.gz +0 -0
- package/dashboard/dist/assets/nByHNHoW.js +1 -0
- package/dashboard/dist/assets/nByHNHoW.js.br +0 -0
- package/dashboard/dist/assets/nByHNHoW.js.gz +0 -0
- package/dashboard/dist/assets/qm8xLgv-.css +1 -0
- package/dashboard/dist/assets/qm8xLgv-.css.br +0 -0
- package/dashboard/dist/assets/qm8xLgv-.css.gz +0 -0
- package/dashboard/dist/assets/tS9mbYZi.js +1 -0
- package/dashboard/dist/assets/tS9mbYZi.js.br +0 -0
- package/dashboard/dist/assets/tS9mbYZi.js.gz +0 -0
- package/dashboard/dist/brand/anthropic-mark.svg.br +0 -0
- package/dashboard/dist/brand/anthropic-mark.svg.gz +0 -0
- package/dashboard/dist/brand/openai-mark.svg.br +0 -0
- package/dashboard/dist/brand/openai-mark.svg.gz +0 -0
- package/dashboard/dist/brand/openclaw-mark.svg.br +0 -0
- package/dashboard/dist/brand/openclaw-mark.svg.gz +0 -0
- package/dashboard/dist/brand/xandy-orchestrator.png +0 -0
- package/dashboard/dist/index.html +7 -5
- package/dashboard/dist/index.html.br +0 -0
- package/dashboard/dist/index.html.gz +0 -0
- package/dist/activity-actor-fields.js +26 -4
- package/dist/activity-store.js +38 -26
- package/dist/agent-context-store.js +84 -42
- package/dist/agent-run-store.js +49 -28
- package/dist/agent-suite.d.ts +9 -0
- package/dist/agent-suite.js +150 -17
- package/dist/artifacts/artifact-domain-schemas.d.ts +66 -0
- package/dist/artifacts/artifact-domain-schemas.js +357 -0
- package/dist/artifacts/register-artifact.d.ts +4 -3
- package/dist/artifacts/register-artifact.js +170 -57
- package/dist/auth/flows.d.ts +47 -0
- package/dist/auth/flows.js +169 -0
- package/dist/auth-store.js +6 -26
- package/dist/byok-store.js +5 -19
- package/dist/chat-store.d.ts +157 -0
- package/dist/chat-store.js +586 -0
- package/dist/cli/orgx.d.ts +66 -0
- package/dist/cli/orgx.js +102 -0
- package/dist/config/refresh.d.ts +32 -0
- package/dist/config/refresh.js +55 -0
- package/dist/config/resolution.d.ts +37 -0
- package/dist/config/resolution.js +178 -0
- package/dist/contracts/client.d.ts +43 -3
- package/dist/contracts/client.js +159 -30
- package/dist/contracts/retro-schema.d.ts +81 -0
- package/dist/contracts/retro-schema.js +80 -0
- package/dist/contracts/shared-types.d.ts +306 -0
- package/dist/contracts/shared-types.js +179 -0
- package/dist/contracts/skill-pack-schema.d.ts +192 -0
- package/dist/contracts/skill-pack-schema.js +180 -0
- package/dist/contracts/types.d.ts +224 -132
- package/dist/contracts/types.js +5 -0
- package/dist/entities/auto-assignment.d.ts +36 -0
- package/dist/entities/auto-assignment.js +141 -0
- package/dist/entity-comment-store.js +5 -25
- package/dist/event-sanitization.d.ts +11 -0
- package/dist/event-sanitization.js +113 -0
- package/dist/fs-utils.js +13 -1
- package/dist/gateway-watchdog.d.ts +5 -0
- package/dist/gateway-watchdog.js +50 -0
- package/dist/hash-utils.d.ts +2 -0
- package/dist/hash-utils.js +12 -0
- package/dist/hooks/post-reporting-event.mjs +1 -5
- package/dist/http/helpers/activity-headline.d.ts +10 -0
- package/dist/http/helpers/activity-headline.js +73 -0
- package/dist/http/helpers/artifact-fallback.d.ts +13 -0
- package/dist/http/helpers/artifact-fallback.js +148 -0
- package/dist/http/helpers/auto-continue-engine.d.ts +486 -0
- package/dist/http/helpers/auto-continue-engine.js +3563 -0
- package/dist/http/helpers/autopilot-operations.d.ts +176 -0
- package/dist/http/helpers/autopilot-operations.js +554 -0
- package/dist/http/helpers/autopilot-runtime.d.ts +43 -0
- package/dist/http/helpers/autopilot-runtime.js +607 -0
- package/dist/http/helpers/autopilot-slice-utils.d.ts +56 -0
- package/dist/http/helpers/autopilot-slice-utils.js +899 -0
- package/dist/http/helpers/decision-mapper.d.ts +52 -0
- package/dist/http/helpers/decision-mapper.js +260 -0
- package/dist/http/helpers/dispatch-lifecycle.d.ts +119 -0
- package/dist/http/helpers/dispatch-lifecycle.js +809 -0
- package/dist/http/helpers/hash-utils.d.ts +1 -0
- package/dist/http/helpers/hash-utils.js +1 -0
- package/dist/http/helpers/kickoff-context.d.ts +12 -0
- package/dist/http/helpers/kickoff-context.js +228 -0
- package/dist/http/helpers/llm-client.d.ts +47 -0
- package/dist/http/helpers/llm-client.js +256 -0
- package/dist/http/helpers/mission-control.d.ts +193 -0
- package/dist/http/helpers/mission-control.js +1383 -0
- package/dist/http/helpers/openclaw-cli.d.ts +37 -0
- package/dist/http/helpers/openclaw-cli.js +283 -0
- package/dist/http/helpers/runtime-sse.d.ts +20 -0
- package/dist/http/helpers/runtime-sse.js +110 -0
- package/dist/http/helpers/sentinel-catalog.d.ts +23 -0
- package/dist/http/helpers/sentinel-catalog.js +193 -0
- package/dist/http/helpers/session-classification.d.ts +9 -0
- package/dist/http/helpers/session-classification.js +564 -0
- package/dist/http/helpers/slice-experience-v2.d.ts +137 -0
- package/dist/http/helpers/slice-experience-v2.js +677 -0
- package/dist/http/helpers/slice-run-projections.d.ts +72 -0
- package/dist/http/helpers/slice-run-projections.js +860 -0
- package/dist/http/helpers/triage-mapper.d.ts +43 -0
- package/dist/http/helpers/triage-mapper.js +549 -0
- package/dist/http/helpers/value-utils.d.ts +6 -0
- package/dist/http/helpers/value-utils.js +72 -0
- package/dist/http/helpers/workspace-scope.d.ts +15 -0
- package/dist/http/helpers/workspace-scope.js +170 -0
- package/dist/http/index.d.ts +88 -0
- package/dist/http/index.js +3610 -0
- package/dist/http/router.d.ts +23 -0
- package/dist/http/router.js +23 -0
- package/dist/http/routes/agent-control.d.ts +79 -0
- package/dist/http/routes/agent-control.js +684 -0
- package/dist/http/routes/agent-suite.d.ts +38 -0
- package/dist/http/routes/agent-suite.js +397 -0
- package/dist/http/routes/agents-catalog.d.ts +40 -0
- package/dist/http/routes/agents-catalog.js +128 -0
- package/dist/http/routes/billing.d.ts +23 -0
- package/dist/http/routes/billing.js +55 -0
- package/dist/http/routes/chat.d.ts +19 -0
- package/dist/http/routes/chat.js +522 -0
- package/dist/http/routes/debug.d.ts +14 -0
- package/dist/http/routes/debug.js +21 -0
- package/dist/http/routes/decision-actions.d.ts +20 -0
- package/dist/http/routes/decision-actions.js +103 -0
- package/dist/http/routes/delegation.d.ts +19 -0
- package/dist/http/routes/delegation.js +32 -0
- package/dist/http/routes/dispatch-gateway-envelope.d.ts +25 -0
- package/dist/http/routes/dispatch-gateway-envelope.js +26 -0
- package/dist/http/routes/entities.d.ts +63 -0
- package/dist/http/routes/entities.js +440 -0
- package/dist/http/routes/entity-dynamic.d.ts +25 -0
- package/dist/http/routes/entity-dynamic.js +191 -0
- package/dist/http/routes/health.d.ts +22 -0
- package/dist/http/routes/health.js +49 -0
- package/dist/http/routes/live-legacy.d.ts +115 -0
- package/dist/http/routes/live-legacy.js +112 -0
- package/dist/http/routes/live-misc.d.ts +81 -0
- package/dist/http/routes/live-misc.js +426 -0
- package/dist/http/routes/live-snapshot.d.ts +136 -0
- package/dist/http/routes/live-snapshot.js +916 -0
- package/dist/http/routes/live-terminal.d.ts +11 -0
- package/dist/http/routes/live-terminal.js +261 -0
- package/dist/http/routes/live-triage.d.ts +61 -0
- package/dist/http/routes/live-triage.js +248 -0
- package/dist/http/routes/mission-control-actions.d.ts +131 -0
- package/dist/http/routes/mission-control-actions.js +1791 -0
- package/dist/http/routes/mission-control-read.d.ts +73 -0
- package/dist/http/routes/mission-control-read.js +1640 -0
- package/dist/http/routes/onboarding.d.ts +34 -0
- package/dist/http/routes/onboarding.js +101 -0
- package/dist/http/routes/realtime-orchestrator.d.ts +10 -0
- package/dist/http/routes/realtime-orchestrator.js +74 -0
- package/dist/http/routes/run-control.d.ts +27 -0
- package/dist/http/routes/run-control.js +96 -0
- package/dist/http/routes/runtime-hooks.d.ts +69 -0
- package/dist/http/routes/runtime-hooks.js +437 -0
- package/dist/http/routes/sentinels-catalog.d.ts +7 -0
- package/dist/http/routes/sentinels-catalog.js +24 -0
- package/dist/http/routes/settings-byok.d.ts +23 -0
- package/dist/http/routes/settings-byok.js +163 -0
- package/dist/http/routes/summary.d.ts +18 -0
- package/dist/http/routes/summary.js +49 -0
- package/dist/http/routes/usage.d.ts +24 -0
- package/dist/http/routes/usage.js +362 -0
- package/dist/http/routes/work-artifacts.d.ts +9 -0
- package/dist/http/routes/work-artifacts.js +55 -0
- package/dist/http/shared-state.d.ts +16 -0
- package/dist/http/shared-state.js +1 -0
- package/dist/http-handler.d.ts +1 -88
- package/dist/http-handler.js +1 -10605
- package/dist/index.js +287 -2284
- package/dist/json-utils.d.ts +1 -0
- package/dist/json-utils.js +8 -0
- package/dist/local-openclaw.js +29 -6
- package/dist/mcp-client-setup.js +3 -3
- package/dist/mcp-http-handler.js +33 -59
- package/dist/next-up-queue-store.d.ts +16 -1
- package/dist/next-up-queue-store.js +93 -25
- package/dist/outbox.d.ts +5 -0
- package/dist/outbox.js +113 -9
- package/dist/paths.js +24 -5
- package/dist/reporting/rollups.d.ts +53 -0
- package/dist/reporting/rollups.js +148 -0
- package/dist/retro/domain-templates.d.ts +45 -0
- package/dist/retro/domain-templates.js +297 -0
- package/dist/retro/quality-rubric.d.ts +33 -0
- package/dist/retro/quality-rubric.js +213 -0
- package/dist/runtime-cleanup.d.ts +18 -0
- package/dist/runtime-cleanup.js +87 -0
- package/dist/runtime-instance-store.js +5 -31
- package/dist/services/background.d.ts +34 -0
- package/dist/services/background.js +45 -0
- package/dist/services/experiment-randomization.d.ts +21 -0
- package/dist/services/experiment-randomization.js +63 -0
- package/dist/services/instrumentation.d.ts +29 -0
- package/dist/services/instrumentation.js +136 -0
- package/dist/skill-pack-state.d.ts +36 -5
- package/dist/skill-pack-state.js +273 -29
- package/dist/snapshot-store.js +5 -25
- package/dist/stores/json-store.d.ts +11 -0
- package/dist/stores/json-store.js +42 -0
- package/dist/sync/local-agent-telemetry.d.ts +13 -0
- package/dist/sync/local-agent-telemetry.js +128 -0
- package/dist/sync/outbox-replay.d.ts +55 -0
- package/dist/sync/outbox-replay.js +621 -0
- package/dist/team-context-store.d.ts +23 -0
- package/dist/team-context-store.js +116 -0
- package/dist/telemetry/posthog.js +4 -2
- package/dist/tools/core-tools.d.ts +72 -0
- package/dist/tools/core-tools.js +2270 -0
- package/dist/types.d.ts +2 -0
- package/dist/types.js +2 -0
- package/dist/worker-supervisor.js +23 -0
- package/package.json +14 -4
- package/dashboard/dist/assets/B3ziCA02.js +0 -8
- package/dashboard/dist/assets/BNeJ0kpF.js +0 -1
- package/dashboard/dist/assets/BzkiMPmM.js +0 -215
- package/dashboard/dist/assets/CUV9IHHi.js +0 -1
- package/dashboard/dist/assets/Ie7d9Iq2.css +0 -1
- package/dashboard/dist/assets/sAhvFnpk.js +0 -4
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function parseJsonSafe<T>(value: string): T | null;
|
package/dist/local-openclaw.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { homedir } from "node:os";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { readFile, stat, writeFile } from "node:fs/promises";
|
|
4
|
+
import { callLlm } from "./http/helpers/llm-client.js";
|
|
4
5
|
const ACTIVE_WINDOW_MS = 30 * 60_000;
|
|
5
6
|
const LOCAL_SNAPSHOT_CACHE_TTL_MS = 1_500;
|
|
6
7
|
let localSnapshotCache = null;
|
|
@@ -475,9 +476,9 @@ function groupEventsIntoTurns(events, maxTurns, limits) {
|
|
|
475
476
|
return turns;
|
|
476
477
|
}
|
|
477
478
|
// ---------------------------------------------------------------------------
|
|
478
|
-
//
|
|
479
|
+
// Turn summarization — LLM with heuristic fallback
|
|
479
480
|
// ---------------------------------------------------------------------------
|
|
480
|
-
function
|
|
481
|
+
function heuristicSummarizeTurn(turn, _agentLabel) {
|
|
481
482
|
const normalize = (value) => value.replace(/\s+/g, " ").trim();
|
|
482
483
|
if (turn.errorMessage) {
|
|
483
484
|
return `Error: ${normalize(turn.errorMessage)}`;
|
|
@@ -503,6 +504,28 @@ function summarizeTurn(turn, _agentLabel) {
|
|
|
503
504
|
}
|
|
504
505
|
return "Activity";
|
|
505
506
|
}
|
|
507
|
+
async function summarizeTurnWithLlm(turn, agentLabel) {
|
|
508
|
+
const parts = [];
|
|
509
|
+
if (turn.userPrompt)
|
|
510
|
+
parts.push(`User: ${turn.userPrompt}`);
|
|
511
|
+
if (turn.toolNames.length > 0)
|
|
512
|
+
parts.push(`Tools: ${turn.toolNames.join(", ")}`);
|
|
513
|
+
if (turn.assistantResponse)
|
|
514
|
+
parts.push(`Assistant: ${turn.assistantResponse}`);
|
|
515
|
+
if (turn.errorMessage)
|
|
516
|
+
parts.push(`Error: ${turn.errorMessage}`);
|
|
517
|
+
const compact = parts.join("\n").slice(0, 4000);
|
|
518
|
+
const response = await callLlm({
|
|
519
|
+
taskId: "turn_summary",
|
|
520
|
+
systemPrompt: "You summarize agent session turns for a dashboard activity feed. " +
|
|
521
|
+
"Write one clear sentence (max 120 chars) describing what happened. " +
|
|
522
|
+
"Focus on the action and outcome, not internal details. No markdown.",
|
|
523
|
+
userPrompt: compact,
|
|
524
|
+
maxTokens: 64,
|
|
525
|
+
temperature: 0.15,
|
|
526
|
+
}, () => heuristicSummarizeTurn(turn, agentLabel));
|
|
527
|
+
return response.result;
|
|
528
|
+
}
|
|
506
529
|
function isDigestSummaryStale(cached, fresh) {
|
|
507
530
|
if (!cached)
|
|
508
531
|
return true;
|
|
@@ -552,9 +575,9 @@ async function writeDigestCache(baseDir, agentId, sessionId, cache) {
|
|
|
552
575
|
// ---------------------------------------------------------------------------
|
|
553
576
|
// Turn → LiveActivityItem mapping
|
|
554
577
|
// ---------------------------------------------------------------------------
|
|
555
|
-
function turnToActivity(turn, session, cachedSummary, index) {
|
|
578
|
+
async function turnToActivity(turn, session, cachedSummary, index) {
|
|
556
579
|
const agentLabel = session.agentName ?? session.agentId ?? "OpenClaw";
|
|
557
|
-
const summary = cachedSummary ??
|
|
580
|
+
const summary = cachedSummary ?? await summarizeTurnWithLlm(turn, agentLabel);
|
|
558
581
|
// Determine activity type
|
|
559
582
|
let type = "delegation";
|
|
560
583
|
if (turn.errorMessage) {
|
|
@@ -663,11 +686,11 @@ export async function toLocalLiveActivity(snapshot, limit = 200) {
|
|
|
663
686
|
const turn = turns[i];
|
|
664
687
|
const cached = cachedMap.get(turn.id) ?? null;
|
|
665
688
|
const agentLabel = session.agentName ?? session.agentId ?? "OpenClaw";
|
|
666
|
-
const computedSummary =
|
|
689
|
+
const computedSummary = await summarizeTurnWithLlm(turn, agentLabel);
|
|
667
690
|
const fallbackSummary = isDigestSummaryStale(cached, computedSummary)
|
|
668
691
|
? computedSummary
|
|
669
692
|
: cached;
|
|
670
|
-
allActivities.push(turnToActivity(turn, session, fallbackSummary, i));
|
|
693
|
+
allActivities.push(await turnToActivity(turn, session, fallbackSummary, i));
|
|
671
694
|
// Track for cache
|
|
672
695
|
if (isDigestSummaryStale(cached, computedSummary)) {
|
|
673
696
|
newCacheEntries.push({ turnId: turn.id, summary: fallbackSummary });
|
package/dist/mcp-client-setup.js
CHANGED
|
@@ -126,7 +126,7 @@ function upsertCodexMcpServerSection(input) {
|
|
|
126
126
|
const currentText = input.current;
|
|
127
127
|
const lines = currentText.split(/\r?\n/);
|
|
128
128
|
const escapedKey = escapeRegExp(input.key);
|
|
129
|
-
const headerRegex = new RegExp(`^\\[mcp_servers\\.(?:"${escapedKey}"|${escapedKey})\\]\\s*$`);
|
|
129
|
+
const headerRegex = new RegExp(`^\\[mcp_servers\\.(?:"${escapedKey}"|'${escapedKey}'|${escapedKey})\\]\\s*$`);
|
|
130
130
|
let headerIndex = -1;
|
|
131
131
|
for (let i = 0; i < lines.length; i += 1) {
|
|
132
132
|
if (headerRegex.test(lines[i].trim())) {
|
|
@@ -193,12 +193,12 @@ function removeCodexLegacyScopedMcpSections(input) {
|
|
|
193
193
|
let index = 0;
|
|
194
194
|
while (index < lines.length) {
|
|
195
195
|
const trimmed = lines[index].trim();
|
|
196
|
-
const headerMatch = trimmed.match(/^\[mcp_servers\.(?:"([^"]+)"|([A-Za-z0-9_]+))\]\s*$/);
|
|
196
|
+
const headerMatch = trimmed.match(/^\[mcp_servers\.(?:"([^"]+)"|'([^']+)'|([A-Za-z0-9_]+))\]\s*$/);
|
|
197
197
|
if (!headerMatch) {
|
|
198
198
|
index += 1;
|
|
199
199
|
continue;
|
|
200
200
|
}
|
|
201
|
-
const key = (headerMatch[1] ?? headerMatch[2] ?? "").trim();
|
|
201
|
+
const key = (headerMatch[1] ?? headerMatch[2] ?? headerMatch[3] ?? "").trim();
|
|
202
202
|
const isLegacyScoped = key !== input.baseKey &&
|
|
203
203
|
key.startsWith(scopedPrefix);
|
|
204
204
|
if (!isLegacyScoped) {
|
package/dist/mcp-http-handler.js
CHANGED
|
@@ -5,73 +5,47 @@ const DEFAULT_PROTOCOL_VERSION = "2024-11-05";
|
|
|
5
5
|
//
|
|
6
6
|
// NOTE: This scopes only the tools exposed by this plugin (OrgX reporting + mutation).
|
|
7
7
|
// It cannot restrict OpenClaw-native tools (filesystem, shell, etc).
|
|
8
|
+
// Base tools available to all domain scopes
|
|
9
|
+
const ORGX_BASE_TOOLS = [
|
|
10
|
+
"orgx_status",
|
|
11
|
+
"orgx_sync",
|
|
12
|
+
"list_agent_configs",
|
|
13
|
+
"get_agent_config",
|
|
14
|
+
"orgx_emit_activity",
|
|
15
|
+
"orgx_report_progress",
|
|
16
|
+
"update_stream_progress",
|
|
17
|
+
"orgx_register_artifact",
|
|
18
|
+
"orgx_request_decision",
|
|
19
|
+
"orgx_spawn_check",
|
|
20
|
+
// Proof ladder tools (all domains)
|
|
21
|
+
"orgx_quality_score",
|
|
22
|
+
"orgx_proof_status",
|
|
23
|
+
"orgx_record_outcome",
|
|
24
|
+
"orgx_get_outcome_attribution",
|
|
25
|
+
"orgx_verify_completion",
|
|
26
|
+
];
|
|
8
27
|
const ORGX_MCP_ALLOWED_TOOLS_BY_SCOPE = {
|
|
9
|
-
engineering: [
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
"orgx_register_artifact",
|
|
15
|
-
"orgx_request_decision",
|
|
16
|
-
"orgx_spawn_check",
|
|
17
|
-
],
|
|
18
|
-
product: [
|
|
19
|
-
"orgx_status",
|
|
20
|
-
"orgx_sync",
|
|
21
|
-
"orgx_emit_activity",
|
|
22
|
-
"orgx_report_progress",
|
|
23
|
-
"orgx_register_artifact",
|
|
24
|
-
"orgx_request_decision",
|
|
25
|
-
"orgx_spawn_check",
|
|
26
|
-
],
|
|
27
|
-
design: [
|
|
28
|
-
"orgx_status",
|
|
29
|
-
"orgx_sync",
|
|
30
|
-
"orgx_emit_activity",
|
|
31
|
-
"orgx_report_progress",
|
|
32
|
-
"orgx_register_artifact",
|
|
33
|
-
"orgx_request_decision",
|
|
34
|
-
"orgx_spawn_check",
|
|
35
|
-
],
|
|
36
|
-
marketing: [
|
|
37
|
-
"orgx_status",
|
|
38
|
-
"orgx_sync",
|
|
39
|
-
"orgx_emit_activity",
|
|
40
|
-
"orgx_report_progress",
|
|
41
|
-
"orgx_register_artifact",
|
|
42
|
-
"orgx_request_decision",
|
|
43
|
-
"orgx_spawn_check",
|
|
44
|
-
],
|
|
45
|
-
sales: [
|
|
46
|
-
"orgx_status",
|
|
47
|
-
"orgx_sync",
|
|
48
|
-
"orgx_emit_activity",
|
|
49
|
-
"orgx_report_progress",
|
|
50
|
-
"orgx_register_artifact",
|
|
51
|
-
"orgx_request_decision",
|
|
52
|
-
"orgx_spawn_check",
|
|
53
|
-
],
|
|
28
|
+
engineering: [...ORGX_BASE_TOOLS],
|
|
29
|
+
product: [...ORGX_BASE_TOOLS],
|
|
30
|
+
design: [...ORGX_BASE_TOOLS],
|
|
31
|
+
marketing: [...ORGX_BASE_TOOLS],
|
|
32
|
+
sales: [...ORGX_BASE_TOOLS],
|
|
54
33
|
operations: [
|
|
55
|
-
|
|
56
|
-
"
|
|
57
|
-
"orgx_emit_activity",
|
|
58
|
-
"orgx_report_progress",
|
|
59
|
-
"orgx_register_artifact",
|
|
60
|
-
"orgx_request_decision",
|
|
61
|
-
"orgx_spawn_check",
|
|
34
|
+
...ORGX_BASE_TOOLS,
|
|
35
|
+
"update_agent_config",
|
|
62
36
|
// Operations is allowed to do explicit changesets for remediation/runbooks.
|
|
63
37
|
"orgx_apply_changeset",
|
|
38
|
+
// Stream reassignment is a targeted operational mutation.
|
|
39
|
+
"orgx_reassign_stream",
|
|
40
|
+
"orgx_reassign_streams",
|
|
64
41
|
],
|
|
65
42
|
orchestration: [
|
|
66
|
-
|
|
67
|
-
"
|
|
68
|
-
"orgx_emit_activity",
|
|
69
|
-
"orgx_report_progress",
|
|
70
|
-
"orgx_register_artifact",
|
|
71
|
-
"orgx_request_decision",
|
|
72
|
-
"orgx_spawn_check",
|
|
43
|
+
...ORGX_BASE_TOOLS,
|
|
44
|
+
"update_agent_config",
|
|
73
45
|
// Orchestrator is the primary mutation surface by design.
|
|
74
46
|
"orgx_apply_changeset",
|
|
47
|
+
"orgx_reassign_stream",
|
|
48
|
+
"orgx_reassign_streams",
|
|
75
49
|
],
|
|
76
50
|
};
|
|
77
51
|
function isRecord(value) {
|
|
@@ -6,10 +6,17 @@ export type NextUpPinnedEntry = {
|
|
|
6
6
|
createdAt: string;
|
|
7
7
|
updatedAt: string;
|
|
8
8
|
};
|
|
9
|
+
export type NextUpSuppressedEntry = {
|
|
10
|
+
initiativeId: string;
|
|
11
|
+
workstreamId: string;
|
|
12
|
+
createdAt: string;
|
|
13
|
+
updatedAt: string;
|
|
14
|
+
};
|
|
9
15
|
type PersistedNextUpQueue = {
|
|
10
|
-
version: 1;
|
|
16
|
+
version: 1 | 2;
|
|
11
17
|
updatedAt: string;
|
|
12
18
|
pins: NextUpPinnedEntry[];
|
|
19
|
+
suppressions: NextUpSuppressedEntry[];
|
|
13
20
|
};
|
|
14
21
|
export declare function readNextUpQueuePins(): PersistedNextUpQueue;
|
|
15
22
|
export declare function upsertNextUpQueuePin(input: {
|
|
@@ -28,4 +35,12 @@ export declare function setNextUpQueuePinOrder(input: {
|
|
|
28
35
|
workstreamId: string;
|
|
29
36
|
}>;
|
|
30
37
|
}): PersistedNextUpQueue;
|
|
38
|
+
export declare function suppressNextUpQueueItem(input: {
|
|
39
|
+
initiativeId: string;
|
|
40
|
+
workstreamId: string;
|
|
41
|
+
}): PersistedNextUpQueue;
|
|
42
|
+
export declare function unsuppressNextUpQueueItem(input: {
|
|
43
|
+
initiativeId: string;
|
|
44
|
+
workstreamId: string;
|
|
45
|
+
}): PersistedNextUpQueue;
|
|
31
46
|
export {};
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { existsSync, readFileSync, } from "node:fs";
|
|
2
2
|
import { getOrgxPluginConfigDir, getOrgxPluginConfigPath } from "./paths.js";
|
|
3
3
|
import { backupCorruptFileSync, writeJsonFileAtomicSync } from "./fs-utils.js";
|
|
4
|
+
import { ensureStoreDirSync, parseJsonSafe, } from "./stores/json-store.js";
|
|
4
5
|
const MAX_PINS = 240;
|
|
6
|
+
const MAX_SUPPRESSIONS = 2_000;
|
|
5
7
|
function storeDir() {
|
|
6
8
|
return getOrgxPluginConfigDir();
|
|
7
9
|
}
|
|
@@ -9,22 +11,7 @@ function storeFile() {
|
|
|
9
11
|
return getOrgxPluginConfigPath("next-up-queue.json");
|
|
10
12
|
}
|
|
11
13
|
function ensureStoreDir() {
|
|
12
|
-
|
|
13
|
-
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
14
|
-
try {
|
|
15
|
-
chmodSync(dir, 0o700);
|
|
16
|
-
}
|
|
17
|
-
catch {
|
|
18
|
-
// best effort
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
function parseJson(value) {
|
|
22
|
-
try {
|
|
23
|
-
return JSON.parse(value);
|
|
24
|
-
}
|
|
25
|
-
catch {
|
|
26
|
-
return null;
|
|
27
|
-
}
|
|
14
|
+
ensureStoreDirSync(storeDir());
|
|
28
15
|
}
|
|
29
16
|
function normalizeNullableString(value) {
|
|
30
17
|
if (typeof value !== "string")
|
|
@@ -42,31 +29,56 @@ function normalizeEntry(input) {
|
|
|
42
29
|
updatedAt: input.updatedAt,
|
|
43
30
|
};
|
|
44
31
|
}
|
|
32
|
+
function normalizeSuppression(input) {
|
|
33
|
+
return {
|
|
34
|
+
initiativeId: input.initiativeId.trim(),
|
|
35
|
+
workstreamId: input.workstreamId.trim(),
|
|
36
|
+
createdAt: input.createdAt,
|
|
37
|
+
updatedAt: input.updatedAt,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
function entryKey(input) {
|
|
41
|
+
return `${input.initiativeId}:${input.workstreamId}`;
|
|
42
|
+
}
|
|
43
|
+
function createEmptyStore() {
|
|
44
|
+
return {
|
|
45
|
+
version: 2,
|
|
46
|
+
updatedAt: new Date().toISOString(),
|
|
47
|
+
pins: [],
|
|
48
|
+
suppressions: [],
|
|
49
|
+
};
|
|
50
|
+
}
|
|
45
51
|
export function readNextUpQueuePins() {
|
|
46
52
|
const file = storeFile();
|
|
47
53
|
try {
|
|
48
54
|
if (!existsSync(file)) {
|
|
49
|
-
return
|
|
55
|
+
return createEmptyStore();
|
|
50
56
|
}
|
|
51
57
|
const raw = readFileSync(file, "utf8");
|
|
52
|
-
const parsed =
|
|
58
|
+
const parsed = parseJsonSafe(raw);
|
|
53
59
|
if (!parsed || typeof parsed !== "object") {
|
|
54
60
|
backupCorruptFileSync(file);
|
|
55
|
-
return
|
|
61
|
+
return createEmptyStore();
|
|
56
62
|
}
|
|
57
63
|
const pins = Array.isArray(parsed.pins) ? parsed.pins : [];
|
|
64
|
+
const suppressions = Array.isArray(parsed.suppressions)
|
|
65
|
+
? parsed.suppressions
|
|
66
|
+
: [];
|
|
58
67
|
return {
|
|
59
|
-
version:
|
|
68
|
+
version: 2,
|
|
60
69
|
updatedAt: typeof parsed.updatedAt === "string"
|
|
61
70
|
? parsed.updatedAt
|
|
62
71
|
: new Date().toISOString(),
|
|
63
72
|
pins: pins
|
|
64
73
|
.filter((entry) => Boolean(entry && typeof entry === "object"))
|
|
65
74
|
.map((entry) => normalizeEntry(entry)),
|
|
75
|
+
suppressions: suppressions
|
|
76
|
+
.filter((entry) => Boolean(entry && typeof entry === "object"))
|
|
77
|
+
.map((entry) => normalizeSuppression(entry)),
|
|
66
78
|
};
|
|
67
79
|
}
|
|
68
80
|
catch {
|
|
69
|
-
return
|
|
81
|
+
return createEmptyStore();
|
|
70
82
|
}
|
|
71
83
|
}
|
|
72
84
|
export function upsertNextUpQueuePin(input) {
|
|
@@ -79,7 +91,7 @@ export function upsertNextUpQueuePin(input) {
|
|
|
79
91
|
const now = new Date().toISOString();
|
|
80
92
|
const next = readNextUpQueuePins();
|
|
81
93
|
const key = `${initiativeId}:${workstreamId}`;
|
|
82
|
-
const existing = next.pins.find((pin) =>
|
|
94
|
+
const existing = next.pins.find((pin) => entryKey(pin) === key);
|
|
83
95
|
const updated = normalizeEntry({
|
|
84
96
|
initiativeId,
|
|
85
97
|
workstreamId,
|
|
@@ -88,7 +100,8 @@ export function upsertNextUpQueuePin(input) {
|
|
|
88
100
|
createdAt: existing?.createdAt ?? now,
|
|
89
101
|
updatedAt: now,
|
|
90
102
|
});
|
|
91
|
-
next.pins = [updated, ...next.pins.filter((pin) =>
|
|
103
|
+
next.pins = [updated, ...next.pins.filter((pin) => entryKey(pin) !== key)].slice(0, MAX_PINS);
|
|
104
|
+
next.suppressions = next.suppressions.filter((suppression) => entryKey(suppression) !== key);
|
|
92
105
|
next.updatedAt = now;
|
|
93
106
|
try {
|
|
94
107
|
writeJsonFileAtomicSync(storeFile(), next, 0o600);
|
|
@@ -107,7 +120,7 @@ export function removeNextUpQueuePin(input) {
|
|
|
107
120
|
ensureStoreDir();
|
|
108
121
|
const next = readNextUpQueuePins();
|
|
109
122
|
const key = `${initiativeId}:${workstreamId}`;
|
|
110
|
-
const filtered = next.pins.filter((pin) =>
|
|
123
|
+
const filtered = next.pins.filter((pin) => entryKey(pin) !== key);
|
|
111
124
|
if (filtered.length === next.pins.length)
|
|
112
125
|
return next;
|
|
113
126
|
next.pins = filtered;
|
|
@@ -158,6 +171,39 @@ export function setNextUpQueuePinOrder(input) {
|
|
|
158
171
|
ordered.push(pin);
|
|
159
172
|
}
|
|
160
173
|
next.pins = ordered.slice(0, MAX_PINS);
|
|
174
|
+
if (seen.size > 0 && next.suppressions.length > 0) {
|
|
175
|
+
next.suppressions = next.suppressions.filter((suppression) => !seen.has(entryKey(suppression)));
|
|
176
|
+
}
|
|
177
|
+
next.updatedAt = now;
|
|
178
|
+
try {
|
|
179
|
+
writeJsonFileAtomicSync(storeFile(), next, 0o600);
|
|
180
|
+
}
|
|
181
|
+
catch {
|
|
182
|
+
// best effort
|
|
183
|
+
}
|
|
184
|
+
return next;
|
|
185
|
+
}
|
|
186
|
+
export function suppressNextUpQueueItem(input) {
|
|
187
|
+
const initiativeId = input.initiativeId.trim();
|
|
188
|
+
const workstreamId = input.workstreamId.trim();
|
|
189
|
+
if (!initiativeId || !workstreamId) {
|
|
190
|
+
return readNextUpQueuePins();
|
|
191
|
+
}
|
|
192
|
+
ensureStoreDir();
|
|
193
|
+
const now = new Date().toISOString();
|
|
194
|
+
const next = readNextUpQueuePins();
|
|
195
|
+
const key = `${initiativeId}:${workstreamId}`;
|
|
196
|
+
const existing = next.suppressions.find((suppression) => entryKey(suppression) === key);
|
|
197
|
+
next.suppressions = [
|
|
198
|
+
normalizeSuppression({
|
|
199
|
+
initiativeId,
|
|
200
|
+
workstreamId,
|
|
201
|
+
createdAt: existing?.createdAt ?? now,
|
|
202
|
+
updatedAt: now,
|
|
203
|
+
}),
|
|
204
|
+
...next.suppressions.filter((suppression) => entryKey(suppression) !== key),
|
|
205
|
+
].slice(0, MAX_SUPPRESSIONS);
|
|
206
|
+
next.pins = next.pins.filter((pin) => entryKey(pin) !== key);
|
|
161
207
|
next.updatedAt = now;
|
|
162
208
|
try {
|
|
163
209
|
writeJsonFileAtomicSync(storeFile(), next, 0o600);
|
|
@@ -167,3 +213,25 @@ export function setNextUpQueuePinOrder(input) {
|
|
|
167
213
|
}
|
|
168
214
|
return next;
|
|
169
215
|
}
|
|
216
|
+
export function unsuppressNextUpQueueItem(input) {
|
|
217
|
+
const initiativeId = input.initiativeId.trim();
|
|
218
|
+
const workstreamId = input.workstreamId.trim();
|
|
219
|
+
if (!initiativeId || !workstreamId) {
|
|
220
|
+
return readNextUpQueuePins();
|
|
221
|
+
}
|
|
222
|
+
ensureStoreDir();
|
|
223
|
+
const next = readNextUpQueuePins();
|
|
224
|
+
const key = `${initiativeId}:${workstreamId}`;
|
|
225
|
+
const filtered = next.suppressions.filter((suppression) => entryKey(suppression) !== key);
|
|
226
|
+
if (filtered.length === next.suppressions.length)
|
|
227
|
+
return next;
|
|
228
|
+
next.suppressions = filtered;
|
|
229
|
+
next.updatedAt = new Date().toISOString();
|
|
230
|
+
try {
|
|
231
|
+
writeJsonFileAtomicSync(storeFile(), next, 0o600);
|
|
232
|
+
}
|
|
233
|
+
catch {
|
|
234
|
+
// best effort
|
|
235
|
+
}
|
|
236
|
+
return next;
|
|
237
|
+
}
|
package/dist/outbox.d.ts
CHANGED
|
@@ -11,6 +11,10 @@ export interface OutboxEvent {
|
|
|
11
11
|
payload: Record<string, unknown>;
|
|
12
12
|
/** Converted to a LiveActivityItem for dashboard display. */
|
|
13
13
|
activityItem: LiveActivityItem;
|
|
14
|
+
/** Internal replay diagnostics for bounded retry/dead-letter handling. */
|
|
15
|
+
replayFailures?: number;
|
|
16
|
+
lastReplayError?: string | null;
|
|
17
|
+
lastReplayAt?: string | null;
|
|
14
18
|
}
|
|
15
19
|
export interface OutboxSummary {
|
|
16
20
|
pendingTotal: number;
|
|
@@ -18,6 +22,7 @@ export interface OutboxSummary {
|
|
|
18
22
|
oldestEventAt: string | null;
|
|
19
23
|
newestEventAt: string | null;
|
|
20
24
|
}
|
|
25
|
+
export declare function appendOutboxDeadLetter(sessionId: string, event: OutboxEvent, reason: string, error?: string | null): Promise<void>;
|
|
21
26
|
export declare function readOutbox(sessionId: string): Promise<OutboxEvent[]>;
|
|
22
27
|
export declare function appendToOutbox(sessionId: string, event: OutboxEvent): Promise<void>;
|
|
23
28
|
export declare function replaceOutbox(sessionId: string, events: OutboxEvent[]): Promise<void>;
|
package/dist/outbox.js
CHANGED
|
@@ -4,8 +4,9 @@
|
|
|
4
4
|
* Events are flushed on next successful sync.
|
|
5
5
|
*/
|
|
6
6
|
import { join } from "node:path";
|
|
7
|
-
import { readFile, writeFile, mkdir, chmod, rename, unlink, readdir, } from "node:fs/promises";
|
|
7
|
+
import { readFile, writeFile, mkdir, chmod, rename, unlink, readdir, appendFile, } from "node:fs/promises";
|
|
8
8
|
import { randomUUID } from "node:crypto";
|
|
9
|
+
import { classifyOutboxReplaySkip } from "./event-sanitization.js";
|
|
9
10
|
import { getOrgxOutboxDir } from "./paths.js";
|
|
10
11
|
function outboxDir() {
|
|
11
12
|
return getOrgxOutboxDir();
|
|
@@ -36,6 +37,11 @@ async function hardenPath(path, mode) {
|
|
|
36
37
|
// best effort
|
|
37
38
|
}
|
|
38
39
|
}
|
|
40
|
+
/** Events older than 7 days are stale — sync will never recover them. */
|
|
41
|
+
const OUTBOX_EVENT_TTL_MS = 7 * 24 * 60 * 60_000;
|
|
42
|
+
/** Hard cap per session to prevent unbounded growth if sync repeatedly fails. */
|
|
43
|
+
const OUTBOX_MAX_EVENTS_PER_SESSION = 500;
|
|
44
|
+
const DEAD_LETTER_DIRNAME = "_dead-letter";
|
|
39
45
|
async function ensureDir() {
|
|
40
46
|
const dir = outboxDir();
|
|
41
47
|
try {
|
|
@@ -46,6 +52,12 @@ async function ensureDir() {
|
|
|
46
52
|
// Directory may already exist
|
|
47
53
|
}
|
|
48
54
|
}
|
|
55
|
+
function deadLetterDir() {
|
|
56
|
+
return join(outboxDir(), DEAD_LETTER_DIRNAME);
|
|
57
|
+
}
|
|
58
|
+
function deadLetterPath(sessionId) {
|
|
59
|
+
return join(deadLetterDir(), `${normalizeSessionId(sessionId)}.jsonl`);
|
|
60
|
+
}
|
|
49
61
|
function outboxPath(sessionId) {
|
|
50
62
|
return join(outboxDir(), `${normalizeSessionId(sessionId)}.json`);
|
|
51
63
|
}
|
|
@@ -89,13 +101,79 @@ async function writeFileAtomic(targetPath, content, mode) {
|
|
|
89
101
|
}
|
|
90
102
|
await hardenPath(targetPath, mode);
|
|
91
103
|
}
|
|
104
|
+
async function appendOutboxDeadLetterRecord(sessionId, record) {
|
|
105
|
+
await ensureDir();
|
|
106
|
+
const dir = deadLetterDir();
|
|
107
|
+
await mkdir(dir, { recursive: true, mode: 0o700 });
|
|
108
|
+
await hardenPath(dir, 0o700);
|
|
109
|
+
const targetPath = deadLetterPath(sessionId);
|
|
110
|
+
await appendFile(targetPath, `${JSON.stringify(record)}\n`, {
|
|
111
|
+
encoding: "utf8",
|
|
112
|
+
mode: 0o600,
|
|
113
|
+
});
|
|
114
|
+
await hardenPath(targetPath, 0o600);
|
|
115
|
+
}
|
|
116
|
+
export async function appendOutboxDeadLetter(sessionId, event, reason, error) {
|
|
117
|
+
const droppedAt = new Date().toISOString();
|
|
118
|
+
await appendOutboxDeadLetterRecord(sessionId, {
|
|
119
|
+
droppedAt,
|
|
120
|
+
queueId: normalizeSessionId(sessionId),
|
|
121
|
+
reason,
|
|
122
|
+
error: error ?? null,
|
|
123
|
+
event,
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
/** Drop stale events and enforce per-session cap. Returns true if any were removed. */
|
|
127
|
+
function pruneOutboxEvents(events) {
|
|
128
|
+
const cutoff = Date.now() - OUTBOX_EVENT_TTL_MS;
|
|
129
|
+
const fresh = events.filter((e) => {
|
|
130
|
+
const epoch = Date.parse(e.timestamp);
|
|
131
|
+
return Number.isFinite(epoch) && epoch >= cutoff;
|
|
132
|
+
});
|
|
133
|
+
// Keep newest events if over cap.
|
|
134
|
+
const capped = fresh.length > OUTBOX_MAX_EVENTS_PER_SESSION
|
|
135
|
+
? fresh.slice(fresh.length - OUTBOX_MAX_EVENTS_PER_SESSION)
|
|
136
|
+
: fresh;
|
|
137
|
+
return { pruned: capped, changed: capped.length !== events.length };
|
|
138
|
+
}
|
|
92
139
|
export async function readOutbox(sessionId) {
|
|
93
|
-
|
|
140
|
+
let targetPath;
|
|
141
|
+
try {
|
|
142
|
+
targetPath = outboxPath(sessionId);
|
|
143
|
+
}
|
|
144
|
+
catch {
|
|
145
|
+
return [];
|
|
146
|
+
}
|
|
94
147
|
try {
|
|
95
148
|
const raw = await readFile(targetPath, "utf8");
|
|
96
149
|
try {
|
|
97
150
|
const parsed = JSON.parse(raw);
|
|
98
|
-
|
|
151
|
+
const events = Array.isArray(parsed) ? parsed : [];
|
|
152
|
+
const { pruned, changed } = pruneOutboxEvents(events);
|
|
153
|
+
const filtered = [];
|
|
154
|
+
let filteredChanged = changed;
|
|
155
|
+
for (const event of pruned) {
|
|
156
|
+
const skipReason = classifyOutboxReplaySkip(event);
|
|
157
|
+
if (!skipReason) {
|
|
158
|
+
filtered.push(event);
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
filteredChanged = true;
|
|
162
|
+
await appendOutboxDeadLetter(sessionId, event, `pruned_on_read:${skipReason}`, null);
|
|
163
|
+
}
|
|
164
|
+
// Write back if stale events were dropped.
|
|
165
|
+
if (filteredChanged) {
|
|
166
|
+
if (filtered.length === 0) {
|
|
167
|
+
try {
|
|
168
|
+
await unlink(targetPath);
|
|
169
|
+
}
|
|
170
|
+
catch { /* ok */ }
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
await writeFileAtomic(targetPath, JSON.stringify(filtered), 0o600);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return filtered;
|
|
99
177
|
}
|
|
100
178
|
catch {
|
|
101
179
|
await backupCorruptOutboxFile(targetPath);
|
|
@@ -107,9 +185,21 @@ export async function readOutbox(sessionId) {
|
|
|
107
185
|
}
|
|
108
186
|
}
|
|
109
187
|
export async function appendToOutbox(sessionId, event) {
|
|
188
|
+
let normalizedSessionId;
|
|
189
|
+
try {
|
|
190
|
+
normalizedSessionId = normalizeSessionId(sessionId);
|
|
191
|
+
}
|
|
192
|
+
catch {
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
const skipReason = classifyOutboxReplaySkip(event);
|
|
196
|
+
if (skipReason) {
|
|
197
|
+
await appendOutboxDeadLetter(normalizedSessionId, event, `suppressed_on_append:${skipReason}`, null);
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
110
200
|
await ensureDir();
|
|
111
|
-
const targetPath = outboxPath(
|
|
112
|
-
const existing = await readOutbox(
|
|
201
|
+
const targetPath = outboxPath(normalizedSessionId);
|
|
202
|
+
const existing = await readOutbox(normalizedSessionId);
|
|
113
203
|
const idx = existing.findIndex((item) => item.id === event.id);
|
|
114
204
|
if (idx >= 0) {
|
|
115
205
|
existing[idx] = event;
|
|
@@ -117,11 +207,18 @@ export async function appendToOutbox(sessionId, event) {
|
|
|
117
207
|
else {
|
|
118
208
|
existing.push(event);
|
|
119
209
|
}
|
|
120
|
-
await writeFileAtomic(targetPath, JSON.stringify(existing
|
|
210
|
+
await writeFileAtomic(targetPath, JSON.stringify(existing), 0o600);
|
|
121
211
|
}
|
|
122
212
|
export async function replaceOutbox(sessionId, events) {
|
|
213
|
+
let normalizedSessionId;
|
|
214
|
+
try {
|
|
215
|
+
normalizedSessionId = normalizeSessionId(sessionId);
|
|
216
|
+
}
|
|
217
|
+
catch {
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
123
220
|
await ensureDir();
|
|
124
|
-
const targetPath = outboxPath(
|
|
221
|
+
const targetPath = outboxPath(normalizedSessionId);
|
|
125
222
|
if (events.length === 0) {
|
|
126
223
|
try {
|
|
127
224
|
await unlink(targetPath);
|
|
@@ -132,7 +229,7 @@ export async function replaceOutbox(sessionId, events) {
|
|
|
132
229
|
return;
|
|
133
230
|
}
|
|
134
231
|
}
|
|
135
|
-
await writeFileAtomic(targetPath, JSON.stringify(events
|
|
232
|
+
await writeFileAtomic(targetPath, JSON.stringify(events), 0o600);
|
|
136
233
|
}
|
|
137
234
|
export async function readAllOutboxItems() {
|
|
138
235
|
try {
|
|
@@ -209,8 +306,15 @@ export async function readOutboxSummary() {
|
|
|
209
306
|
}
|
|
210
307
|
}
|
|
211
308
|
export async function clearOutbox(sessionId) {
|
|
309
|
+
let normalizedSessionId;
|
|
310
|
+
try {
|
|
311
|
+
normalizedSessionId = normalizeSessionId(sessionId);
|
|
312
|
+
}
|
|
313
|
+
catch {
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
212
316
|
try {
|
|
213
|
-
await unlink(outboxPath(
|
|
317
|
+
await unlink(outboxPath(normalizedSessionId));
|
|
214
318
|
}
|
|
215
319
|
catch {
|
|
216
320
|
// File may not exist
|