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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ release gates. Stable promotion and publication require approval of the exact te

### Beta development notes

- Restore the agent directory refresh to 03:00 local time after a spring-forward clock change
instead of retaining the transition day’s normalized 04:00 hour.

- Clarify that the network tool's policy is advisory in `/sudo` direct-host threads, with no
OS-level egress cutoff. Ordinary proxy-mode container enforcement is unchanged.

Expand Down
4 changes: 3 additions & 1 deletion FEATURES.md
Original file line number Diff line number Diff line change
Expand Up @@ -428,7 +428,9 @@ Slack’s native running/Stop label shows the resolved model and explicitly conf
recommends a channel without sending messages, delegating or transferring files. Organization
skills can supply company terminology; no company directory is hardcoded in the product skill.
- Built-in reconciliation refreshes channel metadata at startup and nightly at 03:00 in the host
local timezone without an engine turn or user schedule. Metadata caches narrow keyword searches;
local timezone without an engine turn or user schedule. A missing spring-forward 03:00 runs at
the first valid local time and the next day returns to 03:00; autumn runs once per local day.
Metadata caches narrow keyword searches;
returned recommendations still verify live metadata/membership and fresh gateway access.
The daemon registers its existing Slack lifecycle manager in the live connector registry;
disconnects fail closed and reconnects resolve the current client without retaining old readers.
Expand Down
6 changes: 6 additions & 0 deletions TEST-PLAN.md
Original file line number Diff line number Diff line change
Expand Up @@ -862,6 +862,12 @@ inbox). Record the address and the servers; never the password.

## Channel agent directory (2026-10-21)

- [x] DST rearm regression (2026-11-06): `test/agent-directory-refresh.test.js` runs in an
explicit `Europe/Bucharest` subprocess. Ordinary dates retain 03:00, the missing spring 03:00
normalizes to 04:00, the following day returns to 03:00 without drift, and the repeated autumn
hour produces only one refresh per local day. Disposable lifecycle acceptance also observes
the actual timer rearm across the spring gap; this does not claim a deployed overnight run.

- [x] Production boot integration: `test/agent-directory-slack-boot.test.js` uses the real Slack
manager and `createPlatformTransports({ slack })` with a scratch store and simulated provider.
Search uses its default verifier (no directory/connector override), returns the current channel
Expand Down
6 changes: 5 additions & 1 deletion src/gateway/agent-directory-refresh.js
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,11 @@ export async function verifyDirectoryChannel({ entry, createdBy, channelId }, de
export function nextAgentDirectoryRefreshDelay(at = Date.now()) {
const next = new Date(at);
next.setHours(3, 0, 0, 0);
if (next.getTime() <= at) next.setDate(next.getDate() + 1);
if (next.getTime() <= at) {
next.setDate(next.getDate() + 1);
// A missing 03:00 can normalize to 04:00; restore the intended hour on the next date.
next.setHours(3, 0, 0, 0);
}
return next.getTime() - at;
}

Expand Down
35 changes: 35 additions & 0 deletions test/agent-directory-refresh.test.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import test from "node:test";
import assert from "node:assert/strict";
import { spawnSync } from "node:child_process";
import { ensureTestEnv } from "./helpers.js";
ensureTestEnv();
const {
Expand Down Expand Up @@ -160,3 +161,37 @@ test("local-calendar 03:00 deadline is strictly future, including at the boundar
const tomorrow = new Date(2026, 9, 22, 3).getTime();
assert.equal(nextAgentDirectoryRefreshDelay(boundary), tomorrow - boundary);
});


test("local 03:00 refresh returns to 03:00 after the spring DST gap without autumn drift", () => {
const moduleUrl = new URL("../src/gateway/agent-directory-refresh.js", import.meta.url).href;
const script = `
const { nextAgentDirectoryRefreshDelay } = await import(${JSON.stringify(moduleUrl)});
const inputs = [
"2026-02-10T02:59:59+02:00",
"2026-03-28T03:00:00+02:00",
"2026-03-29T02:59:59+02:00",
"2026-03-29T04:00:00+03:00",
"2026-03-29T12:00:00+03:00",
"2026-10-24T03:00:00+03:00",
"2026-10-25T03:00:00+03:00",
];
console.log(JSON.stringify(inputs.map(input => {
const at = Date.parse(input);
return new Date(at + nextAgentDirectoryRefreshDelay(at)).toISOString();
})));
`;
const result = spawnSync(process.execPath, ["--input-type=module", "-e", script], {
env: { ...process.env, TZ: "Europe/Bucharest" }, encoding: "utf8", timeout: 10_000,
});
assert.equal(result.status, 0, result.stderr);
assert.deepEqual(JSON.parse(result.stdout.trim()), [
"2026-02-10T01:00:00.000Z",
"2026-03-29T01:00:00.000Z", // Missing local 03:00 normalizes to the first valid 04:00.
"2026-03-29T01:00:00.000Z",
"2026-03-30T00:00:00.000Z", // The next ordinary day must return to local 03:00.
"2026-03-30T00:00:00.000Z",
"2026-10-25T00:00:00.000Z",
"2026-10-26T01:00:00.000Z", // One deadline per local day across the repeated hour.
]);
});
Loading