switchroom 0.19.39 → 0.19.41
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/dist/agent-scheduler/index.js +10 -1
- package/dist/auth-broker/index.js +12 -3
- package/dist/cli/notion-write-pretool.mjs +10 -1
- package/dist/cli/switchroom.js +562 -196
- package/dist/host-control/main.js +287 -20
- package/dist/vault/approvals/kernel-server.js +12 -3
- package/dist/vault/broker/server.js +12 -3
- package/package.json +1 -1
- package/profiles/_base/start.sh.hbs +10 -0
- package/telegram-plugin/bridge/bridge.ts +2 -2
- package/telegram-plugin/dist/bridge/bridge.js +10 -2
- package/telegram-plugin/dist/gateway/gateway.js +549 -232
- package/telegram-plugin/dist/server.js +10 -2
- package/telegram-plugin/gateway/backstop-delivery.ts +48 -0
- package/telegram-plugin/gateway/checklist-fallback.ts +370 -0
- package/telegram-plugin/gateway/compaction-marker.ts +84 -0
- package/telegram-plugin/gateway/gateway.ts +72 -72
- package/telegram-plugin/gateway/liveness-wiring.ts +15 -0
- package/telegram-plugin/gateway/outbound-send-path.ts +20 -0
- package/telegram-plugin/gateway/outbox-sweep.ts +116 -18
- package/telegram-plugin/gateway/silence-poke-session-event.ts +13 -0
- package/telegram-plugin/gateway/stream-render.ts +39 -1
- package/telegram-plugin/gateway/turn-record-status.ts +80 -0
- package/telegram-plugin/hooks/compaction-marker-precompact.mjs +70 -0
- package/telegram-plugin/hooks/hooks.json +11 -0
- package/telegram-plugin/session-tail.ts +20 -0
- package/telegram-plugin/silence-poke.ts +28 -0
- package/telegram-plugin/tests/checklist-fallback.test.ts +317 -0
- package/telegram-plugin/tests/gateway-outbound-redact.test.ts +10 -6
- package/telegram-plugin/tests/outbox-delivery.test.ts +38 -1
- package/telegram-plugin/tests/outbox-flush-ack-claim-race.test.ts +213 -0
- package/telegram-plugin/tests/outbox-reply-then-recap-e2e.test.ts +1 -1
- package/telegram-plugin/tests/outbox-sweep-flood-breaker.test.ts +4 -4
- package/telegram-plugin/tests/outbox-sweep-listen-button.test.ts +71 -8
- package/telegram-plugin/tests/send-reply-golden.test.ts +47 -0
- package/telegram-plugin/tests/silence-poke-compaction.test.ts +222 -0
- package/telegram-plugin/tests/turn-record-status.test.ts +62 -0
|
@@ -10977,7 +10977,7 @@ var init_zod = __esm(() => {
|
|
|
10977
10977
|
});
|
|
10978
10978
|
|
|
10979
10979
|
// src/memory/observation-scopes.ts
|
|
10980
|
-
var OBSERVATION_SCOPES;
|
|
10980
|
+
var OBSERVATION_SCOPES, OBSERVATION_SCOPE_STRATEGIES;
|
|
10981
10981
|
var init_observation_scopes = __esm(() => {
|
|
10982
10982
|
OBSERVATION_SCOPES = [
|
|
10983
10983
|
"per_tag",
|
|
@@ -10985,10 +10985,16 @@ var init_observation_scopes = __esm(() => {
|
|
|
10985
10985
|
"all_combinations",
|
|
10986
10986
|
"shared"
|
|
10987
10987
|
];
|
|
10988
|
+
OBSERVATION_SCOPE_STRATEGIES = [
|
|
10989
|
+
"curated",
|
|
10990
|
+
"shared",
|
|
10991
|
+
"combined",
|
|
10992
|
+
"off"
|
|
10993
|
+
];
|
|
10988
10994
|
});
|
|
10989
10995
|
|
|
10990
10996
|
// src/config/schema.ts
|
|
10991
|
-
var CodeRepoEntrySchema, AgentBindMountSchema, HttpDiffPollSchema, PollSpecSchema, TelegramMessageActionSchema, WebhookActionSchema, ActionSpecSchema, ScheduleEntrySchema, AgentSoulSchema, AgentToolsSchema, ObservationScopesSchema, AgentMemorySchema, HookEntrySchema, AgentHooksSchema, SubagentSchema, SessionSchema, SessionContinuitySchema, webhookDispatchRule, TelegramChannelSchema, ChannelsSchema, TIMEZONE_REGEX, ApproverIdSchema, GoogleWorkspaceTierSchema, GoogleWorkspaceConfigSchema, LiteLLMConfigSchema, HindsightPerOpLlmSchema, HindsightConfigSchema, MicrosoftWorkspaceConfigSchema, NotionWorkspaceConfigSchema, AgentGoogleWorkspaceConfigSchema, MicrosoftAccountEmailSchema, MicrosoftToolTokenSchema, MicrosoftAccountBindingSchema, AgentMicrosoftWorkspaceConfigSchema, AgentNotionWorkspaceConfigSchema, ReactionsSchema, ReactionDispatchSchema, releaseBlockFields, ReleaseBlock, RootReleaseBlock, NetworkIsolationSchema, servesField, knowsField, profileFields, ProfileSchema, _omitExtends, defaultsFields, AgentDefaultsSchema, AgentSchema, TelegramConfigSchema, MemoryBackendConfigSchema, VaultConfigSchema, QuotaConfigSchema, AutoReleaseCheckSchema, HostControlConfigSchema, WebServiceConfigSchema, FleetHealthConfigSchema, HostdConfigSchema, CronEgressSchema, CronConfigSchema, UserSchema, SwitchroomConfigSchema;
|
|
10997
|
+
var CodeRepoEntrySchema, AgentBindMountSchema, HttpDiffPollSchema, PollSpecSchema, TelegramMessageActionSchema, WebhookActionSchema, ActionSpecSchema, ScheduleEntrySchema, AgentSoulSchema, AgentToolsSchema, ObservationScopesSchema, ObservationScopeStrategySchema, AgentMemorySchema, HookEntrySchema, AgentHooksSchema, SubagentSchema, SessionSchema, SessionContinuitySchema, webhookDispatchRule, TelegramChannelSchema, ChannelsSchema, TIMEZONE_REGEX, ApproverIdSchema, GoogleWorkspaceTierSchema, GoogleWorkspaceConfigSchema, LiteLLMConfigSchema, HindsightPerOpLlmSchema, HindsightConfigSchema, MicrosoftWorkspaceConfigSchema, NotionWorkspaceConfigSchema, AgentGoogleWorkspaceConfigSchema, MicrosoftAccountEmailSchema, MicrosoftToolTokenSchema, MicrosoftAccountBindingSchema, AgentMicrosoftWorkspaceConfigSchema, AgentNotionWorkspaceConfigSchema, ReactionsSchema, ReactionDispatchSchema, releaseBlockFields, ReleaseBlock, RootReleaseBlock, NetworkIsolationSchema, servesField, knowsField, profileFields, ProfileSchema, _omitExtends, defaultsFields, AgentDefaultsSchema, AgentSchema, TelegramConfigSchema, MemoryBackendConfigSchema, VaultConfigSchema, QuotaConfigSchema, AutoReleaseCheckSchema, HostControlConfigSchema, WebServiceConfigSchema, FleetHealthConfigSchema, HostdConfigSchema, CronEgressSchema, CronConfigSchema, UserSchema, SwitchroomConfigSchema;
|
|
10992
10998
|
var init_schema = __esm(() => {
|
|
10993
10999
|
init_zod();
|
|
10994
11000
|
init_observation_scopes();
|
|
@@ -11103,7 +11109,8 @@ var init_schema = __esm(() => {
|
|
|
11103
11109
|
allow: exports_external.array(exports_external.string()).default([]).describe("Allowed tools (use ['all'] for unrestricted)"),
|
|
11104
11110
|
deny: exports_external.array(exports_external.string()).default([]).describe("Denied tools (overrides allow)")
|
|
11105
11111
|
}).optional();
|
|
11106
|
-
ObservationScopesSchema = exports_external.enum(OBSERVATION_SCOPES).optional().describe("
|
|
11112
|
+
ObservationScopesSchema = exports_external.enum(OBSERVATION_SCOPES).optional().describe("Operator PIN for the per-row observation scope, overriding the " + 'default `curated` strategy (see docs/configuration.md). "shared" ' + "forces Hindsight's consolidation to write EVERY observation into ONE " + "global untagged scope — what several agents pooling one bank want so " + "their observations merge rather than sitting in parallel silos. " + '"combined" is the pin form of opting out (restores the pre-#4035 ' + "engine default). UNSET does NOT mean 'nothing on the wire': the " + "`curated` strategy still computes a scope per retain. Applies to " + "every retain path (Stop hook, " + "sidechain, boot reconcile, queue drain, backfill, session-handoff " + "mirror) and is carried on the queued payload, so a retain that fails " + "now and drains later still lands in this scope. Accepted values: " + `${OBSERVATION_SCOPES.join(", ")}. ` + "Cascade: override (per-agent wins over default).");
|
|
11113
|
+
ObservationScopeStrategySchema = exports_external.enum(OBSERVATION_SCOPE_STRATEGIES).optional().describe("Per-retain strategy that decides how the observation scope is " + 'computed when no `observation_scopes` pin is set. "curated" ' + "(the default since #4035) strips volatile per-session provenance " + "tags from each retain's consolidation scope — keeping the stable " + "semantic ones on the source fact — so an agent's observations dedup " + "and merge across sessions instead of pocketing one-per-session. " + '"shared" sends every retain to the one global untagged scope. ' + '"combined" / "off" opt OUT: no per-row scope goes on the wire, ' + "restoring the pre-feature engine default (byte-identical to an " + "unconfigured client). OMITTED BY DEFAULT: unset leaves the plugin's " + "own default (`curated`) in force. A manual `observation_scopes` pin " + "wins over this strategy. Accepted values: " + `${OBSERVATION_SCOPE_STRATEGIES.join(", ")}. ` + "Cascade: override (per-agent wins over default).");
|
|
11107
11114
|
AgentMemorySchema = exports_external.object({
|
|
11108
11115
|
collection: exports_external.string().describe("Hindsight collection name for this agent"),
|
|
11109
11116
|
auto_recall: exports_external.boolean().default(true).describe("Auto-search memories before each response"),
|
|
@@ -11139,6 +11146,7 @@ var init_schema = __esm(() => {
|
|
|
11139
11146
|
}).optional().describe("Personality traits (1-5 each) steering how this bank frames recall, " + "reflect, and observation synthesis — a coach leans empathy-high, a " + "lawyer/analyst leans skepticism/literalism-high. Maps to the engine's " + "flat `disposition_skepticism`/`_literalism`/`_empathy` fields. " + "Cascade: per-key merge (an agent overrides individual traits and " + "inherits the rest, matching `recall`)."),
|
|
11140
11147
|
directive_capture_nudge: exports_external.boolean().optional().describe("Deterministic directive-capture nudge (issue #2848 Stage B). When " + "on (switchroom default true — Stage A measured a ~55% miss rate on " + "durable corrections), the auto-recall hook regex-detects correction " + '/ standing-rule-shaped inbound ("always/never …", "from now on …", ' + `"stop doing …", a stated preference, "that's wrong, it's …") and ` + "appends a terse advisory to the turn's context telling the model to " + "persist the rule with mcp__hindsight__create_directive if it IS " + "durable. Detection is pure regex — the model does the judgment " + "in-session and calls create_directive itself (no model callsite, no " + "silent hook-side write). Set false to disable per-agent. " + "Cascade: override (per-agent wins over default)."),
|
|
11141
11148
|
observation_scopes: ObservationScopesSchema,
|
|
11149
|
+
observation_scope_strategy: ObservationScopeStrategySchema,
|
|
11142
11150
|
recall: exports_external.object({
|
|
11143
11151
|
max_memories: exports_external.number().int().min(0).optional().describe("Cap on the number of memories injected into the prompt by " + "auto-recall, regardless of token budget. Plugin default is 12. " + "0 disables the cap (all memories Hindsight returns are injected)."),
|
|
11144
11152
|
cache_ttl_secs: exports_external.number().int().min(0).optional().describe("Per-session recall cache TTL in seconds. When > 0, identical " + "(prompt, bank) within the same session reuse the cached recall " + "result instead of round-tripping to Hindsight. 0 disables. " + "Default is 600 (10 min) for switchroom-managed agents."),
|
|
@@ -11539,6 +11547,7 @@ var init_schema = __esm(() => {
|
|
|
11539
11547
|
isolation: exports_external.enum(["default", "strict"]).optional(),
|
|
11540
11548
|
directive_capture_nudge: exports_external.boolean().optional(),
|
|
11541
11549
|
observation_scopes: ObservationScopesSchema,
|
|
11550
|
+
observation_scope_strategy: ObservationScopeStrategySchema,
|
|
11542
11551
|
recall: exports_external.object({
|
|
11543
11552
|
max_memories: exports_external.number().int().min(0).optional(),
|
|
11544
11553
|
cache_ttl_secs: exports_external.number().int().min(0).optional(),
|
|
@@ -21001,6 +21010,7 @@ function parsePendingRolloutMarker(raw) {
|
|
|
21001
21010
|
if (o.allow_downgrade !== undefined && typeof o.allow_downgrade !== "boolean") {
|
|
21002
21011
|
return null;
|
|
21003
21012
|
}
|
|
21013
|
+
const narrationIdValid = typeof o.narration_message_id === "number" && Number.isInteger(o.narration_message_id) && o.narration_message_id > 0;
|
|
21004
21014
|
return {
|
|
21005
21015
|
v: 1,
|
|
21006
21016
|
request_id: o.request_id,
|
|
@@ -21008,6 +21018,7 @@ function parsePendingRolloutMarker(raw) {
|
|
|
21008
21018
|
...o.agents !== undefined ? { agents: o.agents } : {},
|
|
21009
21019
|
...o.skip_web !== undefined ? { skip_web: o.skip_web } : {},
|
|
21010
21020
|
...o.allow_downgrade !== undefined ? { allow_downgrade: o.allow_downgrade } : {},
|
|
21021
|
+
...narrationIdValid ? { narration_message_id: o.narration_message_id } : {},
|
|
21011
21022
|
caller: caller.kind === "agent" ? { kind: "agent", name: caller.name } : { kind: "operator" },
|
|
21012
21023
|
created_at: o.created_at,
|
|
21013
21024
|
prior_hostd_version: o.prior_hostd_version
|
|
@@ -21311,7 +21322,7 @@ function allocateAgentUid(name) {
|
|
|
21311
21322
|
}
|
|
21312
21323
|
|
|
21313
21324
|
// src/build-info.ts
|
|
21314
|
-
var VERSION = "0.19.
|
|
21325
|
+
var VERSION = "0.19.41";
|
|
21315
21326
|
|
|
21316
21327
|
// src/setup/hindsight-recall-tunables.ts
|
|
21317
21328
|
var RECALL_DEADLINE_HEADROOM_SECONDS = 2;
|
|
@@ -28405,6 +28416,10 @@ function parseRolloutPhaseLine(line) {
|
|
|
28405
28416
|
"agent-done",
|
|
28406
28417
|
"persist-pin",
|
|
28407
28418
|
"web-refresh",
|
|
28419
|
+
"web-refresh-done",
|
|
28420
|
+
"hindsight-refresh",
|
|
28421
|
+
"hindsight-refresh-done",
|
|
28422
|
+
"hindsight-skipped",
|
|
28408
28423
|
"hostd-web-deferred",
|
|
28409
28424
|
"self-bump",
|
|
28410
28425
|
"self-bump-done"
|
|
@@ -29385,6 +29400,14 @@ function phaseLine(s) {
|
|
|
29385
29400
|
return "persisting pin";
|
|
29386
29401
|
case "web-refresh":
|
|
29387
29402
|
return "refreshing web dashboard (webd install)";
|
|
29403
|
+
case "web-refresh-done":
|
|
29404
|
+
return "web dashboard refreshed";
|
|
29405
|
+
case "hindsight-refresh":
|
|
29406
|
+
return "refreshing hindsight (memory backend recreate)";
|
|
29407
|
+
case "hindsight-refresh-done":
|
|
29408
|
+
return "hindsight refreshed";
|
|
29409
|
+
case "hindsight-skipped":
|
|
29410
|
+
return "hindsight — no container on this host; skipped";
|
|
29388
29411
|
case "hostd-web-deferred":
|
|
29389
29412
|
return "hostd refresh deferred (run host-side)";
|
|
29390
29413
|
case "self-bump":
|
|
@@ -29452,6 +29475,33 @@ function checklistLines(s, compact) {
|
|
|
29452
29475
|
}
|
|
29453
29476
|
return lines;
|
|
29454
29477
|
}
|
|
29478
|
+
function singletonGlyph(status) {
|
|
29479
|
+
switch (status) {
|
|
29480
|
+
case "updated":
|
|
29481
|
+
return "✓";
|
|
29482
|
+
case "updating":
|
|
29483
|
+
return "⏳";
|
|
29484
|
+
case "deferred":
|
|
29485
|
+
return "⧗";
|
|
29486
|
+
case "skipped":
|
|
29487
|
+
return "○";
|
|
29488
|
+
case "failed":
|
|
29489
|
+
return "✗";
|
|
29490
|
+
default:
|
|
29491
|
+
return "·";
|
|
29492
|
+
}
|
|
29493
|
+
}
|
|
29494
|
+
function singletonLines(s) {
|
|
29495
|
+
const singletons = s.singletons ?? [];
|
|
29496
|
+
if (singletons.length === 0)
|
|
29497
|
+
return [];
|
|
29498
|
+
const lines = ["**Singletons / shared:**"];
|
|
29499
|
+
for (const g of singletons) {
|
|
29500
|
+
const detail = g.detail ? ` — ${g.detail}` : "";
|
|
29501
|
+
lines.push(`- ${singletonGlyph(g.status)} \`${g.name}\`${detail}`);
|
|
29502
|
+
}
|
|
29503
|
+
return lines;
|
|
29504
|
+
}
|
|
29455
29505
|
function etaLine(s) {
|
|
29456
29506
|
if (s.m === undefined)
|
|
29457
29507
|
return null;
|
|
@@ -29485,6 +29535,7 @@ function renderWith(s, compact) {
|
|
|
29485
29535
|
const rolled = s.rolled ?? [];
|
|
29486
29536
|
const rolledCount = rolled.length;
|
|
29487
29537
|
const checklist = checklistLines(s, compact);
|
|
29538
|
+
const singletons = singletonLines(s);
|
|
29488
29539
|
const elapsedMs = s.elapsedMs ?? (s.startedAtMs !== undefined && s.nowMs !== undefined ? s.nowMs - s.startedAtMs : undefined);
|
|
29489
29540
|
if (s.terminal === "completed") {
|
|
29490
29541
|
const parts2 = [headerLine(s, "✅")];
|
|
@@ -29495,6 +29546,8 @@ function renderWith(s, compact) {
|
|
|
29495
29546
|
parts2.push(summary);
|
|
29496
29547
|
if (checklist.length > 0)
|
|
29497
29548
|
parts2.push("", ...checklist);
|
|
29549
|
+
if (singletons.length > 0)
|
|
29550
|
+
parts2.push("", ...singletons);
|
|
29498
29551
|
if (s.deferred !== false)
|
|
29499
29552
|
parts2.push("", ...deferredLines(s.target));
|
|
29500
29553
|
return parts2.join(`
|
|
@@ -29512,6 +29565,8 @@ function renderWith(s, compact) {
|
|
|
29512
29565
|
parts2.push("", `Re-running the roll will NOT fix this — the agents are already on ` + `target. Finish the stale component(s), then \`switchroom update --check\`.`);
|
|
29513
29566
|
if (checklist.length > 0)
|
|
29514
29567
|
parts2.push("", ...checklist);
|
|
29568
|
+
if (singletons.length > 0)
|
|
29569
|
+
parts2.push("", ...singletons);
|
|
29515
29570
|
return parts2.join(`
|
|
29516
29571
|
`);
|
|
29517
29572
|
}
|
|
@@ -29525,12 +29580,16 @@ function renderWith(s, compact) {
|
|
|
29525
29580
|
parts2.push(summary);
|
|
29526
29581
|
if (checklist.length > 0)
|
|
29527
29582
|
parts2.push("", ...checklist);
|
|
29583
|
+
if (singletons.length > 0)
|
|
29584
|
+
parts2.push("", ...singletons);
|
|
29528
29585
|
return parts2.join(`
|
|
29529
29586
|
`);
|
|
29530
29587
|
}
|
|
29531
29588
|
const parts = [headerLine(s, "⏳"), `**Phase:** ${phaseLine(s)}`];
|
|
29532
29589
|
if (checklist.length > 0)
|
|
29533
29590
|
parts.push("", ...checklist);
|
|
29591
|
+
if (singletons.length > 0)
|
|
29592
|
+
parts.push("", ...singletons);
|
|
29534
29593
|
const footer = [];
|
|
29535
29594
|
if (s.m !== undefined)
|
|
29536
29595
|
footer.push(`${rolledCount}/${s.m} rolled`);
|
|
@@ -30729,6 +30788,27 @@ class HostdServer {
|
|
|
30729
30788
|
})
|
|
30730
30789
|
};
|
|
30731
30790
|
}
|
|
30791
|
+
persistNarrationMessageId(requestId, messageId) {
|
|
30792
|
+
try {
|
|
30793
|
+
if (!Number.isInteger(messageId) || messageId <= 0)
|
|
30794
|
+
return;
|
|
30795
|
+
const markerPath = join13(this.hostdDirPath(), SELF_BUMP_MARKER_FILENAME);
|
|
30796
|
+
if (!existsSync15(markerPath))
|
|
30797
|
+
return;
|
|
30798
|
+
const marker = parsePendingRolloutMarker(readFileSync11(markerPath, "utf8"));
|
|
30799
|
+
if (!marker)
|
|
30800
|
+
return;
|
|
30801
|
+
if (marker.request_id !== requestId)
|
|
30802
|
+
return;
|
|
30803
|
+
const updated = {
|
|
30804
|
+
...marker,
|
|
30805
|
+
narration_message_id: messageId
|
|
30806
|
+
};
|
|
30807
|
+
const tmp = `${markerPath}.tmp`;
|
|
30808
|
+
writeFileSync6(tmp, encodePendingRolloutMarker(updated), { mode: 384 });
|
|
30809
|
+
renameSync5(tmp, markerPath);
|
|
30810
|
+
} catch {}
|
|
30811
|
+
}
|
|
30732
30812
|
async resumePendingSelfBumpRollout() {
|
|
30733
30813
|
const markerPath = join13(this.hostdDirPath(), SELF_BUMP_MARKER_FILENAME);
|
|
30734
30814
|
if (!existsSync15(markerPath))
|
|
@@ -30797,6 +30877,9 @@ class HostdServer {
|
|
|
30797
30877
|
...marker.skip_web ? { skip_web: true } : {},
|
|
30798
30878
|
...marker.allow_downgrade ? { allow_downgrade: true } : {}
|
|
30799
30879
|
}, marker.request_id, caller, Date.now());
|
|
30880
|
+
if (marker.narration_message_id !== undefined && caller.kind === "agent") {
|
|
30881
|
+
this.rolloutNarrator?.seedPostedMessage?.(marker.request_id, caller.name, marker.narration_message_id);
|
|
30882
|
+
}
|
|
30800
30883
|
this.onRolloutPhase(entry, { phase: "self-bump-done", target: marker.pin });
|
|
30801
30884
|
}
|
|
30802
30885
|
async handleAgentStart(req, started) {
|
|
@@ -31446,6 +31529,8 @@ ${pinNote}`.trim();
|
|
|
31446
31529
|
const drifted = entry.drifted ?? [];
|
|
31447
31530
|
const notes = entry.failed_step === "verify-components" ? [
|
|
31448
31531
|
`Unattended auto-update roll to ${entry.pin ?? "?"} rolled ` + `${(entry.rolled ?? []).length} agent(s) but did NOT converge the ` + `whole host: ${drifted.join(", ") || "one or more components"} ` + `still behind. Re-running the roll will not fix this — run the ` + `per-component install named in the rollout warnings, then ` + `\`switchroom update --check\` to confirm.`
|
|
31532
|
+
] : entry.failed_step === "ensure-banks" ? [
|
|
31533
|
+
`Unattended auto-update roll to ${entry.pin ?? "?"} rolled ` + `${(entry.rolled ?? []).length} agent(s) but could NOT create the ` + `Hindsight bank for: ${drifted.join(", ") || "one or more agents"}. ` + `Re-running the roll will not fix this — restart the named agent(s) ` + `to re-run bank creation now that Hindsight is back up.`
|
|
31449
31534
|
] : [
|
|
31450
31535
|
`Unattended auto-update roll to ${entry.pin ?? "?"} FAILED at ` + `${entry.failed_step ?? "unknown step"}` + `${entry.failed_agent ? ` (agent ${entry.failed_agent})` : ""}. ` + `Unattended retries of this version are disabled; fix the cause, ` + `then roll manually (\`switchroom rollout --pin ${entry.pin ?? "vX.Y.Z"}\`).`
|
|
31451
31536
|
];
|
|
@@ -31473,6 +31558,10 @@ ${pinNote}`.trim();
|
|
|
31473
31558
|
notes.push(`No automatic rollback: the agent fleet reached ` + `${entry.pin ?? "the target"} and the pin is committed — the gap is ` + `${(entry.drifted ?? []).join(", ") || "a component"}, not the fleet. ` + `Rolling back would move working agents backwards; converge the ` + `named component(s) forward instead.`);
|
|
31474
31559
|
return notes;
|
|
31475
31560
|
}
|
|
31561
|
+
if (entry.failed_step === "ensure-banks") {
|
|
31562
|
+
notes.push(`No automatic rollback: the agent fleet reached ` + `${entry.pin ?? "the target"} and the pin is committed — the gap is a ` + `missing Hindsight bank for ${(entry.drifted ?? []).join(", ") || "an agent"}, ` + `not the fleet. Restart the named agent(s) to re-create the bank; do ` + `not roll back.`);
|
|
31563
|
+
return notes;
|
|
31564
|
+
}
|
|
31476
31565
|
const beforeAnyAgent = entry.failed_step === "apply" || entry.failed_step === "restart-agent" && rolledCount === 0;
|
|
31477
31566
|
if (!beforeAnyAgent) {
|
|
31478
31567
|
notes.push(rolledCount > 0 ? `No automatic rollback: ${rolledCount} agent(s) already confirmed on ` + `${entry.pin ?? "the target"} past a green canary ` + `(${(entry.rolled ?? []).join(", ")}). The fleet is mixed — ` + `finish the roll or roll back manually.` : `No automatic rollback: the roll stopped at ` + `${entry.failed_step ?? "an unknown step"} before any change ` + `landed — nothing to revert. Verify host-side if unsure.`);
|
|
@@ -32314,6 +32403,26 @@ class SocketRolloutNarrationRelay {
|
|
|
32314
32403
|
}
|
|
32315
32404
|
|
|
32316
32405
|
// src/host-control/rollout-narrator.ts
|
|
32406
|
+
var SINGLETON_WEB = "switchroom-web";
|
|
32407
|
+
var SINGLETON_HINDSIGHT = "hindsight";
|
|
32408
|
+
var SINGLETON_HOSTD = "hostd";
|
|
32409
|
+
var SINGLETON_SHARED = "shared services (approval-kernel · auth-broker · vault-broker · voice)";
|
|
32410
|
+
function initialSingletons() {
|
|
32411
|
+
return [
|
|
32412
|
+
{ name: SINGLETON_WEB, status: "pending" },
|
|
32413
|
+
{ name: SINGLETON_HINDSIGHT, status: "pending" },
|
|
32414
|
+
{
|
|
32415
|
+
name: SINGLETON_HOSTD,
|
|
32416
|
+
status: "deferred",
|
|
32417
|
+
detail: "host-side (agent-invoked roll cannot recreate its own hostd)"
|
|
32418
|
+
},
|
|
32419
|
+
{
|
|
32420
|
+
name: SINGLETON_SHARED,
|
|
32421
|
+
status: "pending",
|
|
32422
|
+
detail: "self-heal with the first agent restart"
|
|
32423
|
+
}
|
|
32424
|
+
];
|
|
32425
|
+
}
|
|
32317
32426
|
var DEFAULT_DEBOUNCE_MS = 1500;
|
|
32318
32427
|
var MAX_POST_ATTEMPTS = 2;
|
|
32319
32428
|
|
|
@@ -32366,6 +32475,68 @@ class LogTailRolloutNarrator {
|
|
|
32366
32475
|
break;
|
|
32367
32476
|
}
|
|
32368
32477
|
}
|
|
32478
|
+
static patchSingleton(st, name, patch) {
|
|
32479
|
+
const row = st.singletons.find((g) => g.name === name);
|
|
32480
|
+
if (row)
|
|
32481
|
+
Object.assign(row, patch);
|
|
32482
|
+
}
|
|
32483
|
+
trackSingletonProgress(st, phase) {
|
|
32484
|
+
const patch = LogTailRolloutNarrator.patchSingleton;
|
|
32485
|
+
switch (phase.phase) {
|
|
32486
|
+
case "canary-pass":
|
|
32487
|
+
case "agent-done": {
|
|
32488
|
+
const shared = st.singletons.find((g) => g.name === SINGLETON_SHARED);
|
|
32489
|
+
if (shared && shared.status === "pending") {
|
|
32490
|
+
shared.status = "updating";
|
|
32491
|
+
shared.detail = "recreated with the first agent restart — verified at roll end";
|
|
32492
|
+
}
|
|
32493
|
+
break;
|
|
32494
|
+
}
|
|
32495
|
+
case "web-refresh":
|
|
32496
|
+
patch(st, SINGLETON_WEB, { status: "updating", detail: "webd install…" });
|
|
32497
|
+
break;
|
|
32498
|
+
case "web-refresh-done":
|
|
32499
|
+
patch(st, SINGLETON_WEB, { status: "updated", detail: undefined });
|
|
32500
|
+
break;
|
|
32501
|
+
case "hindsight-refresh":
|
|
32502
|
+
patch(st, SINGLETON_HINDSIGHT, {
|
|
32503
|
+
status: "updating",
|
|
32504
|
+
detail: "memory setup --recreate…"
|
|
32505
|
+
});
|
|
32506
|
+
break;
|
|
32507
|
+
case "hindsight-refresh-done":
|
|
32508
|
+
patch(st, SINGLETON_HINDSIGHT, { status: "updated", detail: undefined });
|
|
32509
|
+
break;
|
|
32510
|
+
case "hindsight-skipped":
|
|
32511
|
+
patch(st, SINGLETON_HINDSIGHT, {
|
|
32512
|
+
status: "skipped",
|
|
32513
|
+
detail: "no hindsight container on this host"
|
|
32514
|
+
});
|
|
32515
|
+
break;
|
|
32516
|
+
case "self-bump":
|
|
32517
|
+
patch(st, SINGLETON_HOSTD, {
|
|
32518
|
+
status: "updating",
|
|
32519
|
+
detail: "self-bump to the target…"
|
|
32520
|
+
});
|
|
32521
|
+
break;
|
|
32522
|
+
case "self-bump-done":
|
|
32523
|
+
patch(st, SINGLETON_HOSTD, {
|
|
32524
|
+
status: "updated",
|
|
32525
|
+
detail: "self-bump; template regen stays host-side"
|
|
32526
|
+
});
|
|
32527
|
+
break;
|
|
32528
|
+
case "hostd-web-deferred": {
|
|
32529
|
+
const hostd = st.singletons.find((g) => g.name === SINGLETON_HOSTD);
|
|
32530
|
+
if (hostd && hostd.status !== "updated") {
|
|
32531
|
+
hostd.status = "deferred";
|
|
32532
|
+
hostd.detail = "host-side";
|
|
32533
|
+
}
|
|
32534
|
+
break;
|
|
32535
|
+
}
|
|
32536
|
+
default:
|
|
32537
|
+
break;
|
|
32538
|
+
}
|
|
32539
|
+
}
|
|
32369
32540
|
static seqFor(phase) {
|
|
32370
32541
|
switch (phase.phase) {
|
|
32371
32542
|
case "self-bump":
|
|
@@ -32385,7 +32556,12 @@ class LogTailRolloutNarrator {
|
|
|
32385
32556
|
case "agent-done":
|
|
32386
32557
|
return 2 * (phase.n ?? 1) + 1;
|
|
32387
32558
|
case "web-refresh":
|
|
32388
|
-
|
|
32559
|
+
case "web-refresh-done":
|
|
32560
|
+
return Number.MAX_SAFE_INTEGER - 4;
|
|
32561
|
+
case "hindsight-refresh":
|
|
32562
|
+
case "hindsight-refresh-done":
|
|
32563
|
+
case "hindsight-skipped":
|
|
32564
|
+
return Number.MAX_SAFE_INTEGER - 3;
|
|
32389
32565
|
case "hostd-web-deferred":
|
|
32390
32566
|
return Number.MAX_SAFE_INTEGER - 1;
|
|
32391
32567
|
default:
|
|
@@ -32402,6 +32578,7 @@ class LogTailRolloutNarrator {
|
|
|
32402
32578
|
return;
|
|
32403
32579
|
st.lastAppliedSeq = seq;
|
|
32404
32580
|
this.trackAgentProgress(st, phase);
|
|
32581
|
+
this.trackSingletonProgress(st, phase);
|
|
32405
32582
|
const doneNames = st.agents.filter((a) => a.status === "done").map((a) => a.name);
|
|
32406
32583
|
const rolledSoFar = entry.rolled && entry.rolled.length > 0 ? entry.rolled : doneNames;
|
|
32407
32584
|
st.render = {
|
|
@@ -32413,6 +32590,7 @@ class LogTailRolloutNarrator {
|
|
|
32413
32590
|
...phase.agent !== undefined ? { agent: phase.agent } : {},
|
|
32414
32591
|
rolled: rolledSoFar,
|
|
32415
32592
|
agents: st.agents,
|
|
32593
|
+
singletons: st.singletons,
|
|
32416
32594
|
requestId: entry.request_id,
|
|
32417
32595
|
...entry.prior_pin ? { fromVersion: entry.prior_pin } : {},
|
|
32418
32596
|
startedAtMs: entry.started_at
|
|
@@ -32435,6 +32613,7 @@ class LogTailRolloutNarrator {
|
|
|
32435
32613
|
a.status = "pending";
|
|
32436
32614
|
}
|
|
32437
32615
|
}
|
|
32616
|
+
this.reconcileSingletonsAtTerminal(st, entry);
|
|
32438
32617
|
st.render = {
|
|
32439
32618
|
target: entry.pin ?? st.render.target,
|
|
32440
32619
|
rolled: entry.rolled ?? st.render.rolled,
|
|
@@ -32444,6 +32623,7 @@ class LogTailRolloutNarrator {
|
|
|
32444
32623
|
...entry.got !== undefined ? { got: entry.got } : {},
|
|
32445
32624
|
...entry.drifted && entry.drifted.length > 0 ? { drifted: entry.drifted } : {},
|
|
32446
32625
|
...st.agents.length > 0 ? { agents: st.agents } : {},
|
|
32626
|
+
...st.singletons.length > 0 ? { singletons: st.singletons } : {},
|
|
32447
32627
|
...st.render.m !== undefined ? { m: st.render.m } : {},
|
|
32448
32628
|
requestId: entry.request_id,
|
|
32449
32629
|
...entry.prior_pin ?? st.render.fromVersion ? { fromVersion: entry.prior_pin ?? st.render.fromVersion } : {},
|
|
@@ -32461,27 +32641,106 @@ class LogTailRolloutNarrator {
|
|
|
32461
32641
|
this.opts.log?.(`onTerminal threw (non-fatal): ${e.message}`);
|
|
32462
32642
|
}
|
|
32463
32643
|
}
|
|
32644
|
+
reconcileSingletonsAtTerminal(st, entry) {
|
|
32645
|
+
const completed = entry.result === "completed";
|
|
32646
|
+
const drifted = new Set(entry.drifted ?? []);
|
|
32647
|
+
const driftedHits = (row) => {
|
|
32648
|
+
switch (row) {
|
|
32649
|
+
case SINGLETON_WEB:
|
|
32650
|
+
return [...drifted].filter((d) => d === "switchroom-web");
|
|
32651
|
+
case SINGLETON_HINDSIGHT:
|
|
32652
|
+
return [...drifted].filter((d) => d === "switchroom-hindsight");
|
|
32653
|
+
case SINGLETON_HOSTD:
|
|
32654
|
+
return [...drifted].filter((d) => d === "switchroom-hostd" || d === "switchroom-hindsight-autoheal");
|
|
32655
|
+
case SINGLETON_SHARED:
|
|
32656
|
+
return [...drifted].filter((d) => d.includes("broker") || d.includes("kernel") || d.includes("voice"));
|
|
32657
|
+
default:
|
|
32658
|
+
return [];
|
|
32659
|
+
}
|
|
32660
|
+
};
|
|
32661
|
+
for (const g of st.singletons) {
|
|
32662
|
+
const hits = driftedHits(g.name);
|
|
32663
|
+
if (hits.length > 0) {
|
|
32664
|
+
g.status = "failed";
|
|
32665
|
+
g.detail = `still behind (${hits.join(", ")}) — see the roll's warnings`;
|
|
32666
|
+
continue;
|
|
32667
|
+
}
|
|
32668
|
+
if (entry.failed_step === "refresh-hindsight" && g.name === SINGLETON_HINDSIGHT) {
|
|
32669
|
+
g.status = "failed";
|
|
32670
|
+
g.detail = "recreate FAILED — memory backend down; run `switchroom memory setup`";
|
|
32671
|
+
continue;
|
|
32672
|
+
}
|
|
32673
|
+
if (!completed) {
|
|
32674
|
+
if (g.status === "updating" && g.name !== SINGLETON_SHARED) {
|
|
32675
|
+
g.detail = "outcome unknown — the roll stopped; see warnings";
|
|
32676
|
+
}
|
|
32677
|
+
continue;
|
|
32678
|
+
}
|
|
32679
|
+
switch (g.name) {
|
|
32680
|
+
case SINGLETON_WEB:
|
|
32681
|
+
case SINGLETON_HINDSIGHT:
|
|
32682
|
+
if (g.status === "updated")
|
|
32683
|
+
break;
|
|
32684
|
+
if (g.status === "updating") {
|
|
32685
|
+
g.status = "updated";
|
|
32686
|
+
g.detail = "verified by the end-of-roll component check";
|
|
32687
|
+
} else if (g.status === "pending") {
|
|
32688
|
+
g.status = "skipped";
|
|
32689
|
+
g.detail = "no refresh observed in this roll — see warnings";
|
|
32690
|
+
}
|
|
32691
|
+
break;
|
|
32692
|
+
case SINGLETON_SHARED:
|
|
32693
|
+
g.status = "updated";
|
|
32694
|
+
g.detail = "self-healed with the first agent restart; end-of-roll check passed";
|
|
32695
|
+
break;
|
|
32696
|
+
default:
|
|
32697
|
+
break;
|
|
32698
|
+
}
|
|
32699
|
+
}
|
|
32700
|
+
}
|
|
32701
|
+
freshState(agentName, target) {
|
|
32702
|
+
return {
|
|
32703
|
+
agentName,
|
|
32704
|
+
lastAppliedSeq: -1,
|
|
32705
|
+
messageId: null,
|
|
32706
|
+
posting: false,
|
|
32707
|
+
postAttempts: 0,
|
|
32708
|
+
postFailed: false,
|
|
32709
|
+
render: { target },
|
|
32710
|
+
agents: [],
|
|
32711
|
+
singletons: initialSingletons(),
|
|
32712
|
+
frozen: false,
|
|
32713
|
+
timer: null,
|
|
32714
|
+
pendingEditAfterPost: false
|
|
32715
|
+
};
|
|
32716
|
+
}
|
|
32464
32717
|
ensureState(entry) {
|
|
32465
32718
|
let st = this.states.get(entry.request_id);
|
|
32466
32719
|
if (!st) {
|
|
32467
32720
|
const agentName = entry.caller.kind === "agent" ? entry.caller.name : "";
|
|
32468
|
-
st =
|
|
32469
|
-
agentName,
|
|
32470
|
-
lastAppliedSeq: -1,
|
|
32471
|
-
messageId: null,
|
|
32472
|
-
posting: false,
|
|
32473
|
-
postAttempts: 0,
|
|
32474
|
-
postFailed: false,
|
|
32475
|
-
render: { target: entry.pin ?? "" },
|
|
32476
|
-
agents: [],
|
|
32477
|
-
frozen: false,
|
|
32478
|
-
timer: null,
|
|
32479
|
-
pendingEditAfterPost: false
|
|
32480
|
-
};
|
|
32721
|
+
st = this.freshState(agentName, entry.pin ?? "");
|
|
32481
32722
|
this.states.set(entry.request_id, st);
|
|
32482
32723
|
}
|
|
32483
32724
|
return st;
|
|
32484
32725
|
}
|
|
32726
|
+
seedPostedMessage(requestId, agentName, messageId) {
|
|
32727
|
+
const existing = this.states.get(requestId);
|
|
32728
|
+
if (existing) {
|
|
32729
|
+
if (existing.messageId === null) {
|
|
32730
|
+
existing.messageId = messageId;
|
|
32731
|
+
existing.posting = false;
|
|
32732
|
+
existing.postFailed = false;
|
|
32733
|
+
if (existing.postAttempts < 1)
|
|
32734
|
+
existing.postAttempts = 1;
|
|
32735
|
+
existing.pendingEditAfterPost = false;
|
|
32736
|
+
}
|
|
32737
|
+
return;
|
|
32738
|
+
}
|
|
32739
|
+
const st = this.freshState(agentName, "");
|
|
32740
|
+
st.messageId = messageId;
|
|
32741
|
+
st.postAttempts = 1;
|
|
32742
|
+
this.states.set(requestId, st);
|
|
32743
|
+
}
|
|
32485
32744
|
scheduleRenderOrPost(requestId) {
|
|
32486
32745
|
const st = this.states.get(requestId);
|
|
32487
32746
|
if (!st)
|
|
@@ -32522,6 +32781,11 @@ class LogTailRolloutNarrator {
|
|
|
32522
32781
|
}
|
|
32523
32782
|
st.messageId = mid;
|
|
32524
32783
|
st.postFailed = false;
|
|
32784
|
+
try {
|
|
32785
|
+
this.opts.onMessageId?.(requestId, mid);
|
|
32786
|
+
} catch (e) {
|
|
32787
|
+
this.opts.log?.(`onMessageId sink threw (non-fatal): ${e.message}`);
|
|
32788
|
+
}
|
|
32525
32789
|
if (st.pendingEditAfterPost) {
|
|
32526
32790
|
st.pendingEditAfterPost = false;
|
|
32527
32791
|
this.flush(requestId, false);
|
|
@@ -33166,8 +33430,11 @@ async function main() {
|
|
|
33166
33430
|
resolveGatewaySocket,
|
|
33167
33431
|
log: (m) => process.stderr.write(`hostd: rollout-narration — ${m}
|
|
33168
33432
|
`)
|
|
33169
|
-
}), {
|
|
33170
|
-
|
|
33433
|
+
}), {
|
|
33434
|
+
log: (m) => process.stderr.write(`hostd: rollout-narration — ${m}
|
|
33435
|
+
`),
|
|
33436
|
+
onMessageId: (rid, mid) => server?.persistNarrationMessageId(rid, mid)
|
|
33437
|
+
});
|
|
33171
33438
|
const server = new HostdServer({
|
|
33172
33439
|
homeDir: homedir8(),
|
|
33173
33440
|
agentUids,
|
|
@@ -4417,7 +4417,7 @@ var init_protocol = __esm(() => {
|
|
|
4417
4417
|
});
|
|
4418
4418
|
|
|
4419
4419
|
// src/memory/observation-scopes.ts
|
|
4420
|
-
var OBSERVATION_SCOPES;
|
|
4420
|
+
var OBSERVATION_SCOPES, OBSERVATION_SCOPE_STRATEGIES;
|
|
4421
4421
|
var init_observation_scopes = __esm(() => {
|
|
4422
4422
|
OBSERVATION_SCOPES = [
|
|
4423
4423
|
"per_tag",
|
|
@@ -4425,10 +4425,16 @@ var init_observation_scopes = __esm(() => {
|
|
|
4425
4425
|
"all_combinations",
|
|
4426
4426
|
"shared"
|
|
4427
4427
|
];
|
|
4428
|
+
OBSERVATION_SCOPE_STRATEGIES = [
|
|
4429
|
+
"curated",
|
|
4430
|
+
"shared",
|
|
4431
|
+
"combined",
|
|
4432
|
+
"off"
|
|
4433
|
+
];
|
|
4428
4434
|
});
|
|
4429
4435
|
|
|
4430
4436
|
// src/config/schema.ts
|
|
4431
|
-
var CodeRepoEntrySchema, AgentBindMountSchema, HttpDiffPollSchema, PollSpecSchema, TelegramMessageActionSchema, WebhookActionSchema, ActionSpecSchema, ScheduleEntrySchema, AgentSoulSchema, AgentToolsSchema, ObservationScopesSchema, AgentMemorySchema, HookEntrySchema, AgentHooksSchema, SubagentSchema, SessionSchema, SessionContinuitySchema, webhookDispatchRule, TelegramChannelSchema, ChannelsSchema, TIMEZONE_REGEX, ApproverIdSchema, GoogleWorkspaceTierSchema, GoogleWorkspaceConfigSchema, LiteLLMConfigSchema, HindsightPerOpLlmSchema, HindsightConfigSchema, MicrosoftWorkspaceConfigSchema, NotionWorkspaceConfigSchema, AgentGoogleWorkspaceConfigSchema, MicrosoftAccountEmailSchema, MicrosoftToolTokenSchema, MicrosoftAccountBindingSchema, AgentMicrosoftWorkspaceConfigSchema, AgentNotionWorkspaceConfigSchema, ReactionsSchema, ReactionDispatchSchema, releaseBlockFields, ReleaseBlock, RootReleaseBlock, NetworkIsolationSchema, servesField, knowsField, profileFields, ProfileSchema, _omitExtends, defaultsFields, AgentDefaultsSchema, AgentSchema, TelegramConfigSchema, MemoryBackendConfigSchema, VaultConfigSchema, QuotaConfigSchema, AutoReleaseCheckSchema, HostControlConfigSchema, WebServiceConfigSchema, FleetHealthConfigSchema, HostdConfigSchema, CronEgressSchema, CronConfigSchema, UserSchema, SwitchroomConfigSchema;
|
|
4437
|
+
var CodeRepoEntrySchema, AgentBindMountSchema, HttpDiffPollSchema, PollSpecSchema, TelegramMessageActionSchema, WebhookActionSchema, ActionSpecSchema, ScheduleEntrySchema, AgentSoulSchema, AgentToolsSchema, ObservationScopesSchema, ObservationScopeStrategySchema, AgentMemorySchema, HookEntrySchema, AgentHooksSchema, SubagentSchema, SessionSchema, SessionContinuitySchema, webhookDispatchRule, TelegramChannelSchema, ChannelsSchema, TIMEZONE_REGEX, ApproverIdSchema, GoogleWorkspaceTierSchema, GoogleWorkspaceConfigSchema, LiteLLMConfigSchema, HindsightPerOpLlmSchema, HindsightConfigSchema, MicrosoftWorkspaceConfigSchema, NotionWorkspaceConfigSchema, AgentGoogleWorkspaceConfigSchema, MicrosoftAccountEmailSchema, MicrosoftToolTokenSchema, MicrosoftAccountBindingSchema, AgentMicrosoftWorkspaceConfigSchema, AgentNotionWorkspaceConfigSchema, ReactionsSchema, ReactionDispatchSchema, releaseBlockFields, ReleaseBlock, RootReleaseBlock, NetworkIsolationSchema, servesField, knowsField, profileFields, ProfileSchema, _omitExtends, defaultsFields, AgentDefaultsSchema, AgentSchema, TelegramConfigSchema, MemoryBackendConfigSchema, VaultConfigSchema, QuotaConfigSchema, AutoReleaseCheckSchema, HostControlConfigSchema, WebServiceConfigSchema, FleetHealthConfigSchema, HostdConfigSchema, CronEgressSchema, CronConfigSchema, UserSchema, SwitchroomConfigSchema;
|
|
4432
4438
|
var init_schema = __esm(() => {
|
|
4433
4439
|
init_zod();
|
|
4434
4440
|
init_observation_scopes();
|
|
@@ -4543,7 +4549,8 @@ var init_schema = __esm(() => {
|
|
|
4543
4549
|
allow: exports_external.array(exports_external.string()).default([]).describe("Allowed tools (use ['all'] for unrestricted)"),
|
|
4544
4550
|
deny: exports_external.array(exports_external.string()).default([]).describe("Denied tools (overrides allow)")
|
|
4545
4551
|
}).optional();
|
|
4546
|
-
ObservationScopesSchema = exports_external.enum(OBSERVATION_SCOPES).optional().describe("
|
|
4552
|
+
ObservationScopesSchema = exports_external.enum(OBSERVATION_SCOPES).optional().describe("Operator PIN for the per-row observation scope, overriding the " + 'default `curated` strategy (see docs/configuration.md). "shared" ' + "forces Hindsight's consolidation to write EVERY observation into ONE " + "global untagged scope — what several agents pooling one bank want so " + "their observations merge rather than sitting in parallel silos. " + '"combined" is the pin form of opting out (restores the pre-#4035 ' + "engine default). UNSET does NOT mean 'nothing on the wire': the " + "`curated` strategy still computes a scope per retain. Applies to " + "every retain path (Stop hook, " + "sidechain, boot reconcile, queue drain, backfill, session-handoff " + "mirror) and is carried on the queued payload, so a retain that fails " + "now and drains later still lands in this scope. Accepted values: " + `${OBSERVATION_SCOPES.join(", ")}. ` + "Cascade: override (per-agent wins over default).");
|
|
4553
|
+
ObservationScopeStrategySchema = exports_external.enum(OBSERVATION_SCOPE_STRATEGIES).optional().describe("Per-retain strategy that decides how the observation scope is " + 'computed when no `observation_scopes` pin is set. "curated" ' + "(the default since #4035) strips volatile per-session provenance " + "tags from each retain's consolidation scope — keeping the stable " + "semantic ones on the source fact — so an agent's observations dedup " + "and merge across sessions instead of pocketing one-per-session. " + '"shared" sends every retain to the one global untagged scope. ' + '"combined" / "off" opt OUT: no per-row scope goes on the wire, ' + "restoring the pre-feature engine default (byte-identical to an " + "unconfigured client). OMITTED BY DEFAULT: unset leaves the plugin's " + "own default (`curated`) in force. A manual `observation_scopes` pin " + "wins over this strategy. Accepted values: " + `${OBSERVATION_SCOPE_STRATEGIES.join(", ")}. ` + "Cascade: override (per-agent wins over default).");
|
|
4547
4554
|
AgentMemorySchema = exports_external.object({
|
|
4548
4555
|
collection: exports_external.string().describe("Hindsight collection name for this agent"),
|
|
4549
4556
|
auto_recall: exports_external.boolean().default(true).describe("Auto-search memories before each response"),
|
|
@@ -4579,6 +4586,7 @@ var init_schema = __esm(() => {
|
|
|
4579
4586
|
}).optional().describe("Personality traits (1-5 each) steering how this bank frames recall, " + "reflect, and observation synthesis — a coach leans empathy-high, a " + "lawyer/analyst leans skepticism/literalism-high. Maps to the engine's " + "flat `disposition_skepticism`/`_literalism`/`_empathy` fields. " + "Cascade: per-key merge (an agent overrides individual traits and " + "inherits the rest, matching `recall`)."),
|
|
4580
4587
|
directive_capture_nudge: exports_external.boolean().optional().describe("Deterministic directive-capture nudge (issue #2848 Stage B). When " + "on (switchroom default true — Stage A measured a ~55% miss rate on " + "durable corrections), the auto-recall hook regex-detects correction " + '/ standing-rule-shaped inbound ("always/never …", "from now on …", ' + `"stop doing …", a stated preference, "that's wrong, it's …") and ` + "appends a terse advisory to the turn's context telling the model to " + "persist the rule with mcp__hindsight__create_directive if it IS " + "durable. Detection is pure regex — the model does the judgment " + "in-session and calls create_directive itself (no model callsite, no " + "silent hook-side write). Set false to disable per-agent. " + "Cascade: override (per-agent wins over default)."),
|
|
4581
4588
|
observation_scopes: ObservationScopesSchema,
|
|
4589
|
+
observation_scope_strategy: ObservationScopeStrategySchema,
|
|
4582
4590
|
recall: exports_external.object({
|
|
4583
4591
|
max_memories: exports_external.number().int().min(0).optional().describe("Cap on the number of memories injected into the prompt by " + "auto-recall, regardless of token budget. Plugin default is 12. " + "0 disables the cap (all memories Hindsight returns are injected)."),
|
|
4584
4592
|
cache_ttl_secs: exports_external.number().int().min(0).optional().describe("Per-session recall cache TTL in seconds. When > 0, identical " + "(prompt, bank) within the same session reuse the cached recall " + "result instead of round-tripping to Hindsight. 0 disables. " + "Default is 600 (10 min) for switchroom-managed agents."),
|
|
@@ -4979,6 +4987,7 @@ var init_schema = __esm(() => {
|
|
|
4979
4987
|
isolation: exports_external.enum(["default", "strict"]).optional(),
|
|
4980
4988
|
directive_capture_nudge: exports_external.boolean().optional(),
|
|
4981
4989
|
observation_scopes: ObservationScopesSchema,
|
|
4990
|
+
observation_scope_strategy: ObservationScopeStrategySchema,
|
|
4982
4991
|
recall: exports_external.object({
|
|
4983
4992
|
max_memories: exports_external.number().int().min(0).optional(),
|
|
4984
4993
|
cache_ttl_secs: exports_external.number().int().min(0).optional(),
|