chapterhouse 0.3.26 → 0.4.1
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/api/server.js +12 -0
- package/dist/api/server.test.js +39 -0
- package/dist/config.js +70 -0
- package/dist/config.test.js +109 -0
- package/dist/copilot/agents.js +32 -6
- package/dist/copilot/agents.test.js +41 -0
- package/dist/copilot/oneshot.js +54 -0
- package/dist/copilot/orchestrator.js +224 -3
- package/dist/copilot/orchestrator.test.js +380 -0
- package/dist/copilot/prompt-date.js +8 -0
- package/dist/copilot/system-message.js +8 -0
- package/dist/copilot/system-message.test.js +58 -0
- package/dist/copilot/tools.agent.test.js +24 -0
- package/dist/copilot/tools.js +351 -4
- package/dist/copilot/tools.memory.test.js +297 -0
- package/dist/copilot/turn-event-log-env.test.js +19 -0
- package/dist/copilot/turn-event-log.js +22 -23
- package/dist/copilot/turn-event-log.test.js +61 -2
- package/dist/memory/active-scope.js +69 -0
- package/dist/memory/active-scope.test.js +76 -0
- package/dist/memory/checkpoint-prompt.js +71 -0
- package/dist/memory/checkpoint.js +257 -0
- package/dist/memory/checkpoint.test.js +255 -0
- package/dist/memory/decisions.js +53 -0
- package/dist/memory/decisions.test.js +92 -0
- package/dist/memory/entities.js +59 -0
- package/dist/memory/entities.test.js +65 -0
- package/dist/memory/eot.js +219 -0
- package/dist/memory/eot.test.js +263 -0
- package/dist/memory/hot-tier.js +187 -0
- package/dist/memory/hot-tier.test.js +197 -0
- package/dist/memory/housekeeping.js +352 -0
- package/dist/memory/housekeeping.test.js +280 -0
- package/dist/memory/inbox.js +73 -0
- package/dist/memory/index.js +11 -0
- package/dist/memory/observations.js +46 -0
- package/dist/memory/observations.test.js +86 -0
- package/dist/memory/recall.js +210 -0
- package/dist/memory/recall.test.js +238 -0
- package/dist/memory/scopes.js +89 -0
- package/dist/memory/scopes.test.js +201 -0
- package/dist/memory/tiering.js +193 -0
- package/dist/memory/types.js +2 -0
- package/dist/paths.js +7 -1
- package/dist/store/db.js +412 -8
- package/dist/store/db.test.js +83 -0
- package/dist/test/setup-env.js +16 -0
- package/dist/test/setup-env.test.js +4 -0
- package/package.json +1 -1
- package/web/dist/assets/{index-BRPJa1DK.js → index-DmYLALt0.js} +70 -70
- package/web/dist/assets/index-DmYLALt0.js.map +1 -0
- package/web/dist/index.html +1 -1
- package/web/dist/assets/index-BRPJa1DK.js.map +0 -1
package/dist/copilot/tools.js
CHANGED
|
@@ -7,7 +7,7 @@ import { homedir } from "os";
|
|
|
7
7
|
import { listSkills, createSkill, removeSkill } from "./skills.js";
|
|
8
8
|
import { config, persistModel } from "../config.js";
|
|
9
9
|
import { agentEventBus } from "./agent-event-bus.js";
|
|
10
|
-
import { getCurrentSourceChannel, getCurrentActivityCallback, getCurrentActiveProjectRules, getCurrentAuthenticatedUser, getLastAuthenticatedUser, getCurrentAuthorizationHeader, getCurrentSessionKey, switchSessionModel, } from "./orchestrator.js";
|
|
10
|
+
import { getCurrentSourceChannel, getCurrentActivityCallback, getCurrentActiveProjectRules, getCurrentAuthenticatedUser, getLastAuthenticatedUser, getCurrentAuthorizationHeader, getCurrentSessionKey, invalidateOrchestratorSession, maybeScheduleScopeChangeCheckpoint, resetCheckpointSessionState, switchSessionModel, } from "./orchestrator.js";
|
|
11
11
|
import { getRouterConfig, updateRouterConfig } from "./router.js";
|
|
12
12
|
import { ensureWikiStructure, readPage, writePage, deletePage, writeRawSource, assertPagePath } from "../wiki/fs.js";
|
|
13
13
|
import { searchIndex, addToIndex, removeFromIndex, buildIndexEntryForPage } from "../wiki/index-manager.js";
|
|
@@ -20,7 +20,8 @@ import { loadTaxonomy } from "../wiki/taxonomy.js";
|
|
|
20
20
|
import { getCategoryDir, topicPagePath, slugify, entityCategories, FLAT_CATEGORIES } from "../wiki/topic-structure.js";
|
|
21
21
|
import { withWikiWrite } from "../wiki/lock.js";
|
|
22
22
|
import { readWikiPage, teamWikiSync } from "../wiki/team-sync.js";
|
|
23
|
-
import { getAgentRegistry, getAgent, createEphemeralAgentSession, getAgentSessionStatus, getTask, registerTask, completeTask, failTask, createAgentFile, removeAgentFile, loadAgents, } from "./agents.js";
|
|
23
|
+
import { getAgentRegistry, getAgent, createEphemeralAgentSession, getAgentSessionStatus, getTask, registerTask, completeTask, failTask, createTaskId, createAgentFile, removeAgentFile, loadAgents, } from "./agents.js";
|
|
24
|
+
import * as agentsModule from "./agents.js";
|
|
24
25
|
import { detectProjectRuleWarnings } from "./project-rule-warnings.js";
|
|
25
26
|
import { renderDelegatedProjectRulesPreamble } from "./project-rules-injection.js";
|
|
26
27
|
import { adoGetOkrs, adoOkrSummary, adoUpdateKr } from "../integrations/ado-skill.js";
|
|
@@ -28,6 +29,7 @@ import { TeamsNotifier } from "../integrations/teams-notify.js";
|
|
|
28
29
|
import { TeamPushClient } from "../integrations/team-push.js";
|
|
29
30
|
import { OKRMapper, parseOKRPageContent } from "./okr-mapper.js";
|
|
30
31
|
import { childLogger } from "../util/logger.js";
|
|
32
|
+
import { getActiveScope as getMemoryActiveScope, getScope as getMemoryScope, inferScopeFromText, demoteToCold, demoteToWarm, queueMemoryProposal, recall as recallMemory, recordDecision, recordObservation, runHousekeeping, setActiveScope as setMemoryActiveScope, promoteToHot, upsertEntity, } from "../memory/index.js";
|
|
31
33
|
const log = childLogger("tools");
|
|
32
34
|
/** Escape a string for safe inclusion as a single-line YAML scalar value. */
|
|
33
35
|
function yamlEscape(value) {
|
|
@@ -56,6 +58,81 @@ function isTimeoutError(err) {
|
|
|
56
58
|
function hasAdoPat() {
|
|
57
59
|
return (process.env.ADO_PAT?.trim() || config.adoPat).length > 0;
|
|
58
60
|
}
|
|
61
|
+
function requireOrchestratorMemoryWrite() {
|
|
62
|
+
const agentSlug = agentsModule.getCurrentToolAgentSlug?.();
|
|
63
|
+
if (agentSlug && agentSlug !== "chapterhouse") {
|
|
64
|
+
return "Memory writes are orchestrator-only. Use memory_propose instead.";
|
|
65
|
+
}
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
function resolveMemoryScopeForWrite(explicitScope, content) {
|
|
69
|
+
const explicit = explicitScope ? getMemoryScope(explicitScope) : undefined;
|
|
70
|
+
if (explicitScope && !explicit) {
|
|
71
|
+
throw new Error(`Unknown memory scope '${explicitScope}'.`);
|
|
72
|
+
}
|
|
73
|
+
if (explicit) {
|
|
74
|
+
return { scopeId: explicit.id, scopeSlug: explicit.slug };
|
|
75
|
+
}
|
|
76
|
+
const active = getMemoryActiveScope();
|
|
77
|
+
if (active) {
|
|
78
|
+
return { scopeId: active.id, scopeSlug: active.slug };
|
|
79
|
+
}
|
|
80
|
+
const inferred = inferScopeFromText(content);
|
|
81
|
+
if (inferred) {
|
|
82
|
+
const inferredScope = getMemoryScope(inferred.scope_id);
|
|
83
|
+
if (inferredScope) {
|
|
84
|
+
return { scopeId: inferredScope.id, scopeSlug: inferredScope.slug };
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
const validScopes = getDb().prepare(`
|
|
88
|
+
SELECT slug
|
|
89
|
+
FROM mem_scopes
|
|
90
|
+
WHERE active = 1
|
|
91
|
+
ORDER BY slug
|
|
92
|
+
`).all();
|
|
93
|
+
const activeScope = getMemoryActiveScope()?.slug ?? "none";
|
|
94
|
+
throw new Error(`No scope inferred. Active scope: ${activeScope}. Valid scopes: [${validScopes.map((row) => row.slug).join(", ")}]. `
|
|
95
|
+
+ "Set active scope with memory_set_scope or pass scope explicitly.");
|
|
96
|
+
}
|
|
97
|
+
const observationProposalPayloadSchema = z.object({
|
|
98
|
+
content: z.string(),
|
|
99
|
+
entity_id: z.number().int().positive().optional(),
|
|
100
|
+
source: z.string().optional(),
|
|
101
|
+
});
|
|
102
|
+
const decisionProposalPayloadSchema = z.object({
|
|
103
|
+
title: z.string(),
|
|
104
|
+
rationale: z.string().optional(),
|
|
105
|
+
decided_at: z.string().optional(),
|
|
106
|
+
});
|
|
107
|
+
const entityProposalPayloadSchema = z.object({
|
|
108
|
+
name: z.string(),
|
|
109
|
+
kind: z.string(),
|
|
110
|
+
summary: z.string().optional(),
|
|
111
|
+
});
|
|
112
|
+
const memoryProposeArgsSchema = z.object({
|
|
113
|
+
kind: z.enum(["observation", "decision", "entity"]),
|
|
114
|
+
scope_slug: z.string().optional(),
|
|
115
|
+
payload: z.record(z.string(), z.unknown()),
|
|
116
|
+
confidence: z.number().min(0).max(1).optional(),
|
|
117
|
+
reason: z.string().optional(),
|
|
118
|
+
}).superRefine((value, context) => {
|
|
119
|
+
const schema = value.kind === "observation"
|
|
120
|
+
? observationProposalPayloadSchema
|
|
121
|
+
: value.kind === "decision"
|
|
122
|
+
? decisionProposalPayloadSchema
|
|
123
|
+
: entityProposalPayloadSchema;
|
|
124
|
+
const parsed = schema.safeParse(value.payload);
|
|
125
|
+
if (!parsed.success) {
|
|
126
|
+
for (const issue of parsed.error.issues) {
|
|
127
|
+
context.addIssue({
|
|
128
|
+
code: z.ZodIssueCode.custom,
|
|
129
|
+
message: issue.message,
|
|
130
|
+
path: ["payload", ...issue.path],
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
const memoryTierTableSchema = z.enum(["observation", "decision", "entity"]);
|
|
59
136
|
function getCurrentQuarter(now = new Date()) {
|
|
60
137
|
return `${now.getUTCFullYear()}-Q${Math.floor(now.getUTCMonth() / 3) + 1}`;
|
|
61
138
|
}
|
|
@@ -110,16 +187,17 @@ export function createTools(deps) {
|
|
|
110
187
|
return `Agent '${args.agent_name}' not found. Available agents: ${available}`;
|
|
111
188
|
}
|
|
112
189
|
const delegatedSlug = agent.slug;
|
|
190
|
+
const taskId = createTaskId();
|
|
113
191
|
let session;
|
|
114
192
|
try {
|
|
115
193
|
const allTools = createTools(deps);
|
|
116
|
-
session = await createEphemeralAgentSession(agent.slug, deps.client, allTools, args.model_override);
|
|
194
|
+
session = await createEphemeralAgentSession(agent.slug, deps.client, allTools, args.model_override, undefined, taskId);
|
|
117
195
|
}
|
|
118
196
|
catch (err) {
|
|
119
197
|
const msg = err instanceof Error ? err.message : String(err);
|
|
120
198
|
return `Failed to create session for @${delegatedSlug}: ${msg}`;
|
|
121
199
|
}
|
|
122
|
-
const task = registerTask(delegatedSlug, args.summary, getCurrentSourceChannel());
|
|
200
|
+
const task = registerTask(delegatedSlug, args.summary, getCurrentSourceChannel(), taskId);
|
|
123
201
|
const activeProjectRules = getCurrentActiveProjectRules();
|
|
124
202
|
const warningLines = activeProjectRules
|
|
125
203
|
? detectProjectRuleWarnings(args.task, activeProjectRules.rules.hard)
|
|
@@ -689,6 +767,275 @@ export function createTools(deps) {
|
|
|
689
767
|
return `Auto-routing disabled. Using fixed model: ${config.copilotModel}`;
|
|
690
768
|
},
|
|
691
769
|
}),
|
|
770
|
+
defineTool("memory_remember", {
|
|
771
|
+
description: "Write scoped agent memory (observation or decision) into the SQLite memory store. " +
|
|
772
|
+
"Use this for the new agent-memory system, not the legacy wiki-backed remember tool.",
|
|
773
|
+
parameters: z.object({
|
|
774
|
+
content: z.string().describe("Observation content or decision rationale."),
|
|
775
|
+
scope: z.string().optional().describe("Optional memory scope slug. Defaults to active scope, then keyword inference."),
|
|
776
|
+
kind: z.enum(["observation", "decision"]).optional().describe("Memory entry kind. Defaults to observation."),
|
|
777
|
+
entity_name: z.string().optional().describe("Optional entity name to attach. Auto-upserts the entity if provided."),
|
|
778
|
+
entity_kind: z.string().optional().describe("Required when entity_name is provided."),
|
|
779
|
+
title: z.string().optional().describe("Required for decision entries."),
|
|
780
|
+
decided_at: z.string().optional().describe("Decision date. Defaults to today."),
|
|
781
|
+
tier: z.enum(["hot", "warm", "cold"]).optional().describe("Storage tier. Defaults to warm."),
|
|
782
|
+
}),
|
|
783
|
+
handler: async (args) => {
|
|
784
|
+
const denied = requireOrchestratorMemoryWrite();
|
|
785
|
+
if (denied)
|
|
786
|
+
return denied;
|
|
787
|
+
if (args.entity_name && !args.entity_kind) {
|
|
788
|
+
return "entity_kind is required when entity_name is provided.";
|
|
789
|
+
}
|
|
790
|
+
try {
|
|
791
|
+
const { scopeId, scopeSlug } = resolveMemoryScopeForWrite(args.scope, args.content);
|
|
792
|
+
const kind = args.kind ?? "observation";
|
|
793
|
+
const entity = args.entity_name
|
|
794
|
+
? upsertEntity({
|
|
795
|
+
scope_id: scopeId,
|
|
796
|
+
kind: args.entity_kind,
|
|
797
|
+
name: args.entity_name,
|
|
798
|
+
tier: args.tier ?? "warm",
|
|
799
|
+
})
|
|
800
|
+
: undefined;
|
|
801
|
+
if (kind === "decision") {
|
|
802
|
+
if (!args.title) {
|
|
803
|
+
return "title is required when kind='decision'.";
|
|
804
|
+
}
|
|
805
|
+
const decision = recordDecision({
|
|
806
|
+
scope_id: scopeId,
|
|
807
|
+
entity_id: entity?.id,
|
|
808
|
+
title: args.title,
|
|
809
|
+
rationale: args.content,
|
|
810
|
+
decided_at: args.decided_at ?? new Date().toISOString().slice(0, 10),
|
|
811
|
+
tier: args.tier ?? "warm",
|
|
812
|
+
});
|
|
813
|
+
return {
|
|
814
|
+
ok: true,
|
|
815
|
+
id: decision.id,
|
|
816
|
+
scope: scopeSlug,
|
|
817
|
+
kind,
|
|
818
|
+
entity_id: entity?.id,
|
|
819
|
+
};
|
|
820
|
+
}
|
|
821
|
+
const observation = recordObservation({
|
|
822
|
+
scope_id: scopeId,
|
|
823
|
+
entity_id: entity?.id,
|
|
824
|
+
content: args.content,
|
|
825
|
+
source: `agent:${agentsModule.getCurrentToolAgentSlug?.() ?? "chapterhouse"}`,
|
|
826
|
+
tier: args.tier ?? "warm",
|
|
827
|
+
});
|
|
828
|
+
return {
|
|
829
|
+
ok: true,
|
|
830
|
+
id: observation.id,
|
|
831
|
+
scope: scopeSlug,
|
|
832
|
+
kind,
|
|
833
|
+
entity_id: entity?.id,
|
|
834
|
+
};
|
|
835
|
+
}
|
|
836
|
+
catch (err) {
|
|
837
|
+
return err instanceof Error ? err.message : String(err);
|
|
838
|
+
}
|
|
839
|
+
},
|
|
840
|
+
}),
|
|
841
|
+
defineTool("memory_propose", {
|
|
842
|
+
description: "Queue a proposed scoped memory item for orchestrator review at end-of-task. " +
|
|
843
|
+
"Available to all agents; writes land in mem_inbox as pending proposals.",
|
|
844
|
+
parameters: memoryProposeArgsSchema,
|
|
845
|
+
handler: async (args) => {
|
|
846
|
+
try {
|
|
847
|
+
const parsedArgs = memoryProposeArgsSchema.parse(args);
|
|
848
|
+
const payload = parsedArgs.kind === "observation"
|
|
849
|
+
? observationProposalPayloadSchema.parse(parsedArgs.payload)
|
|
850
|
+
: parsedArgs.kind === "decision"
|
|
851
|
+
? decisionProposalPayloadSchema.parse(parsedArgs.payload)
|
|
852
|
+
: entityProposalPayloadSchema.parse(parsedArgs.payload);
|
|
853
|
+
const proposal = queueMemoryProposal({
|
|
854
|
+
kind: parsedArgs.kind,
|
|
855
|
+
scopeSlug: parsedArgs.scope_slug,
|
|
856
|
+
payload,
|
|
857
|
+
confidence: parsedArgs.confidence ?? 0.5,
|
|
858
|
+
reason: parsedArgs.reason,
|
|
859
|
+
sourceAgent: agentsModule.getCurrentToolAgentSlug?.() ?? "chapterhouse",
|
|
860
|
+
sourceTaskId: agentsModule.getCurrentToolTaskId?.(),
|
|
861
|
+
});
|
|
862
|
+
return {
|
|
863
|
+
proposal_id: proposal.id,
|
|
864
|
+
status: "queued",
|
|
865
|
+
};
|
|
866
|
+
}
|
|
867
|
+
catch (err) {
|
|
868
|
+
if (err instanceof z.ZodError) {
|
|
869
|
+
return err.issues.map((issue) => issue.message).join("; ");
|
|
870
|
+
}
|
|
871
|
+
return err instanceof Error ? err.message : String(err);
|
|
872
|
+
}
|
|
873
|
+
},
|
|
874
|
+
}),
|
|
875
|
+
defineTool("memory_recall", {
|
|
876
|
+
description: "Search scoped agent memory with FTS-backed recall. Use this for the new agent-memory store, not the wiki-backed recall tool.",
|
|
877
|
+
parameters: z.object({
|
|
878
|
+
query: z.string().describe("Query text to search for."),
|
|
879
|
+
scope: z.string().optional().describe("Optional scope slug. Defaults to the active scope when available."),
|
|
880
|
+
kinds: z.array(z.enum(["observation", "decision", "entity"])).optional()
|
|
881
|
+
.describe("Optional filter for memory entry kinds."),
|
|
882
|
+
limit: z.number().int().positive().optional().describe("Maximum number of ranked hits to return. Defaults to 10."),
|
|
883
|
+
includeSuperseded: z.boolean().optional().describe("Include superseded observations and decisions. Defaults to false."),
|
|
884
|
+
includeArchived: z.boolean().optional().describe("Include archived observations and decisions. Defaults to false."),
|
|
885
|
+
includeCold: z.boolean().optional().describe("Include cold-tier rows. Defaults to false."),
|
|
886
|
+
}),
|
|
887
|
+
handler: async (args) => {
|
|
888
|
+
const requestedScope = args.scope ? getMemoryScope(args.scope) : undefined;
|
|
889
|
+
if (args.scope && !requestedScope) {
|
|
890
|
+
return `Unknown memory scope '${args.scope}'.`;
|
|
891
|
+
}
|
|
892
|
+
const result = recallMemory({
|
|
893
|
+
query: args.query,
|
|
894
|
+
scope_id: requestedScope?.id,
|
|
895
|
+
kinds: args.kinds,
|
|
896
|
+
limit: args.limit ?? 10,
|
|
897
|
+
includeSuperseded: args.includeSuperseded,
|
|
898
|
+
includeArchived: args.includeArchived,
|
|
899
|
+
includeCold: args.includeCold,
|
|
900
|
+
});
|
|
901
|
+
return {
|
|
902
|
+
active_scope: result.activeScope
|
|
903
|
+
? { slug: result.activeScope.slug, title: result.activeScope.title }
|
|
904
|
+
: null,
|
|
905
|
+
hot_tier: result.hotTier,
|
|
906
|
+
hits: result.hits.map((hit) => ({
|
|
907
|
+
kind: hit.kind,
|
|
908
|
+
id: hit.id,
|
|
909
|
+
scope: hit.scope,
|
|
910
|
+
content: hit.content,
|
|
911
|
+
decided_at: hit.decidedAt,
|
|
912
|
+
score: hit.score,
|
|
913
|
+
snippet: hit.snippet,
|
|
914
|
+
})),
|
|
915
|
+
};
|
|
916
|
+
},
|
|
917
|
+
}),
|
|
918
|
+
defineTool("memory_housekeep", {
|
|
919
|
+
description: "Run the scoped agent-memory housekeeping pipeline. Orchestrator-only write-tier maintenance tool.",
|
|
920
|
+
parameters: z.object({
|
|
921
|
+
scope_slug: z.string().optional().describe("Optional scope slug. Defaults to the active scope."),
|
|
922
|
+
all_scopes: z.boolean().optional().describe("Run scoped passes for all active scopes instead of one scope."),
|
|
923
|
+
passes: z.array(z.string()).optional().describe("Optional pass names: dedup_observations, dedup_decisions, orphan_cleanup, decay, compact_inbox."),
|
|
924
|
+
}),
|
|
925
|
+
handler: async (args) => {
|
|
926
|
+
const denied = requireOrchestratorMemoryWrite();
|
|
927
|
+
if (denied)
|
|
928
|
+
return denied;
|
|
929
|
+
if (args.scope_slug && args.all_scopes) {
|
|
930
|
+
return "Pass either scope_slug or all_scopes, not both.";
|
|
931
|
+
}
|
|
932
|
+
try {
|
|
933
|
+
const requestedScope = args.scope_slug ? getMemoryScope(args.scope_slug) : undefined;
|
|
934
|
+
if (args.scope_slug && !requestedScope) {
|
|
935
|
+
return `Unknown memory scope '${args.scope_slug}'.`;
|
|
936
|
+
}
|
|
937
|
+
const result = runHousekeeping({
|
|
938
|
+
scopeIds: requestedScope ? [requestedScope.id] : undefined,
|
|
939
|
+
allScopes: args.all_scopes,
|
|
940
|
+
passes: args.passes,
|
|
941
|
+
});
|
|
942
|
+
return {
|
|
943
|
+
ok: result.summaries.every((summary) => summary.errors.length === 0),
|
|
944
|
+
scope_ids: result.scopeIds,
|
|
945
|
+
total_examined: result.totalExamined,
|
|
946
|
+
total_modified: result.totalModified,
|
|
947
|
+
duration_ms: result.durationMs,
|
|
948
|
+
summaries: result.summaries,
|
|
949
|
+
};
|
|
950
|
+
}
|
|
951
|
+
catch (err) {
|
|
952
|
+
return err instanceof Error ? err.message : String(err);
|
|
953
|
+
}
|
|
954
|
+
},
|
|
955
|
+
}),
|
|
956
|
+
defineTool("memory_promote", {
|
|
957
|
+
description: "Promote a memory row to the hot tier. Orchestrator-only manual override.",
|
|
958
|
+
parameters: z.object({
|
|
959
|
+
table: memoryTierTableSchema,
|
|
960
|
+
id: z.number().int().positive(),
|
|
961
|
+
reason: z.string().min(1),
|
|
962
|
+
}),
|
|
963
|
+
handler: async (args) => {
|
|
964
|
+
const denied = requireOrchestratorMemoryWrite();
|
|
965
|
+
if (denied)
|
|
966
|
+
return denied;
|
|
967
|
+
try {
|
|
968
|
+
promoteToHot(args.table, args.id, args.reason);
|
|
969
|
+
return { ok: true, table: args.table, id: args.id, tier: "hot" };
|
|
970
|
+
}
|
|
971
|
+
catch (err) {
|
|
972
|
+
return err instanceof Error ? err.message : String(err);
|
|
973
|
+
}
|
|
974
|
+
},
|
|
975
|
+
}),
|
|
976
|
+
defineTool("memory_demote", {
|
|
977
|
+
description: "Demote a memory row to warm or cold tier. Orchestrator-only manual override.",
|
|
978
|
+
parameters: z.object({
|
|
979
|
+
table: memoryTierTableSchema,
|
|
980
|
+
id: z.number().int().positive(),
|
|
981
|
+
reason: z.string().min(1),
|
|
982
|
+
tier: z.enum(["warm", "cold"]).optional().describe("Target demotion tier. Defaults to warm."),
|
|
983
|
+
}),
|
|
984
|
+
handler: async (args) => {
|
|
985
|
+
const denied = requireOrchestratorMemoryWrite();
|
|
986
|
+
if (denied)
|
|
987
|
+
return denied;
|
|
988
|
+
try {
|
|
989
|
+
if (args.tier === "cold") {
|
|
990
|
+
demoteToCold(args.table, args.id, args.reason);
|
|
991
|
+
return { ok: true, table: args.table, id: args.id, tier: "cold" };
|
|
992
|
+
}
|
|
993
|
+
demoteToWarm(args.table, args.id, args.reason);
|
|
994
|
+
return { ok: true, table: args.table, id: args.id, tier: "warm" };
|
|
995
|
+
}
|
|
996
|
+
catch (err) {
|
|
997
|
+
return err instanceof Error ? err.message : String(err);
|
|
998
|
+
}
|
|
999
|
+
},
|
|
1000
|
+
}),
|
|
1001
|
+
defineTool("memory_set_scope", {
|
|
1002
|
+
description: "Set or clear the active scope for the agent-memory system. This affects default routing for memory_remember and memory_recall.",
|
|
1003
|
+
parameters: z.object({
|
|
1004
|
+
slug: z.string().nullable().describe("Scope slug to activate, or null to clear the active scope."),
|
|
1005
|
+
}),
|
|
1006
|
+
handler: async (args) => {
|
|
1007
|
+
const denied = requireOrchestratorMemoryWrite();
|
|
1008
|
+
if (denied)
|
|
1009
|
+
return denied;
|
|
1010
|
+
try {
|
|
1011
|
+
const previousScope = getMemoryActiveScope();
|
|
1012
|
+
const nextScope = args.slug === null ? null : (getMemoryScope(args.slug) ?? null);
|
|
1013
|
+
if (args.slug !== null && !nextScope) {
|
|
1014
|
+
return `Unknown memory scope '${args.slug}'.`;
|
|
1015
|
+
}
|
|
1016
|
+
const previousSlug = previousScope?.slug ?? null;
|
|
1017
|
+
const nextSlug = nextScope?.slug ?? null;
|
|
1018
|
+
const didChange = previousSlug !== nextSlug;
|
|
1019
|
+
const sessionKey = getCurrentSessionKey();
|
|
1020
|
+
if (didChange) {
|
|
1021
|
+
maybeScheduleScopeChangeCheckpoint(sessionKey, previousScope, nextScope);
|
|
1022
|
+
}
|
|
1023
|
+
const activeScope = setMemoryActiveScope(args.slug);
|
|
1024
|
+
if (didChange) {
|
|
1025
|
+
invalidateOrchestratorSession(sessionKey);
|
|
1026
|
+
resetCheckpointSessionState(sessionKey);
|
|
1027
|
+
}
|
|
1028
|
+
return {
|
|
1029
|
+
active_scope: activeScope
|
|
1030
|
+
? { slug: activeScope.slug, title: activeScope.title }
|
|
1031
|
+
: null,
|
|
1032
|
+
};
|
|
1033
|
+
}
|
|
1034
|
+
catch (err) {
|
|
1035
|
+
return err instanceof Error ? err.message : String(err);
|
|
1036
|
+
}
|
|
1037
|
+
},
|
|
1038
|
+
}),
|
|
692
1039
|
// ----- Wiki-backed memory facades (preserve existing remember/recall/forget UX) -----
|
|
693
1040
|
defineTool("remember", {
|
|
694
1041
|
description: "Save a fact, preference, or detail to the wiki. Routes to topic pages automatically. " +
|