Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
3e41d49
feat(onboard): persist messaging plan in session for resume
sandl99 Jun 7, 2026
461c399
refactor(onboard): move messaging plan env helpers to messaging-chann…
sandl99 Jun 7, 2026
9a4ed8e
fix(onboard): add plan identity guard and test coverage for resume pl…
sandl99 Jun 7, 2026
2d05201
fix(onboard): prefer env-staged plan over session plan on non-interac…
sandl99 Jun 7, 2026
673d441
refactor(onboard): establish messagingPlan as session source of truth
sandl99 Jun 7, 2026
6913199
refactor(messaging): migrate conflict detection to manifest-plan arch…
sandl99 Jun 7, 2026
9e57fc0
fix(messaging): address CodeRabbit review on conflict detection
sandl99 Jun 7, 2026
aa028bd
fix(messaging): address Advisor review findings on conflict detection
sandl99 Jun 7, 2026
b294a4f
Merge remote-tracking branch 'origin/main' into feat/phase-4a-messagi…
sandl99 Jun 7, 2026
d12db3c
fix(messaging): decouple channel registry from conflict-detection cor…
sandl99 Jun 7, 2026
50bbc4e
Merge branch 'main' into feat/phase-4a-messaging-conflict-plan-driven
sandl99 Jun 7, 2026
b2117c8
fix(messaging): address PR Review Advisor findings on conflict detection
sandl99 Jun 7, 2026
b545d0a
fix(messaging): validate env plan sandbox identity and update stale c…
sandl99 Jun 7, 2026
7bee701
fix(messaging): restore legacy hash fallback and fix disabled-channel…
sandl99 Jun 7, 2026
40142b9
fix(messaging): scope legacy hashes by channel and intersect plan bin…
sandl99 Jun 7, 2026
50633fe
fix(messaging): use manifest keys for multi-credential conflict compa…
sandl99 Jun 7, 2026
2e6f1e3
fix(messaging): close plan coverage gap and migrate add-channel to ma…
sandl99 Jun 7, 2026
5e3977a
refactor(messaging): remove providerCredentialHashes from conflict de…
sandl99 Jun 7, 2026
0fe3685
refactor(messaging): remove providerCredentialHashes from all write p…
sandl99 Jun 7, 2026
a2c6510
chore(ci): lower test size budget for channels-add-preset and onboard…
sandl99 Jun 7, 2026
08a563d
merge: resolve test-file-size-budget conflict with origin/main
sandl99 Jun 7, 2026
34e9762
test(messaging): update token rotation plan hash check
sandl99 Jun 8, 2026
ce76283
Merge branch 'main' into feat/phase-4a-messaging-conflict-plan-driven
sandl99 Jun 8, 2026
1d66456
refactor(messaging): remove conflict adapter
sandl99 Jun 8, 2026
3634df4
Merge branch 'main' into feat/phase-4a-messaging-conflict-plan-driven
sandl99 Jun 8, 2026
50f459a
fix(messaging): fail closed on channel conflict errors
sandl99 Jun 8, 2026
b2031d9
refactor(messaging): split conflict detection modules
sandl99 Jun 8, 2026
fab8c69
fix(messaging): keep legacy backfill pre-plan only
sandl99 Jun 8, 2026
be2bbe0
Merge branch 'main' into feat/phase-4a-messaging-conflict-plan-driven
sandl99 Jun 8, 2026
bd847fc
fix(messaging): ignore credentialless conflict comparisons
Jun 8, 2026
f793437
refactor(messaging): persist session registry state as plans
sandl99 Jun 8, 2026
2d0bfe3
fix(messaging): preserve legacy conflict fallback
sandl99 Jun 8, 2026
4350317
refactor(messaging): remove legacy conflict fallback
sandl99 Jun 8, 2026
51db380
Potential fix for pull request finding 'CodeQL / Unused variable, imp…
sandl99 Jun 8, 2026
06d5b9c
refactor(messaging): extract registry messaging helpers
sandl99 Jun 8, 2026
b74c6cd
Merge remote-tracking branch 'origin/main' into feat/messaging-plan-s…
sandl99 Jun 8, 2026
6bdf4c9
refactor(state): extract registry messaging mutations
sandl99 Jun 8, 2026
7652348
fix(messaging): preserve empty channel plans on rebuild
sandl99 Jun 9, 2026
1fc21ab
Merge remote-tracking branch 'origin/main' into feat/messaging-plan-s…
sandl99 Jun 9, 2026
d63bf03
test(onboard): split sandbox messaging plan coverage
sandl99 Jun 9, 2026
a30c537
Merge branch 'main' into feat/messaging-plan-session-registry
sandl99 Jun 9, 2026
3e8d1f5
fix(review): address messaging plan review comments
sandl99 Jun 9, 2026
c0ccf98
Merge remote-tracking branch 'origin/feat/messaging-plan-session-regi…
sandl99 Jun 9, 2026
6a79070
test(policy): keep review regression budget-neutral
sandl99 Jun 9, 2026
91c9dab
Merge remote-tracking branch 'origin/main' into feat/messaging-plan-s…
sandl99 Jun 9, 2026
c824a60
Merge branch 'main' into feat/messaging-plan-session-registry
cv Jun 9, 2026
0ab2047
merge: resolve main conflict for PR 4945
sandl99 Jun 9, 2026
b8b44d4
fix(ci): satisfy PR guardrails
sandl99 Jun 9, 2026
84060fd
Potential fix for pull request finding 'CodeQL / Unused variable, imp…
sandl99 Jun 9, 2026
05a1d9b
merge: sync PR 4945 with main
sandl99 Jun 9, 2026
abd1d20
fix(security): validate persisted messaging plans
sandl99 Jun 9, 2026
a0886e3
fix(channels): require messaging plan persistence
sandl99 Jun 9, 2026
8bb4f22
Potential fix for pull request finding 'CodeQL / Comparison between i…
sandl99 Jun 9, 2026
acd55d3
Merge remote-tracking branch 'origin/main' into feat/messaging-plan-s…
sandl99 Jun 10, 2026
5483a28
test(cli): extend status json timeout
sandl99 Jun 10, 2026
7b46c5a
test(cli): format status json timeout
sandl99 Jun 10, 2026
0cec16e
fix(messaging): validate channel plan before start noop
sandl99 Jun 10, 2026
5b538fb
refactor(messaging): split plan validator modules
sandl99 Jun 10, 2026
ac2d670
fix(messaging): reorder plan validator null guards
sandl99 Jun 10, 2026
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
6 changes: 3 additions & 3 deletions ci/test-file-size-budget.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@
"nemoclaw/src/commands/migration-state.test.ts": 1566,
"src/lib/inference/nim.test.ts": 2068,
"src/lib/onboard/preflight.test.ts": 1905,
"test/channels-add-preset.test.ts": 1872,
"test/channels-add-preset.test.ts": 1866,
"test/generate-openclaw-config.test.ts": 2091,
"test/install-preflight.test.ts": 4396,
"test/nemoclaw-start.test.ts": 5289,
"test/onboard-messaging.test.ts": 2097,
"test/onboard-selection.test.ts": 6922,
"test/onboard.test.ts": 4874,
"test/policies.test.ts": 2763
"test/onboard.test.ts": 4873,
"test/policies.test.ts": 2756
}
}
4 changes: 1 addition & 3 deletions src/lib/actions/inference-route-api.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,7 @@ function session(overrides: Partial<Session> = {}): Session {
routerCredentialHash: null,
webSearchConfig: null,
policyPresets: null,
messagingChannels: null,
messagingChannelConfig: null,
disabledChannels: null,
messagingPlan: null,
migratedLegacyValueHashes: null,
hermesToolGateways: null,
gpuPassthrough: false,
Expand Down
4 changes: 1 addition & 3 deletions src/lib/actions/inference-set.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,7 @@ function baseSession(overrides: Partial<Session> = {}): Session {
routerCredentialHash: null,
webSearchConfig: null,
policyPresets: null,
messagingChannels: null,
messagingChannelConfig: null,
disabledChannels: null,
messagingPlan: null,
migratedLegacyValueHashes: null,
hermesToolGateways: null,
gpuPassthrough: false,
Expand Down
19 changes: 12 additions & 7 deletions src/lib/actions/sandbox/channel-status.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// SPDX-License-Identifier: Apache-2.0

import { describe, expect, it, vi } from "vitest";
import { makeMessagingState } from "../../../../test/helpers/messaging-plan-fixtures";

// The orchestrator transitively pulls in policy/index.ts and agent/defs.ts,
// both of which require runner.ts via CJS; runner.ts uses `require()` calls
Expand All @@ -15,6 +16,14 @@ vi.mock("../../policy", () => ({

vi.mock("../../state/registry", () => ({
getSandbox: vi.fn(),
getConfiguredMessagingChannelsFromEntry: vi.fn((entry?: SandboxEntry | null) =>
(entry?.messaging?.plan.channels ?? [])
.filter((channel) => channel.configured)
.map((channel) => channel.channelId),
),
getDisabledMessagingChannelsFromEntry: vi.fn((entry?: SandboxEntry | null) =>
entry?.messaging?.plan.disabledChannels ? [...entry.messaging.plan.disabledChannels] : [],
),
}));

vi.mock("../../agent/defs", () => ({
Expand Down Expand Up @@ -104,15 +113,11 @@ function fakeAgent(name: "openclaw" | "hermes" = "openclaw"): AgentDefinition {
} as unknown as AgentDefinition;
}

function entry(
messagingChannels: string[] = ["whatsapp"],
disabledChannels: string[] = [],
): SandboxEntry {
function entry(channelIds: string[] = ["whatsapp"], disabledChannels: string[] = []): SandboxEntry {
return {
name: "alpha",
agent: "openclaw",
messagingChannels,
disabledChannels,
messaging: makeMessagingState("alpha", channelIds, disabledChannels),
} as SandboxEntry;
}

Expand Down Expand Up @@ -434,7 +439,7 @@ describe("showSandboxChannelStatus (whatsapp)", () => {
expect(capturedCmd as unknown as string).toMatch(/pgrep -fa/);
});

it("skips the deep probe and reports paused state when WhatsApp is in disabledChannels", async () => {
it("skips the deep probe and reports paused state when WhatsApp is disabled in the plan", async () => {
// Regression guard: `channels stop whatsapp` deliberately drops the
// bridge and preset until the operator runs `channels start`. The
// status command should reflect that rather than probing a torn-down
Expand Down
17 changes: 10 additions & 7 deletions src/lib/actions/sandbox/channel-status.ts
Original file line number Diff line number Diff line change
Expand Up @@ -353,7 +353,9 @@ function buildWhatsappProbeInput(
}

const entry = deps.getSandbox(sandboxName);
const channelEnabledInRegistry = (entry?.messagingChannels ?? []).includes("whatsapp");
const channelEnabledInRegistry = registry
.getConfiguredMessagingChannelsFromEntry(entry)
.includes("whatsapp");

const appliedPresets = deps.getAppliedPresets(sandboxName);
const presetInRegistry = appliedPresets.includes("whatsapp");
Expand Down Expand Up @@ -440,8 +442,8 @@ function buildBasicChannelReport(
deps: Required<StatusDeps>,
): ChannelStatusReport {
const entry = deps.getSandbox(sandboxName);
const enabled = (entry?.messagingChannels ?? []).includes(channelName);
const disabled = (entry?.disabledChannels ?? []).includes(channelName);
const enabled = registry.getConfiguredMessagingChannelsFromEntry(entry).includes(channelName);
const disabled = registry.getDisabledMessagingChannelsFromEntry(entry).includes(channelName);
const appliedPresets = deps.getAppliedPresets(sandboxName);
const presetInRegistry = appliedPresets.includes(channelName);
const signals: DiagnosticSignal[] = [];
Expand Down Expand Up @@ -523,11 +525,12 @@ export async function showSandboxChannelStatus(

let channelName = channelArg;
if (!channelName) {
const enabled = (entry.messagingChannels ?? []).filter((name: string) => name === "whatsapp");
const configuredChannels = registry.getConfiguredMessagingChannelsFromEntry(entry);
const enabled = configuredChannels.filter((name: string) => name === "whatsapp");
if (enabled.length > 0) {
channelName = "whatsapp";
} else if ((entry.messagingChannels ?? []).length > 0) {
channelName = entry.messagingChannels?.[0];
} else if (configuredChannels.length > 0) {
channelName = configuredChannels[0];
} else {
channelName = "whatsapp";
}
Expand All @@ -551,7 +554,7 @@ export async function showSandboxChannelStatus(

const agent = deps.loadAgent(entry.agent || "openclaw");

const disabledChannels = new Set(entry.disabledChannels ?? []);
const disabledChannels = new Set(registry.getDisabledMessagingChannelsFromEntry(entry));
const channelIsPaused = disabledChannels.has(channelName);

let report: ChannelStatusReport;
Expand Down
14 changes: 4 additions & 10 deletions src/lib/actions/sandbox/doctor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -423,9 +423,9 @@ function channelRuntimeDoctorCheck(
}

function messagingDoctorCheck(sandboxName: string, sb: SandboxEntry): DoctorCheck {
const registeredChannels = Array.isArray(sb.messagingChannels) ? sb.messagingChannels : [];
const disabledChannels = new Set(Array.isArray(sb.disabledChannels) ? sb.disabledChannels : []);
const channels = registeredChannels.filter((channel: string) => !disabledChannels.has(channel));
const registeredChannels = registry.getConfiguredMessagingChannelsFromEntry(sb);
const disabledChannels = new Set(registry.getDisabledMessagingChannelsFromEntry(sb));
const channels = registry.getActiveMessagingChannelsFromEntry(sb);
const pausedChannels = registeredChannels.filter((channel: string) =>
disabledChannels.has(channel),
);
Expand Down Expand Up @@ -783,13 +783,7 @@ export async function runSandboxDoctor(
// #4156: bridge the gap between "configured" and "runtime-visible" — the
// existing messaging check above probes provider attachment, not whether
// OpenClaw's runtime config actually surfaces each enabled channel.
const registeredChannels = Array.isArray(sb.messagingChannels) ? sb.messagingChannels : [];
const disabledChannelsSet = new Set(
Array.isArray(sb.disabledChannels) ? sb.disabledChannels : [],
);
const enabledChannels = registeredChannels.filter(
(channel: string) => !disabledChannelsSet.has(channel),
);
const enabledChannels = registry.getActiveMessagingChannelsFromEntry(sb);
const runtimeCheck = channelRuntimeDoctorCheck(sandboxName, enabledChannels);
if (runtimeCheck) checks.push(runtimeCheck);
}
Expand Down
Loading
Loading