memorix 1.2.0 → 1.2.2
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/CHANGELOG.md +30 -1
- package/README.md +18 -4
- package/README.zh-CN.md +18 -4
- package/TEAM.md +86 -86
- package/dist/cli/index.js +15919 -14055
- package/dist/cli/index.js.map +1 -1
- package/dist/index.js +1997 -1021
- package/dist/index.js.map +1 -1
- package/dist/maintenance-runner.d.ts +1 -1
- package/dist/maintenance-runner.js +8481 -8005
- package/dist/maintenance-runner.js.map +1 -1
- package/dist/memcode-runtime/CHANGELOG.md +30 -1
- package/dist/sdk.d.ts +7 -2
- package/dist/sdk.js +2022 -1024
- package/dist/sdk.js.map +1 -1
- package/dist/types.d.ts +49 -1
- package/dist/types.js.map +1 -1
- package/docs/1.2.2-MEMORY-CONTROL-PLANE.md +434 -0
- package/docs/AGENT_OPERATOR_PLAYBOOK.md +4 -0
- package/docs/API_REFERENCE.md +27 -5
- package/docs/DESIGN_DECISIONS.md +357 -357
- package/docs/DEVELOPMENT.md +4 -0
- package/docs/README.md +1 -1
- package/docs/SETUP.md +7 -1
- package/docs/dev-log/progress.txt +91 -11
- package/docs/knowledge/workflows/memorix-release.md +57 -0
- package/package.json +1 -1
- package/plugins/codex/memorix/.codex-plugin/plugin.json +1 -1
- package/src/audit/index.ts +156 -156
- package/src/cli/command-guide.ts +192 -0
- package/src/cli/commands/audit-list.ts +89 -89
- package/src/cli/commands/audit.ts +9 -4
- package/src/cli/commands/background.ts +659 -659
- package/src/cli/commands/cleanup.ts +5 -1
- package/src/cli/commands/codegraph.ts +17 -8
- package/src/cli/commands/context.ts +3 -2
- package/src/cli/commands/doctor.ts +4 -2
- package/src/cli/commands/explain.ts +9 -3
- package/src/cli/commands/formation.ts +48 -48
- package/src/cli/commands/git-hook-install.ts +111 -111
- package/src/cli/commands/handoff.ts +75 -61
- package/src/cli/commands/hooks-status.ts +63 -63
- package/src/cli/commands/identity.ts +116 -0
- package/src/cli/commands/ingest-commit.ts +153 -153
- package/src/cli/commands/ingest-image.ts +71 -69
- package/src/cli/commands/ingest-log.ts +180 -180
- package/src/cli/commands/ingest.ts +44 -44
- package/src/cli/commands/integrate-shared.ts +15 -15
- package/src/cli/commands/knowledge.ts +40 -0
- package/src/cli/commands/lock.ts +93 -92
- package/src/cli/commands/memory.ts +58 -21
- package/src/cli/commands/message.ts +123 -118
- package/src/cli/commands/operator-shared.ts +98 -3
- package/src/cli/commands/poll.ts +74 -64
- package/src/cli/commands/purge-all-memory.ts +85 -85
- package/src/cli/commands/purge-project-memory.ts +83 -83
- package/src/cli/commands/reasoning.ts +135 -121
- package/src/cli/commands/retention.ts +9 -4
- package/src/cli/commands/serve-http.ts +22 -43
- package/src/cli/commands/serve-shared.ts +118 -118
- package/src/cli/commands/session.ts +29 -3
- package/src/cli/commands/setup.ts +9 -3
- package/src/cli/commands/skills.ts +124 -119
- package/src/cli/commands/status.ts +4 -3
- package/src/cli/commands/task.ts +193 -184
- package/src/cli/commands/team.ts +14 -10
- package/src/cli/commands/transfer.ts +108 -55
- package/src/cli/commands/uninstall-project-artifacts.ts +85 -85
- package/src/cli/identity.ts +89 -0
- package/src/cli/index.ts +96 -19
- package/src/cli/invocation.ts +115 -0
- package/src/cli/tui/ChatView.tsx +234 -234
- package/src/cli/tui/CommandBar.tsx +312 -312
- package/src/cli/tui/ContextRail.tsx +118 -118
- package/src/cli/tui/HeaderBar.tsx +72 -72
- package/src/cli/tui/LogoBanner.tsx +51 -51
- package/src/cli/tui/Sidebar.tsx +179 -179
- package/src/cli/tui/chat-service.ts +41 -18
- package/src/cli/tui/data.ts +23 -44
- package/src/cli/tui/index.ts +41 -41
- package/src/cli/tui/markdown-render.tsx +371 -371
- package/src/cli/tui/operator-context.ts +60 -0
- package/src/cli/tui/use-mouse.ts +157 -157
- package/src/cli/tui/useNavigation.ts +56 -56
- package/src/cli/tui/views/MemoryView.tsx +10 -8
- package/src/cli/update-checker.ts +211 -211
- package/src/cli/version.ts +7 -7
- package/src/cli/workbench.ts +1 -1
- package/src/codegraph/auto-context.ts +34 -17
- package/src/codegraph/context-pack.ts +1 -0
- package/src/codegraph/current-facts.ts +19 -1
- package/src/codegraph/project-context.ts +2 -0
- package/src/codegraph/task-lens.ts +49 -5
- package/src/compact/engine.ts +26 -10
- package/src/compact/index-format.ts +25 -2
- package/src/compact/token-budget.ts +74 -74
- package/src/dashboard/project-classification.ts +64 -64
- package/src/dashboard/server.ts +58 -52
- package/src/embedding/fastembed-provider.ts +142 -142
- package/src/embedding/transformers-provider.ts +111 -111
- package/src/git/extractor.ts +209 -209
- package/src/git/hooks-path.ts +85 -85
- package/src/hooks/admission.ts +117 -0
- package/src/hooks/handler.ts +98 -91
- package/src/hooks/pattern-detector.ts +173 -173
- package/src/hooks/significance-filter.ts +250 -250
- package/src/knowledge/claims.ts +51 -1
- package/src/knowledge/context-assembly.ts +97 -0
- package/src/knowledge/types.ts +1 -0
- package/src/knowledge/workflows.ts +34 -3
- package/src/knowledge/workset.ts +179 -10
- package/src/llm/memory-manager.ts +328 -328
- package/src/llm/provider.ts +885 -885
- package/src/llm/quality.ts +248 -248
- package/src/memory/admission.ts +57 -0
- package/src/memory/attribution-guard.ts +249 -249
- package/src/memory/auto-relations.ts +21 -0
- package/src/memory/consolidation.ts +13 -2
- package/src/memory/disclosure-policy.ts +140 -135
- package/src/memory/entity-extractor.ts +197 -197
- package/src/memory/export-import.ts +11 -3
- package/src/memory/formation/evaluate.ts +217 -217
- package/src/memory/formation/extract.ts +361 -361
- package/src/memory/formation/index.ts +417 -417
- package/src/memory/formation/resolve.ts +344 -344
- package/src/memory/formation/types.ts +315 -315
- package/src/memory/freshness.ts +122 -122
- package/src/memory/graph-context.ts +8 -2
- package/src/memory/graph-scope.ts +46 -0
- package/src/memory/graph.ts +197 -197
- package/src/memory/observations.ts +162 -4
- package/src/memory/quality-audit.ts +2 -0
- package/src/memory/refs.ts +94 -94
- package/src/memory/retention.ts +22 -2
- package/src/memory/secret-filter.ts +79 -79
- package/src/memory/session.ts +5 -2
- package/src/memory/visibility.ts +80 -0
- package/src/multimodal/image-loader.ts +143 -143
- package/src/orchestrate/adapters/claude-stream.ts +192 -192
- package/src/orchestrate/adapters/claude.ts +111 -111
- package/src/orchestrate/adapters/codex-stream.ts +134 -134
- package/src/orchestrate/adapters/codex.ts +41 -41
- package/src/orchestrate/adapters/gemini-stream.ts +166 -166
- package/src/orchestrate/adapters/gemini.ts +42 -42
- package/src/orchestrate/adapters/index.ts +73 -73
- package/src/orchestrate/adapters/opencode-stream.ts +143 -143
- package/src/orchestrate/adapters/opencode.ts +47 -47
- package/src/orchestrate/adapters/spawn-helper.ts +286 -286
- package/src/orchestrate/adapters/types.ts +77 -77
- package/src/orchestrate/capability-router.ts +284 -284
- package/src/orchestrate/context-compact.ts +188 -188
- package/src/orchestrate/cost-tracker.ts +219 -219
- package/src/orchestrate/error-recovery.ts +191 -191
- package/src/orchestrate/evidence.ts +140 -140
- package/src/orchestrate/ledger.ts +110 -110
- package/src/orchestrate/memorix-bridge.ts +378 -340
- package/src/orchestrate/output-budget.ts +80 -80
- package/src/orchestrate/permission.ts +152 -152
- package/src/orchestrate/pipeline-trace.ts +131 -131
- package/src/orchestrate/prompt-builder.ts +155 -155
- package/src/orchestrate/ring-buffer.ts +37 -37
- package/src/orchestrate/task-graph.ts +389 -389
- package/src/orchestrate/verify-gate.ts +33 -10
- package/src/orchestrate/worktree.ts +232 -232
- package/src/project/aliases.ts +374 -374
- package/src/project/detector.ts +268 -268
- package/src/rules/adapters/claude-code.ts +99 -99
- package/src/rules/adapters/codex.ts +97 -97
- package/src/rules/adapters/copilot.ts +124 -124
- package/src/rules/adapters/cursor.ts +114 -114
- package/src/rules/adapters/kiro.ts +126 -126
- package/src/rules/adapters/trae.ts +56 -56
- package/src/rules/adapters/windsurf.ts +83 -83
- package/src/rules/syncer.ts +235 -235
- package/src/runtime/control-plane-maintenance.ts +1 -0
- package/src/runtime/isolated-maintenance.ts +1 -0
- package/src/runtime/lifecycle.ts +18 -0
- package/src/runtime/maintenance-jobs.ts +1 -0
- package/src/runtime/maintenance-runner.ts +2 -0
- package/src/runtime/project-maintenance.ts +89 -0
- package/src/sdk.ts +334 -304
- package/src/search/intent-detector.ts +289 -289
- package/src/search/query-expansion.ts +52 -52
- package/src/server/formation-timeout.ts +27 -27
- package/src/server.ts +334 -93
- package/src/skills/mini-skills.ts +386 -386
- package/src/store/chat-store.ts +119 -119
- package/src/store/graph-store.ts +249 -249
- package/src/store/mini-skill-store.ts +349 -349
- package/src/store/orama-store.ts +61 -6
- package/src/store/persistence-json.ts +212 -212
- package/src/store/persistence.ts +291 -291
- package/src/store/project-affinity.ts +195 -195
- package/src/store/sqlite-db.ts +23 -1
- package/src/store/sqlite-store.ts +12 -2
- package/src/team/event-bus.ts +76 -76
- package/src/team/file-locks.ts +173 -173
- package/src/team/handoff.ts +168 -161
- package/src/team/messages.ts +203 -203
- package/src/team/poll.ts +132 -132
- package/src/team/tasks.ts +211 -211
- package/src/types.ts +51 -0
- package/src/wiki/generator.ts +2 -0
- package/src/workspace/mcp-adapters/codex.ts +191 -191
- package/src/workspace/mcp-adapters/copilot.ts +105 -105
- package/src/workspace/mcp-adapters/cursor.ts +53 -53
- package/src/workspace/mcp-adapters/kiro.ts +64 -64
- package/src/workspace/mcp-adapters/opencode.ts +123 -123
- package/src/workspace/mcp-adapters/trae.ts +134 -134
- package/src/workspace/mcp-adapters/windsurf.ts +91 -91
- package/src/workspace/sanitizer.ts +60 -60
- package/src/workspace/workflow-sync.ts +131 -131
package/src/team/handoff.ts
CHANGED
|
@@ -1,167 +1,174 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Team Handoff — Structured context transfer between agents.
|
|
3
|
-
*
|
|
4
|
-
* Phase 4b: Creates a handoff artifact as a standard observation
|
|
5
|
-
* (canonical path — no new JSON/file persistence).
|
|
6
|
-
*
|
|
7
|
-
* Architectural boundary (B4): The canonical record is an Observation
|
|
8
|
-
* stored via storeObservation(). TeamStore only holds a reference pointer.
|
|
9
|
-
* Handoff artifacts use valueCategory: 'core' so they are immune to
|
|
10
|
-
* retention archival (Phase 1 provenance: core = immune).
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
import type { TeamStore } from './team-store.js';
|
|
14
|
-
import type { ObservationType } from '../types.js';
|
|
15
|
-
|
|
16
|
-
// ── Types ──────────────────────────────────────────────────────────
|
|
17
|
-
|
|
18
|
-
export interface HandoffInput {
|
|
19
|
-
projectId: string;
|
|
20
|
-
fromAgentId: string;
|
|
21
|
-
toAgentId?: string; // null = anyone can pick up
|
|
22
|
-
taskId?: string; // link to specific task
|
|
23
|
-
summary: string; // human-readable summary of what was done
|
|
24
|
-
context: string; // machine-readable context for the next agent
|
|
25
|
-
filesModified?: string[]; // files touched during the work
|
|
26
|
-
concepts?: string[]; // key concepts for search discoverability
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export interface HandoffResult {
|
|
30
|
-
observationId: number;
|
|
31
|
-
taskId?: string;
|
|
32
|
-
fromAgentId: string;
|
|
33
|
-
toAgentId?: string;
|
|
34
|
-
summary: string;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// ── createHandoffArtifact ──────────────────────────────────────────
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Create a structured handoff artifact stored as an observation.
|
|
41
|
-
*
|
|
42
|
-
* @param input - Handoff details
|
|
43
|
-
* @param storeObservation - The canonical storeObservation function (injected to avoid circular deps)
|
|
44
|
-
* @param teamStore - For sending notification message to recipient
|
|
45
|
-
*/
|
|
46
|
-
export async function createHandoffArtifact(
|
|
47
|
-
input: HandoffInput,
|
|
48
|
-
storeObservation: (params: {
|
|
49
|
-
entityName: string;
|
|
50
|
-
type: ObservationType;
|
|
51
|
-
title: string;
|
|
52
|
-
narrative: string;
|
|
53
|
-
facts?: string[];
|
|
54
|
-
filesModified?: string[];
|
|
55
|
-
concepts?: string[];
|
|
56
|
-
projectId: string;
|
|
57
|
-
topicKey?: string;
|
|
1
|
+
/**
|
|
2
|
+
* Team Handoff — Structured context transfer between agents.
|
|
3
|
+
*
|
|
4
|
+
* Phase 4b: Creates a handoff artifact as a standard observation
|
|
5
|
+
* (canonical path — no new JSON/file persistence).
|
|
6
|
+
*
|
|
7
|
+
* Architectural boundary (B4): The canonical record is an Observation
|
|
8
|
+
* stored via storeObservation(). TeamStore only holds a reference pointer.
|
|
9
|
+
* Handoff artifacts use valueCategory: 'core' so they are immune to
|
|
10
|
+
* retention archival (Phase 1 provenance: core = immune).
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import type { TeamStore } from './team-store.js';
|
|
14
|
+
import type { ObservationType } from '../types.js';
|
|
15
|
+
|
|
16
|
+
// ── Types ──────────────────────────────────────────────────────────
|
|
17
|
+
|
|
18
|
+
export interface HandoffInput {
|
|
19
|
+
projectId: string;
|
|
20
|
+
fromAgentId: string;
|
|
21
|
+
toAgentId?: string; // null = anyone can pick up
|
|
22
|
+
taskId?: string; // link to specific task
|
|
23
|
+
summary: string; // human-readable summary of what was done
|
|
24
|
+
context: string; // machine-readable context for the next agent
|
|
25
|
+
filesModified?: string[]; // files touched during the work
|
|
26
|
+
concepts?: string[]; // key concepts for search discoverability
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface HandoffResult {
|
|
30
|
+
observationId: number;
|
|
31
|
+
taskId?: string;
|
|
32
|
+
fromAgentId: string;
|
|
33
|
+
toAgentId?: string;
|
|
34
|
+
summary: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// ── createHandoffArtifact ──────────────────────────────────────────
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Create a structured handoff artifact stored as an observation.
|
|
41
|
+
*
|
|
42
|
+
* @param input - Handoff details
|
|
43
|
+
* @param storeObservation - The canonical storeObservation function (injected to avoid circular deps)
|
|
44
|
+
* @param teamStore - For sending notification message to recipient
|
|
45
|
+
*/
|
|
46
|
+
export async function createHandoffArtifact(
|
|
47
|
+
input: HandoffInput,
|
|
48
|
+
storeObservation: (params: {
|
|
49
|
+
entityName: string;
|
|
50
|
+
type: ObservationType;
|
|
51
|
+
title: string;
|
|
52
|
+
narrative: string;
|
|
53
|
+
facts?: string[];
|
|
54
|
+
filesModified?: string[];
|
|
55
|
+
concepts?: string[];
|
|
56
|
+
projectId: string;
|
|
57
|
+
topicKey?: string;
|
|
58
58
|
sourceDetail?: 'explicit' | 'hook' | 'git-ingest';
|
|
59
59
|
valueCategory?: 'core' | 'contextual' | 'ephemeral';
|
|
60
|
+
visibility?: 'personal' | 'project' | 'team';
|
|
61
|
+
sharedWithAgentIds?: string[];
|
|
60
62
|
createdByAgentId?: string;
|
|
61
|
-
}) => Promise<{ observation: { id: number }; upserted: boolean }>,
|
|
62
|
-
teamStore: TeamStore,
|
|
63
|
-
): Promise<HandoffResult> {
|
|
64
|
-
// Build facts from handoff context
|
|
65
|
-
const facts: string[] = [
|
|
66
|
-
`Handoff from agent ${input.fromAgentId}`,
|
|
67
|
-
];
|
|
68
|
-
if (input.toAgentId) facts.push(`Intended recipient: ${input.toAgentId}`);
|
|
69
|
-
if (input.taskId) facts.push(`Related task: ${input.taskId}`);
|
|
70
|
-
facts.push(`Context: ${input.context}`);
|
|
71
|
-
|
|
72
|
-
// Store as observation — this IS the canonical record
|
|
73
|
-
const { observation } = await storeObservation({
|
|
74
|
-
entityName: 'team-handoff',
|
|
75
|
-
type: 'what-changed',
|
|
76
|
-
title: `[Handoff] ${input.summary}`,
|
|
77
|
-
narrative: input.context,
|
|
78
|
-
facts,
|
|
79
|
-
filesModified: input.filesModified,
|
|
80
|
-
concepts: ['handoff', ...(input.concepts ?? [])],
|
|
81
|
-
projectId: input.projectId,
|
|
82
|
-
topicKey: input.taskId ? `handoff:${input.taskId}:${input.fromAgentId}` : undefined,
|
|
63
|
+
}) => Promise<{ observation: { id: number }; upserted: boolean }>,
|
|
64
|
+
teamStore: TeamStore,
|
|
65
|
+
): Promise<HandoffResult> {
|
|
66
|
+
// Build facts from handoff context
|
|
67
|
+
const facts: string[] = [
|
|
68
|
+
`Handoff from agent ${input.fromAgentId}`,
|
|
69
|
+
];
|
|
70
|
+
if (input.toAgentId) facts.push(`Intended recipient: ${input.toAgentId}`);
|
|
71
|
+
if (input.taskId) facts.push(`Related task: ${input.taskId}`);
|
|
72
|
+
facts.push(`Context: ${input.context}`);
|
|
73
|
+
|
|
74
|
+
// Store as observation — this IS the canonical record
|
|
75
|
+
const { observation } = await storeObservation({
|
|
76
|
+
entityName: 'team-handoff',
|
|
77
|
+
type: 'what-changed',
|
|
78
|
+
title: `[Handoff] ${input.summary}`,
|
|
79
|
+
narrative: input.context,
|
|
80
|
+
facts,
|
|
81
|
+
filesModified: input.filesModified,
|
|
82
|
+
concepts: ['handoff', ...(input.concepts ?? [])],
|
|
83
|
+
projectId: input.projectId,
|
|
84
|
+
topicKey: input.taskId ? `handoff:${input.taskId}:${input.fromAgentId}` : undefined,
|
|
83
85
|
sourceDetail: 'explicit',
|
|
84
86
|
valueCategory: 'core', // immune to retention archival
|
|
87
|
+
// A direct handoff is not a project-wide memo. The sender retains access
|
|
88
|
+
// and the explicit recipient receives a read grant. Broadcast handoffs
|
|
89
|
+
// are deliberately visible only to active team members.
|
|
90
|
+
visibility: input.toAgentId ? 'personal' : 'team',
|
|
91
|
+
...(input.toAgentId ? { sharedWithAgentIds: [input.toAgentId] } : {}),
|
|
85
92
|
createdByAgentId: input.fromAgentId,
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
// Emit handoff:created event (best-effort, process-local only).
|
|
89
|
-
// Happens AFTER canonical write succeeds — listener errors never affect the handoff.
|
|
90
|
-
try {
|
|
91
|
-
teamStore.getEventBus()?.emit('handoff:created', {
|
|
92
|
-
observationId: observation.id,
|
|
93
|
-
projectId: input.projectId,
|
|
94
|
-
fromAgent: input.fromAgentId,
|
|
95
|
-
toAgent: input.toAgentId,
|
|
96
|
-
taskId: input.taskId,
|
|
97
|
-
});
|
|
98
|
-
} catch {
|
|
99
|
-
// Best-effort: event emission failure never affects canonical handoff
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// Send notification message to recipient.
|
|
103
|
-
// Broadcast handoff uses fanout-on-write (方案 A): instead of one NULL-recipient
|
|
104
|
-
// shared message, each active agent (excluding sender) gets their own copy.
|
|
105
|
-
// This prevents Agent A's markRead from consuming Agent B's unread state.
|
|
106
|
-
try {
|
|
107
|
-
if (input.toAgentId) {
|
|
108
|
-
// Role validation: if the handoff is linked to a task with required_role,
|
|
109
|
-
// verify the recipient's role matches before sending.
|
|
110
|
-
if (input.taskId) {
|
|
111
|
-
const task = teamStore.getTask(input.taskId);
|
|
112
|
-
if (task?.required_role) {
|
|
113
|
-
const recipient = teamStore.getAgent(input.toAgentId);
|
|
114
|
-
if (recipient && recipient.role !== task.required_role) {
|
|
115
|
-
// Log warning but don't block — handoff observation is still created
|
|
116
|
-
console.error(
|
|
117
|
-
`[memorix] Handoff role mismatch: task "${input.taskId}" requires role "${task.required_role}" but recipient "${input.toAgentId}" has role "${recipient.role}". Handoff proceeds but claim will be rejected.`
|
|
118
|
-
);
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
// Targeted handoff — single recipient-specific message
|
|
123
|
-
teamStore.sendMessage({
|
|
124
|
-
projectId: input.projectId,
|
|
125
|
-
senderAgentId: input.fromAgentId,
|
|
126
|
-
recipientAgentId: input.toAgentId,
|
|
127
|
-
type: 'handoff',
|
|
128
|
-
content: `Handoff: ${input.summary}`,
|
|
129
|
-
payload: {
|
|
130
|
-
observationId: observation.id,
|
|
131
|
-
taskId: input.taskId,
|
|
132
|
-
filesModified: input.filesModified,
|
|
133
|
-
},
|
|
134
|
-
taskId: input.taskId,
|
|
135
|
-
});
|
|
136
|
-
} else {
|
|
137
|
-
// Broadcast handoff — fanout to each active agent (excluding sender)
|
|
138
|
-
const agents = teamStore.listAgents(input.projectId, { status: 'active' });
|
|
139
|
-
for (const agent of agents) {
|
|
140
|
-
if (agent.agent_id === input.fromAgentId) continue;
|
|
141
|
-
teamStore.sendMessage({
|
|
142
|
-
projectId: input.projectId,
|
|
143
|
-
senderAgentId: input.fromAgentId,
|
|
144
|
-
recipientAgentId: agent.agent_id,
|
|
145
|
-
type: 'handoff',
|
|
146
|
-
content: `Handoff: ${input.summary}`,
|
|
147
|
-
payload: {
|
|
148
|
-
observationId: observation.id,
|
|
149
|
-
taskId: input.taskId,
|
|
150
|
-
filesModified: input.filesModified,
|
|
151
|
-
},
|
|
152
|
-
taskId: input.taskId,
|
|
153
|
-
});
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
} catch {
|
|
157
|
-
// Message sending is best-effort — handoff observation is the canonical record
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
return {
|
|
161
|
-
observationId: observation.id,
|
|
162
|
-
taskId: input.taskId,
|
|
163
|
-
fromAgentId: input.fromAgentId,
|
|
164
|
-
toAgentId: input.toAgentId,
|
|
165
|
-
summary: input.summary,
|
|
166
|
-
};
|
|
167
|
-
}
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// Emit handoff:created event (best-effort, process-local only).
|
|
96
|
+
// Happens AFTER canonical write succeeds — listener errors never affect the handoff.
|
|
97
|
+
try {
|
|
98
|
+
teamStore.getEventBus()?.emit('handoff:created', {
|
|
99
|
+
observationId: observation.id,
|
|
100
|
+
projectId: input.projectId,
|
|
101
|
+
fromAgent: input.fromAgentId,
|
|
102
|
+
toAgent: input.toAgentId,
|
|
103
|
+
taskId: input.taskId,
|
|
104
|
+
});
|
|
105
|
+
} catch {
|
|
106
|
+
// Best-effort: event emission failure never affects canonical handoff
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Send notification message to recipient.
|
|
110
|
+
// Broadcast handoff uses fanout-on-write (方案 A): instead of one NULL-recipient
|
|
111
|
+
// shared message, each active agent (excluding sender) gets their own copy.
|
|
112
|
+
// This prevents Agent A's markRead from consuming Agent B's unread state.
|
|
113
|
+
try {
|
|
114
|
+
if (input.toAgentId) {
|
|
115
|
+
// Role validation: if the handoff is linked to a task with required_role,
|
|
116
|
+
// verify the recipient's role matches before sending.
|
|
117
|
+
if (input.taskId) {
|
|
118
|
+
const task = teamStore.getTask(input.taskId);
|
|
119
|
+
if (task?.required_role) {
|
|
120
|
+
const recipient = teamStore.getAgent(input.toAgentId);
|
|
121
|
+
if (recipient && recipient.role !== task.required_role) {
|
|
122
|
+
// Log warning but don't block — handoff observation is still created
|
|
123
|
+
console.error(
|
|
124
|
+
`[memorix] Handoff role mismatch: task "${input.taskId}" requires role "${task.required_role}" but recipient "${input.toAgentId}" has role "${recipient.role}". Handoff proceeds but claim will be rejected.`
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
// Targeted handoff — single recipient-specific message
|
|
130
|
+
teamStore.sendMessage({
|
|
131
|
+
projectId: input.projectId,
|
|
132
|
+
senderAgentId: input.fromAgentId,
|
|
133
|
+
recipientAgentId: input.toAgentId,
|
|
134
|
+
type: 'handoff',
|
|
135
|
+
content: `Handoff: ${input.summary}`,
|
|
136
|
+
payload: {
|
|
137
|
+
observationId: observation.id,
|
|
138
|
+
taskId: input.taskId,
|
|
139
|
+
filesModified: input.filesModified,
|
|
140
|
+
},
|
|
141
|
+
taskId: input.taskId,
|
|
142
|
+
});
|
|
143
|
+
} else {
|
|
144
|
+
// Broadcast handoff — fanout to each active agent (excluding sender)
|
|
145
|
+
const agents = teamStore.listAgents(input.projectId, { status: 'active' });
|
|
146
|
+
for (const agent of agents) {
|
|
147
|
+
if (agent.agent_id === input.fromAgentId) continue;
|
|
148
|
+
teamStore.sendMessage({
|
|
149
|
+
projectId: input.projectId,
|
|
150
|
+
senderAgentId: input.fromAgentId,
|
|
151
|
+
recipientAgentId: agent.agent_id,
|
|
152
|
+
type: 'handoff',
|
|
153
|
+
content: `Handoff: ${input.summary}`,
|
|
154
|
+
payload: {
|
|
155
|
+
observationId: observation.id,
|
|
156
|
+
taskId: input.taskId,
|
|
157
|
+
filesModified: input.filesModified,
|
|
158
|
+
},
|
|
159
|
+
taskId: input.taskId,
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
} catch {
|
|
164
|
+
// Message sending is best-effort — handoff observation is the canonical record
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return {
|
|
168
|
+
observationId: observation.id,
|
|
169
|
+
taskId: input.taskId,
|
|
170
|
+
fromAgentId: input.fromAgentId,
|
|
171
|
+
toAgentId: input.toAgentId,
|
|
172
|
+
summary: input.summary,
|
|
173
|
+
};
|
|
174
|
+
}
|