memorix 1.2.2 → 1.2.4

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.
Files changed (163) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/README.md +3 -3
  3. package/README.zh-CN.md +3 -3
  4. package/TEAM.md +86 -86
  5. package/dist/cli/index.js +5199 -4726
  6. package/dist/cli/index.js.map +1 -1
  7. package/dist/index.js +428 -49
  8. package/dist/index.js.map +1 -1
  9. package/dist/maintenance-runner.js +97 -18
  10. package/dist/maintenance-runner.js.map +1 -1
  11. package/dist/memcode-runtime/CHANGELOG.md +27 -0
  12. package/dist/sdk.js +428 -49
  13. package/dist/sdk.js.map +1 -1
  14. package/docs/1.2.4-PERSISTENT-MEMORY-DELIVERY.md +86 -0
  15. package/docs/AGENT_OPERATOR_PLAYBOOK.md +13 -1
  16. package/docs/API_REFERENCE.md +13 -3
  17. package/docs/DESIGN_DECISIONS.md +357 -357
  18. package/docs/dev-log/progress.txt +60 -9
  19. package/package.json +1 -1
  20. package/plugins/codex/memorix/.codex-plugin/plugin.json +1 -1
  21. package/src/audit/index.ts +156 -156
  22. package/src/cli/capability-map.ts +1 -1
  23. package/src/cli/command-guide.ts +4 -1
  24. package/src/cli/commands/agent-integrations.ts +5 -1
  25. package/src/cli/commands/audit-list.ts +89 -89
  26. package/src/cli/commands/background.ts +659 -659
  27. package/src/cli/commands/codegraph.ts +1 -1
  28. package/src/cli/commands/context.ts +9 -1
  29. package/src/cli/commands/formation.ts +48 -48
  30. package/src/cli/commands/git-hook-install.ts +111 -111
  31. package/src/cli/commands/handoff.ts +54 -54
  32. package/src/cli/commands/hooks-status.ts +63 -63
  33. package/src/cli/commands/ingest-commit.ts +153 -153
  34. package/src/cli/commands/ingest-image.ts +66 -66
  35. package/src/cli/commands/ingest-log.ts +180 -180
  36. package/src/cli/commands/ingest.ts +44 -44
  37. package/src/cli/commands/integrate-shared.ts +15 -15
  38. package/src/cli/commands/lock.ts +82 -82
  39. package/src/cli/commands/message.ts +104 -104
  40. package/src/cli/commands/poll.ts +58 -58
  41. package/src/cli/commands/purge-all-memory.ts +85 -85
  42. package/src/cli/commands/purge-project-memory.ts +83 -83
  43. package/src/cli/commands/reasoning.ts +118 -118
  44. package/src/cli/commands/resume.ts +31 -0
  45. package/src/cli/commands/serve-shared.ts +118 -118
  46. package/src/cli/commands/session.ts +15 -7
  47. package/src/cli/commands/skills.ts +114 -114
  48. package/src/cli/commands/task.ts +167 -167
  49. package/src/cli/commands/transfer.ts +47 -47
  50. package/src/cli/commands/uninstall-project-artifacts.ts +85 -85
  51. package/src/cli/index.ts +3 -1
  52. package/src/cli/tui/ChatView.tsx +234 -234
  53. package/src/cli/tui/CommandBar.tsx +312 -312
  54. package/src/cli/tui/ContextRail.tsx +118 -118
  55. package/src/cli/tui/HeaderBar.tsx +72 -72
  56. package/src/cli/tui/LogoBanner.tsx +51 -51
  57. package/src/cli/tui/Sidebar.tsx +179 -179
  58. package/src/cli/tui/index.ts +41 -41
  59. package/src/cli/tui/markdown-render.tsx +371 -371
  60. package/src/cli/tui/session-service.ts +3 -2
  61. package/src/cli/tui/use-mouse.ts +157 -157
  62. package/src/cli/tui/useNavigation.ts +56 -56
  63. package/src/cli/update-checker.ts +211 -211
  64. package/src/cli/version.ts +7 -7
  65. package/src/cli/workbench.ts +1 -1
  66. package/src/codegraph/auto-context.ts +54 -1
  67. package/src/codegraph/task-lens.ts +29 -0
  68. package/src/compact/token-budget.ts +89 -74
  69. package/src/config/toml-loader.ts +9 -5
  70. package/src/dashboard/project-classification.ts +64 -64
  71. package/src/embedding/fastembed-provider.ts +142 -142
  72. package/src/embedding/transformers-provider.ts +111 -111
  73. package/src/git/extractor.ts +209 -209
  74. package/src/git/hooks-path.ts +85 -85
  75. package/src/hooks/handler.ts +127 -66
  76. package/src/hooks/installers/index.ts +5 -4
  77. package/src/hooks/official-skills.ts +6 -4
  78. package/src/hooks/pattern-detector.ts +173 -173
  79. package/src/hooks/rules/memorix-agent-rules.md +9 -7
  80. package/src/hooks/significance-filter.ts +250 -250
  81. package/src/knowledge/context-assembly.ts +4 -1
  82. package/src/knowledge/workset.ts +89 -1
  83. package/src/llm/memory-manager.ts +328 -328
  84. package/src/llm/provider.ts +885 -885
  85. package/src/llm/quality.ts +248 -248
  86. package/src/memory/attribution-guard.ts +249 -249
  87. package/src/memory/disclosure-policy.ts +135 -135
  88. package/src/memory/entity-extractor.ts +197 -197
  89. package/src/memory/formation/evaluate.ts +217 -217
  90. package/src/memory/formation/extract.ts +361 -361
  91. package/src/memory/formation/index.ts +417 -417
  92. package/src/memory/formation/resolve.ts +344 -344
  93. package/src/memory/formation/types.ts +315 -315
  94. package/src/memory/freshness.ts +122 -122
  95. package/src/memory/graph.ts +197 -197
  96. package/src/memory/refs.ts +94 -94
  97. package/src/memory/secret-filter.ts +79 -79
  98. package/src/memory/session.ts +158 -9
  99. package/src/multimodal/image-loader.ts +143 -143
  100. package/src/orchestrate/adapters/claude-stream.ts +192 -192
  101. package/src/orchestrate/adapters/claude.ts +111 -111
  102. package/src/orchestrate/adapters/codex-stream.ts +134 -134
  103. package/src/orchestrate/adapters/codex.ts +41 -41
  104. package/src/orchestrate/adapters/gemini-stream.ts +166 -166
  105. package/src/orchestrate/adapters/gemini.ts +42 -42
  106. package/src/orchestrate/adapters/index.ts +73 -73
  107. package/src/orchestrate/adapters/opencode-stream.ts +143 -143
  108. package/src/orchestrate/adapters/opencode.ts +47 -47
  109. package/src/orchestrate/adapters/spawn-helper.ts +286 -286
  110. package/src/orchestrate/adapters/types.ts +77 -77
  111. package/src/orchestrate/capability-router.ts +284 -284
  112. package/src/orchestrate/context-compact.ts +188 -188
  113. package/src/orchestrate/cost-tracker.ts +219 -219
  114. package/src/orchestrate/error-recovery.ts +191 -191
  115. package/src/orchestrate/evidence.ts +140 -140
  116. package/src/orchestrate/ledger.ts +110 -110
  117. package/src/orchestrate/memorix-bridge.ts +343 -343
  118. package/src/orchestrate/output-budget.ts +80 -80
  119. package/src/orchestrate/permission.ts +152 -152
  120. package/src/orchestrate/pipeline-trace.ts +131 -131
  121. package/src/orchestrate/prompt-builder.ts +155 -155
  122. package/src/orchestrate/ring-buffer.ts +37 -37
  123. package/src/orchestrate/task-graph.ts +389 -389
  124. package/src/orchestrate/worktree.ts +232 -232
  125. package/src/project/aliases.ts +374 -374
  126. package/src/project/detector.ts +268 -268
  127. package/src/rules/adapters/claude-code.ts +99 -99
  128. package/src/rules/adapters/codex.ts +97 -97
  129. package/src/rules/adapters/copilot.ts +124 -124
  130. package/src/rules/adapters/cursor.ts +114 -114
  131. package/src/rules/adapters/kiro.ts +126 -126
  132. package/src/rules/adapters/trae.ts +56 -56
  133. package/src/rules/adapters/windsurf.ts +83 -83
  134. package/src/rules/syncer.ts +235 -235
  135. package/src/sdk.ts +299 -299
  136. package/src/search/intent-detector.ts +289 -289
  137. package/src/search/query-expansion.ts +52 -52
  138. package/src/server/formation-timeout.ts +27 -27
  139. package/src/server.ts +144 -10
  140. package/src/skills/mini-skills.ts +386 -386
  141. package/src/store/bun-sqlite-compat.ts +118 -15
  142. package/src/store/chat-store.ts +119 -119
  143. package/src/store/graph-store.ts +249 -249
  144. package/src/store/mini-skill-store.ts +349 -349
  145. package/src/store/persistence-json.ts +212 -212
  146. package/src/store/persistence.ts +291 -291
  147. package/src/store/project-affinity.ts +195 -195
  148. package/src/store/sqlite-db.ts +3 -3
  149. package/src/team/event-bus.ts +76 -76
  150. package/src/team/file-locks.ts +173 -173
  151. package/src/team/handoff.ts +161 -161
  152. package/src/team/messages.ts +203 -203
  153. package/src/team/poll.ts +132 -132
  154. package/src/team/tasks.ts +211 -211
  155. package/src/workspace/mcp-adapters/codex.ts +191 -191
  156. package/src/workspace/mcp-adapters/copilot.ts +105 -105
  157. package/src/workspace/mcp-adapters/cursor.ts +53 -53
  158. package/src/workspace/mcp-adapters/kiro.ts +64 -64
  159. package/src/workspace/mcp-adapters/opencode.ts +123 -123
  160. package/src/workspace/mcp-adapters/trae.ts +134 -134
  161. package/src/workspace/mcp-adapters/windsurf.ts +91 -91
  162. package/src/workspace/sanitizer.ts +60 -60
  163. package/src/workspace/workflow-sync.ts +131 -131
@@ -1,94 +1,94 @@
1
- /**
2
- * Typed Memory Reference Protocol (Phase 3a)
3
- *
4
- * Provides a formal, unambiguous way to reference memory objects
5
- * (observations and mini-skills) across internal code and the MCP API.
6
- *
7
- * String format:
8
- * obs:42 — observation #42
9
- * skill:3 — mini-skill #3
10
- * obs:42@org/proj — observation #42 in project org/proj
11
- *
12
- * Legacy support:
13
- * 42 (bare number) → obs:42
14
- * "42" (bare string) → obs:42
15
- *
16
- * Display short forms (presentation only):
17
- * #42 — observation
18
- * S3 — mini-skill
19
- */
20
-
21
- import type { MemoryRef } from '../types.js';
22
-
23
- // ── Parsing ──────────────────────────────────────────────────────
24
-
25
- const TYPED_REF_RE = /^(obs|skill):(\d+)(?:@(.+))?$/;
26
-
27
- /**
28
- * Parse a typed memory reference from a string or number.
29
- *
30
- * Accepts:
31
- * - "obs:42", "skill:3", "obs:42@org/proj"
32
- * - 42 (bare number → obs:42)
33
- * - "42" (bare numeric string → obs:42)
34
- *
35
- * Throws on invalid input.
36
- */
37
- export function parseMemoryRef(input: string | number): MemoryRef {
38
- // Bare number → legacy observation ref
39
- if (typeof input === 'number') {
40
- if (!Number.isInteger(input) || input < 0) {
41
- throw new Error(`Invalid memory ref: ${input} (must be a non-negative integer)`);
42
- }
43
- return { kind: 'obs', id: input };
44
- }
45
-
46
- const trimmed = input.trim();
47
-
48
- // Bare numeric string → legacy observation ref
49
- if (/^\d+$/.test(trimmed)) {
50
- return { kind: 'obs', id: parseInt(trimmed, 10) };
51
- }
52
-
53
- // Typed ref: obs:42 or skill:3 or obs:42@org/proj
54
- const match = trimmed.match(TYPED_REF_RE);
55
- if (!match) {
56
- throw new Error(
57
- `Invalid memory ref: "${input}". Expected format: obs:<id>, skill:<id>, obs:<id>@<projectId>, or a bare number.`,
58
- );
59
- }
60
-
61
- const kind = match[1] as 'obs' | 'skill';
62
- const id = parseInt(match[2], 10);
63
- const projectId = match[3] || undefined;
64
-
65
- return { kind, id, projectId };
66
- }
67
-
68
- // ── Serialization ────────────────────────────────────────────────
69
-
70
- /**
71
- * Serialize a MemoryRef to its canonical string form.
72
- *
73
- * Examples:
74
- * { kind: 'obs', id: 42 } → "obs:42"
75
- * { kind: 'skill', id: 3 } → "skill:3"
76
- * { kind: 'obs', id: 42, projectId: 'o/p' } → "obs:42@o/p"
77
- */
78
- export function serializeMemoryRef(ref: MemoryRef): string {
79
- const base = `${ref.kind}:${ref.id}`;
80
- return ref.projectId ? `${base}@${ref.projectId}` : base;
81
- }
82
-
83
- // ── Display ──────────────────────────────────────────────────────
84
-
85
- /**
86
- * Format a MemoryRef for human-readable display.
87
- *
88
- * Short forms:
89
- * obs:42 → "#42"
90
- * skill:3 → "S3"
91
- */
92
- export function displayRef(ref: MemoryRef): string {
93
- return ref.kind === 'obs' ? `#${ref.id}` : `S${ref.id}`;
94
- }
1
+ /**
2
+ * Typed Memory Reference Protocol (Phase 3a)
3
+ *
4
+ * Provides a formal, unambiguous way to reference memory objects
5
+ * (observations and mini-skills) across internal code and the MCP API.
6
+ *
7
+ * String format:
8
+ * obs:42 — observation #42
9
+ * skill:3 — mini-skill #3
10
+ * obs:42@org/proj — observation #42 in project org/proj
11
+ *
12
+ * Legacy support:
13
+ * 42 (bare number) → obs:42
14
+ * "42" (bare string) → obs:42
15
+ *
16
+ * Display short forms (presentation only):
17
+ * #42 — observation
18
+ * S3 — mini-skill
19
+ */
20
+
21
+ import type { MemoryRef } from '../types.js';
22
+
23
+ // ── Parsing ──────────────────────────────────────────────────────
24
+
25
+ const TYPED_REF_RE = /^(obs|skill):(\d+)(?:@(.+))?$/;
26
+
27
+ /**
28
+ * Parse a typed memory reference from a string or number.
29
+ *
30
+ * Accepts:
31
+ * - "obs:42", "skill:3", "obs:42@org/proj"
32
+ * - 42 (bare number → obs:42)
33
+ * - "42" (bare numeric string → obs:42)
34
+ *
35
+ * Throws on invalid input.
36
+ */
37
+ export function parseMemoryRef(input: string | number): MemoryRef {
38
+ // Bare number → legacy observation ref
39
+ if (typeof input === 'number') {
40
+ if (!Number.isInteger(input) || input < 0) {
41
+ throw new Error(`Invalid memory ref: ${input} (must be a non-negative integer)`);
42
+ }
43
+ return { kind: 'obs', id: input };
44
+ }
45
+
46
+ const trimmed = input.trim();
47
+
48
+ // Bare numeric string → legacy observation ref
49
+ if (/^\d+$/.test(trimmed)) {
50
+ return { kind: 'obs', id: parseInt(trimmed, 10) };
51
+ }
52
+
53
+ // Typed ref: obs:42 or skill:3 or obs:42@org/proj
54
+ const match = trimmed.match(TYPED_REF_RE);
55
+ if (!match) {
56
+ throw new Error(
57
+ `Invalid memory ref: "${input}". Expected format: obs:<id>, skill:<id>, obs:<id>@<projectId>, or a bare number.`,
58
+ );
59
+ }
60
+
61
+ const kind = match[1] as 'obs' | 'skill';
62
+ const id = parseInt(match[2], 10);
63
+ const projectId = match[3] || undefined;
64
+
65
+ return { kind, id, projectId };
66
+ }
67
+
68
+ // ── Serialization ────────────────────────────────────────────────
69
+
70
+ /**
71
+ * Serialize a MemoryRef to its canonical string form.
72
+ *
73
+ * Examples:
74
+ * { kind: 'obs', id: 42 } → "obs:42"
75
+ * { kind: 'skill', id: 3 } → "skill:3"
76
+ * { kind: 'obs', id: 42, projectId: 'o/p' } → "obs:42@o/p"
77
+ */
78
+ export function serializeMemoryRef(ref: MemoryRef): string {
79
+ const base = `${ref.kind}:${ref.id}`;
80
+ return ref.projectId ? `${base}@${ref.projectId}` : base;
81
+ }
82
+
83
+ // ── Display ──────────────────────────────────────────────────────
84
+
85
+ /**
86
+ * Format a MemoryRef for human-readable display.
87
+ *
88
+ * Short forms:
89
+ * obs:42 → "#42"
90
+ * skill:3 → "S3"
91
+ */
92
+ export function displayRef(ref: MemoryRef): string {
93
+ return ref.kind === 'obs' ? `#${ref.id}` : `S${ref.id}`;
94
+ }
@@ -1,79 +1,79 @@
1
- /**
2
- * Secret Filter
3
- *
4
- * Conservative credential detection and redaction for Memorix memory content.
5
- * Designed for low false-positive risk: only matches explicit credential
6
- * assignments (key=value / key: value), not generic discussion of auth concepts.
7
- *
8
- * sanitizeCredentials() — store-time: called inside storeObservation() before write
9
- * redactCredentials() — retrieval-time: called in format/output paths for legacy safety
10
- * containsCredential() — predicate used for testing and optional logging
11
- *
12
- * Both sanitize and redact share the same pattern logic. They are kept as
13
- * separate named exports so call-site semantics remain clear.
14
- */
15
-
16
- /**
17
- * Replaces the VALUE portion of matched credential patterns with [REDACTED].
18
- * The credential key name is preserved so context is not lost.
19
- *
20
- * Pattern categories:
21
- * 1. key=value / key: value forms — password, token, api_key, secret, etc.
22
- * Requires a real value (6+ non-whitespace chars) to avoid matching
23
- * discussion text like "password must be 8 chars".
24
- * 2. Bearer authorization header tokens (20+ chars)
25
- * 3. Structured token prefixes: GitHub (ghp_/ghs_/gho_), OpenAI (sk-), JWT (eyJ)
26
- */
27
- function applyRedaction(text: string): string {
28
- if (!text) return text;
29
- let out = text;
30
-
31
- // key=value / key: "value" / key='value' — preserve key, redact value
32
- out = out.replace(
33
- /((?:password|passwd|pwd|secret|token|api[_-]?key|auth[_-]?key|access[_-]?key|private[_-]?key|client[_-]?secret)\s*[:=]\s*["']?)([^\s"',;\n]{6,})/gi,
34
- '$1[REDACTED]',
35
- );
36
-
37
- // Bearer <token> — preserve "Bearer ", redact token
38
- out = out.replace(
39
- /(Bearer\s+)([A-Za-z0-9._\-/+]{20,})/g,
40
- '$1[REDACTED]',
41
- );
42
-
43
- // GitHub tokens (ghp_, ghs_, gho_, github_pat_)
44
- out = out.replace(/\b(?:ghp|ghs|gho|github_pat)_[A-Za-z0-9]{36,}\b/g, '[REDACTED]');
45
-
46
- // OpenAI / similar keys (sk-...)
47
- out = out.replace(/\bsk-[A-Za-z0-9T-]{20,}\b/g, '[REDACTED]');
48
-
49
- // JWT tokens (base64url header eyJ...)
50
- out = out.replace(/\beyJ[A-Za-z0-9._-]{40,}\b/g, '[REDACTED]');
51
-
52
- return out;
53
- }
54
-
55
- /**
56
- * Store-time sanitization: strips credential values before any durable write.
57
- * Called inside storeObservation() / upsertObservation() so that every write
58
- * path (hooks, git-ingest, CLI, reasoning, compact-on-write) is covered.
59
- */
60
- export function sanitizeCredentials(text: string): string {
61
- return applyRedaction(text);
62
- }
63
-
64
- /**
65
- * Retrieval-time redaction: masks credential values in display output.
66
- * Applied in all output formatters (detail, index table, timeline, session context)
67
- * as a safety net for legacy observations stored before sanitization was in place.
68
- */
69
- export function redactCredentials(text: string): string {
70
- return applyRedaction(text);
71
- }
72
-
73
- /**
74
- * Returns true if the text contains an obvious credential pattern.
75
- * Used for testing and optional diagnostic logging.
76
- */
77
- export function containsCredential(text: string): boolean {
78
- return applyRedaction(text) !== text;
79
- }
1
+ /**
2
+ * Secret Filter
3
+ *
4
+ * Conservative credential detection and redaction for Memorix memory content.
5
+ * Designed for low false-positive risk: only matches explicit credential
6
+ * assignments (key=value / key: value), not generic discussion of auth concepts.
7
+ *
8
+ * sanitizeCredentials() — store-time: called inside storeObservation() before write
9
+ * redactCredentials() — retrieval-time: called in format/output paths for legacy safety
10
+ * containsCredential() — predicate used for testing and optional logging
11
+ *
12
+ * Both sanitize and redact share the same pattern logic. They are kept as
13
+ * separate named exports so call-site semantics remain clear.
14
+ */
15
+
16
+ /**
17
+ * Replaces the VALUE portion of matched credential patterns with [REDACTED].
18
+ * The credential key name is preserved so context is not lost.
19
+ *
20
+ * Pattern categories:
21
+ * 1. key=value / key: value forms — password, token, api_key, secret, etc.
22
+ * Requires a real value (6+ non-whitespace chars) to avoid matching
23
+ * discussion text like "password must be 8 chars".
24
+ * 2. Bearer authorization header tokens (20+ chars)
25
+ * 3. Structured token prefixes: GitHub (ghp_/ghs_/gho_), OpenAI (sk-), JWT (eyJ)
26
+ */
27
+ function applyRedaction(text: string): string {
28
+ if (!text) return text;
29
+ let out = text;
30
+
31
+ // key=value / key: "value" / key='value' — preserve key, redact value
32
+ out = out.replace(
33
+ /((?:password|passwd|pwd|secret|token|api[_-]?key|auth[_-]?key|access[_-]?key|private[_-]?key|client[_-]?secret)\s*[:=]\s*["']?)([^\s"',;\n]{6,})/gi,
34
+ '$1[REDACTED]',
35
+ );
36
+
37
+ // Bearer <token> — preserve "Bearer ", redact token
38
+ out = out.replace(
39
+ /(Bearer\s+)([A-Za-z0-9._\-/+]{20,})/g,
40
+ '$1[REDACTED]',
41
+ );
42
+
43
+ // GitHub tokens (ghp_, ghs_, gho_, github_pat_)
44
+ out = out.replace(/\b(?:ghp|ghs|gho|github_pat)_[A-Za-z0-9]{36,}\b/g, '[REDACTED]');
45
+
46
+ // OpenAI / similar keys (sk-...)
47
+ out = out.replace(/\bsk-[A-Za-z0-9T-]{20,}\b/g, '[REDACTED]');
48
+
49
+ // JWT tokens (base64url header eyJ...)
50
+ out = out.replace(/\beyJ[A-Za-z0-9._-]{40,}\b/g, '[REDACTED]');
51
+
52
+ return out;
53
+ }
54
+
55
+ /**
56
+ * Store-time sanitization: strips credential values before any durable write.
57
+ * Called inside storeObservation() / upsertObservation() so that every write
58
+ * path (hooks, git-ingest, CLI, reasoning, compact-on-write) is covered.
59
+ */
60
+ export function sanitizeCredentials(text: string): string {
61
+ return applyRedaction(text);
62
+ }
63
+
64
+ /**
65
+ * Retrieval-time redaction: masks credential values in display output.
66
+ * Applied in all output formatters (detail, index table, timeline, session context)
67
+ * as a safety net for legacy observations stored before sanitization was in place.
68
+ */
69
+ export function redactCredentials(text: string): string {
70
+ return applyRedaction(text);
71
+ }
72
+
73
+ /**
74
+ * Returns true if the text contains an obvious credential pattern.
75
+ * Used for testing and optional diagnostic logging.
76
+ */
77
+ export function containsCredential(text: string): boolean {
78
+ return applyRedaction(text) !== text;
79
+ }
@@ -11,7 +11,7 @@
11
11
  * - Cross-agent session awareness (all agents share session data)
12
12
  */
13
13
 
14
- import type { Observation, Session } from '../types.js';
14
+ import type { Observation, ObservationReader, Session } from '../types.js';
15
15
  import { isEligibleForAutomaticDelivery } from './admission.js';
16
16
  import { classifyLayer } from './disclosure-policy.js';
17
17
  import { resolveAliases } from '../project/aliases.js';
@@ -19,8 +19,20 @@ import { getObservationStore } from '../store/obs-store.js';
19
19
  import { getSessionStore } from '../store/session-store.js';
20
20
  import { KnowledgeGraphManager } from './graph.js';
21
21
  import { redactCredentials, sanitizeCredentials } from './secret-filter.js';
22
+ import { canReadObservation } from './visibility.js';
22
23
 
23
24
  const PRIORITY_TYPES = new Set(['gotcha', 'decision', 'problem-solution', 'trade-off', 'discovery']);
25
+ const RESUME_TYPES = new Set([
26
+ 'gotcha',
27
+ 'decision',
28
+ 'problem-solution',
29
+ 'trade-off',
30
+ 'discovery',
31
+ 'how-it-works',
32
+ 'what-changed',
33
+ 'reasoning',
34
+ 'session-request',
35
+ ]);
24
36
  const TYPE_EMOJI: Record<string, string> = {
25
37
  'gotcha': '[DISCOVERY]',
26
38
  'decision': '[WHY]',
@@ -177,6 +189,138 @@ function isSystemSelfObservation(obs: Observation): boolean {
177
189
  return SYSTEM_SELF_PATTERNS.some((pattern) => pattern.test(text));
178
190
  }
179
191
 
192
+ export interface SessionResumeMemory {
193
+ id: number;
194
+ title: string;
195
+ type: string;
196
+ detail?: string;
197
+ }
198
+
199
+ /** A compact, source-aware view of prior work for a task continuation. */
200
+ export interface SessionResumeBrief {
201
+ previousSession?: {
202
+ id: string;
203
+ agent?: string;
204
+ endedAt?: string;
205
+ summary: string;
206
+ };
207
+ memories: SessionResumeMemory[];
208
+ }
209
+
210
+ function readerForAlias(
211
+ reader: ObservationReader | undefined,
212
+ aliases: Set<string>,
213
+ observation: Observation,
214
+ ): ObservationReader | undefined {
215
+ if (!reader) return undefined;
216
+ return reader.projectId && aliases.has(observation.projectId)
217
+ ? { ...reader, projectId: observation.projectId }
218
+ : reader;
219
+ }
220
+
221
+ function readableAliasObservations(
222
+ observations: Observation[],
223
+ aliases: Set<string>,
224
+ reader?: ObservationReader,
225
+ ): Observation[] {
226
+ return reader
227
+ ? observations.filter((observation) => canReadObservation(
228
+ observation,
229
+ readerForAlias(reader, aliases, observation),
230
+ ))
231
+ : observations;
232
+ }
233
+
234
+ function isUsefulSessionSummary(summary: string | undefined): summary is string {
235
+ return Boolean(summary)
236
+ && !NOISE_PATTERNS.some((pattern) => pattern.test(summary!))
237
+ && !SYSTEM_SELF_PATTERNS.some((pattern) => pattern.test(summary!));
238
+ }
239
+
240
+ function continuationTaskTokens(task?: string): string[] {
241
+ return [...new Set(
242
+ (task?.toLowerCase().match(/[a-z0-9_./-]+|[\u4e00-\u9fff]+/g) ?? [])
243
+ .map((token) => token.trim())
244
+ .filter((token) => token.length > 1 && !['continue', 'resume', '继续', '接手', '恢复'].includes(token)),
245
+ )].slice(0, 8);
246
+ }
247
+
248
+ function continuationScore(observation: Observation, projectTokens: string[], task?: string): number {
249
+ let score = scoreObservationForSessionContext(observation, projectTokens);
250
+ if (observation.type === 'session-request' || observation.type === 'what-changed') score += 1;
251
+ const text = stringifyObservation(observation);
252
+ const matches = continuationTaskTokens(task).filter((token) => text.includes(token)).length;
253
+ score += Math.min(matches, 2) * 2;
254
+ return score;
255
+ }
256
+
257
+ /**
258
+ * Return only the latest meaningful session summary and a few durable memory
259
+ * anchors. It is deliberately separate from the verbose session-context view:
260
+ * this is a delivery projection for one bounded Task Workset, not another
261
+ * storage model or an automatic transcript dump.
262
+ */
263
+ export async function getSessionResumeBrief(
264
+ projectId: string,
265
+ task?: string,
266
+ reader?: ObservationReader,
267
+ ): Promise<SessionResumeBrief> {
268
+ const aliasSet = await resolveProjectIds(projectId);
269
+ const [sessions, allObs] = await Promise.all([
270
+ loadAliasSessions(aliasSet),
271
+ loadAliasActiveObservations(aliasSet),
272
+ ]);
273
+ const readableObs = readableAliasObservations(allObs, aliasSet, reader);
274
+ const previous = sessions
275
+ .filter((session) => session.status === 'completed')
276
+ .sort((a, b) => new Date(b.endedAt || b.startedAt).getTime() - new Date(a.endedAt || a.startedAt).getTime())
277
+ .find((session) => (
278
+ session.summary
279
+ && session.summary !== '(session ended implicitly by new session start)'
280
+ && isUsefulSessionSummary(session.summary)
281
+ ));
282
+
283
+ const projectTokens = tokenizeProjectId(projectId);
284
+ const memories = readableObs
285
+ .filter((observation) => RESUME_TYPES.has(observation.type))
286
+ .filter((observation) => classifyLayer(observation) === 'L2')
287
+ .filter((observation) => !isNoiseObservation(observation) && !isSystemSelfObservation(observation))
288
+ .map((observation) => ({
289
+ observation,
290
+ score: continuationScore(observation, projectTokens, task),
291
+ }))
292
+ .sort((a, b) => (
293
+ b.score - a.score
294
+ || new Date(b.observation.createdAt).getTime() - new Date(a.observation.createdAt).getTime()
295
+ || a.observation.id - b.observation.id
296
+ ))
297
+ .slice(0, 3)
298
+ .map(({ observation }) => ({
299
+ id: observation.id,
300
+ title: sanitizeCredentials(observation.title),
301
+ type: observation.type,
302
+ ...(observation.facts?.[0]
303
+ ? { detail: sanitizeCredentials(observation.facts[0]) }
304
+ : observation.narrative
305
+ ? { detail: sanitizeCredentials(observation.narrative) }
306
+ : {}),
307
+ }));
308
+
309
+ return {
310
+ ...(previous?.summary
311
+ ? {
312
+ previousSession: {
313
+ id: previous.id,
314
+ ...(previous.agent ? { agent: previous.agent } : {}),
315
+ ...(previous.endedAt ? { endedAt: previous.endedAt } : {}),
316
+ summary: sanitizeCredentials(previous.summary),
317
+ },
318
+ }
319
+ : {}),
320
+ memories,
321
+ };
322
+ }
323
+
180
324
  export function scoreObservationForSessionContext(obs: Observation, projectTokens: string[], now = Date.now()): number {
181
325
  let score = TYPE_WEIGHTS[obs.type] ?? 1;
182
326
  const text = stringifyObservation(obs);
@@ -242,7 +386,7 @@ export function scoreObservationForSessionContext(obs: Observation, projectToken
242
386
  export async function startSession(
243
387
  projectDir: string,
244
388
  projectId: string,
245
- opts?: { sessionId?: string; agent?: string },
389
+ opts?: { sessionId?: string; agent?: string; reader?: ObservationReader },
246
390
  ): Promise<{ session: Session; previousContext: string }> {
247
391
  const sessionId = opts?.sessionId || generateSessionId();
248
392
  const now = new Date().toISOString();
@@ -256,7 +400,7 @@ export async function startSession(
256
400
  };
257
401
 
258
402
  // Load previous context before creating new session
259
- const previousContext = await getSessionContext(projectDir, projectId);
403
+ const previousContext = await getSessionContext(projectDir, projectId, 3, opts?.reader);
260
404
 
261
405
  // Atomic rollover: complete all active sessions for this project's aliases
262
406
  // and insert the new session in a single SQLite transaction.
@@ -306,17 +450,22 @@ export async function endSession(
306
450
  * Key Memories — durable explicit working context (L2)
307
451
  * Session History— orientation log
308
452
  * L3 Evidence — pointers to git-memory and hook traces (on-demand)
453
+ *
454
+ * When a reader is supplied, automatic observation delivery follows that
455
+ * identity's visibility boundary. Omit it only for trusted maintenance paths.
309
456
  */
310
457
  export async function getSessionContext(
311
458
  projectDir: string,
312
459
  projectId: string,
313
460
  limit: number = 3,
461
+ reader?: ObservationReader,
314
462
  ): Promise<string> {
315
463
  const aliasSet = await resolveProjectIds(projectId);
316
464
  const [sessions, allObs] = await Promise.all([
317
465
  loadAliasSessions(aliasSet),
318
466
  loadAliasActiveObservations(aliasSet),
319
467
  ]);
468
+ const readableObs = readableAliasObservations(allObs, aliasSet, reader);
320
469
  /** Check if a session summary contains noise/system-self content */
321
470
  const isNoisySummary = (summary: string | undefined): boolean => {
322
471
  if (!summary) return false;
@@ -329,7 +478,7 @@ export async function getSessionContext(
329
478
  .sort((a, b) => new Date(b.endedAt || b.startedAt).getTime() - new Date(a.endedAt || a.startedAt).getTime())
330
479
  .slice(0, limit);
331
480
 
332
- if (projectSessions.length === 0 && allObs.length === 0) {
481
+ if (projectSessions.length === 0 && readableObs.length === 0) {
333
482
  return '';
334
483
  }
335
484
 
@@ -337,7 +486,7 @@ export async function getSessionContext(
337
486
  const projectTokens = tokenizeProjectId(projectId);
338
487
 
339
488
  // ── Partition project observations by disclosure layer ─────────────
340
- const projectObs = allObs
489
+ const projectObs = readableObs
341
490
  .filter((obs) => !isNoiseObservation(obs) && !isSystemSelfObservation(obs));
342
491
 
343
492
  // L2: durable working context (explicit/undefined/core), priority types only
@@ -392,11 +541,11 @@ export async function getSessionContext(
392
541
  // Active entities enrich the section when it is shown but do not open it alone.
393
542
  const hasL1Content = l1HookObs.length > 0 || l3GitCount > 0;
394
543
  if (hasL1Content) {
395
- // Graph neighbor routing hint: 1-hop neighbors of activeEntities from the
396
- // knowledge graph. Routing only no query expansion, no rerank, no 2-hop
397
- // traversal. Silently skipped if graph is absent, empty, or throws.
544
+ // Graph relations do not yet carry observation visibility metadata, so an
545
+ // agent-facing reader must not use them as an indirect disclosure channel.
546
+ // Trusted maintenance paths retain the existing routing hint.
398
547
  let graphNeighbors: string[] = [];
399
- if (activeEntities.length > 0) {
548
+ if (!reader && activeEntities.length > 0) {
400
549
  try {
401
550
  const graphMgr = new KnowledgeGraphManager(projectDir);
402
551
  await graphMgr.init();