@namzu/sdk 62.1.0 → 62.2.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.
Files changed (66) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/dist/compaction/consolidation.d.ts +16 -1
  3. package/dist/compaction/consolidation.d.ts.map +1 -1
  4. package/dist/compaction/consolidation.js +8 -6
  5. package/dist/compaction/consolidation.js.map +1 -1
  6. package/dist/compaction/context-window.d.ts.map +1 -1
  7. package/dist/compaction/context-window.js +1 -0
  8. package/dist/compaction/context-window.js.map +1 -1
  9. package/dist/compaction/index.d.ts +1 -0
  10. package/dist/compaction/index.d.ts.map +1 -1
  11. package/dist/pricing/catalogue.generated.d.ts.map +1 -1
  12. package/dist/pricing/catalogue.generated.js +41 -1
  13. package/dist/pricing/catalogue.generated.js.map +1 -1
  14. package/dist/public-runtime.d.ts +3 -2
  15. package/dist/public-runtime.d.ts.map +1 -1
  16. package/dist/public-runtime.js +1 -1
  17. package/dist/public-runtime.js.map +1 -1
  18. package/dist/public-types.d.ts +1 -1
  19. package/dist/public-types.d.ts.map +1 -1
  20. package/dist/runtime/query/finalize-turn.d.ts.map +1 -1
  21. package/dist/runtime/query/finalize-turn.js +1 -1
  22. package/dist/runtime/query/finalize-turn.js.map +1 -1
  23. package/dist/runtime/query/index.d.ts +7 -0
  24. package/dist/runtime/query/index.d.ts.map +1 -1
  25. package/dist/runtime/query/index.js.map +1 -1
  26. package/dist/store/index.d.ts +2 -0
  27. package/dist/store/index.d.ts.map +1 -1
  28. package/dist/store/index.js +1 -0
  29. package/dist/store/index.js.map +1 -1
  30. package/dist/store/memory/secrets.d.ts +20 -0
  31. package/dist/store/memory/secrets.d.ts.map +1 -0
  32. package/dist/store/memory/secrets.js +84 -0
  33. package/dist/store/memory/secrets.js.map +1 -0
  34. package/dist/tools/memory/save.d.ts.map +1 -1
  35. package/dist/tools/memory/save.js +5 -0
  36. package/dist/tools/memory/save.js.map +1 -1
  37. package/dist/tools/memory/secret-guard.d.ts +8 -0
  38. package/dist/tools/memory/secret-guard.d.ts.map +1 -0
  39. package/dist/tools/memory/secret-guard.js +23 -0
  40. package/dist/tools/memory/secret-guard.js.map +1 -0
  41. package/dist/tools/memory/update.d.ts.map +1 -1
  42. package/dist/tools/memory/update.js +10 -0
  43. package/dist/tools/memory/update.js.map +1 -1
  44. package/dist/turn/index.d.ts +1 -1
  45. package/dist/turn/index.d.ts.map +1 -1
  46. package/dist/turn/memory-promoter.d.ts +20 -0
  47. package/dist/turn/memory-promoter.d.ts.map +1 -1
  48. package/dist/turn/memory-promoter.js +6 -3
  49. package/dist/turn/memory-promoter.js.map +1 -1
  50. package/package.json +1 -1
  51. package/src/compaction/consolidation.ts +29 -4
  52. package/src/compaction/context-window.ts +1 -0
  53. package/src/compaction/index.ts +1 -0
  54. package/src/pricing/catalogue.generated.ts +41 -1
  55. package/src/pricing/rates.source.json +37 -2
  56. package/src/public-runtime.ts +3 -1
  57. package/src/public-types.ts +2 -0
  58. package/src/runtime/query/finalize-turn.ts +9 -5
  59. package/src/runtime/query/index.ts +8 -0
  60. package/src/store/index.ts +2 -0
  61. package/src/store/memory/secrets.ts +105 -0
  62. package/src/tools/memory/save.ts +4 -0
  63. package/src/tools/memory/secret-guard.ts +23 -0
  64. package/src/tools/memory/update.ts +9 -0
  65. package/src/turn/index.ts +1 -1
  66. package/src/turn/memory-promoter.ts +19 -2
@@ -7,6 +7,7 @@ import {
7
7
  } from '../../advisory/index.js'
8
8
  import { AuthorizationGate } from '../../authorization/gate.js'
9
9
  import { SkillGrantSet } from '../../authorization/skill-grant.js'
10
+ import type { ConsolidationScreen } from '../../compaction/consolidation.js'
10
11
  import { repairToolMessageHistory, toolHistoryRepairChanged } from '../../compaction/dangling.js'
11
12
  import { extractFromUserMessage } from '../../compaction/extractor.js'
12
13
  import { WorkingStateManager } from '../../compaction/manager.js'
@@ -963,6 +964,13 @@ export interface QueryParams {
963
964
  */
964
965
  consolidateInto?: MemoryStore
965
966
 
967
+ /**
968
+ * What a consolidation may quote. Absent keeps the task, decisions,
969
+ * discoveries and failures verbatim; a host whose store is recalled into
970
+ * later prompts sets it to drop or neutralise what should not persist.
971
+ */
972
+ consolidationScreen?: ConsolidationScreen
973
+
966
974
  /**
967
975
  * Optional neutral working-memory seam. When set, the iteration loop
968
976
  * re-renders the provider's string into a single pinned leading system
@@ -37,6 +37,8 @@ export {
37
37
  isMemoryName,
38
38
  slugifyMemoryName,
39
39
  } from './memory/naming.js'
40
+ export { findSecretShape } from './memory/secrets.js'
41
+ export type { SecretScanOptions, SecretShape } from './memory/secrets.js'
40
42
  export type { MemoryContentRejection } from './memory/naming.js'
41
43
  export { MemoryRevisionConflictError } from './memory/revision.js'
42
44
 
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Secret shapes: text that looks like a credential and must not be written to
3
+ * a memory. A memory is recalled into later prompts, sent to providers and
4
+ * kept on disk in the clear, so a key saved once is a key leaked many times.
5
+ *
6
+ * The match is on shape only. It names a reason code and never returns the
7
+ * matched text, so a caller can log the code without logging the secret.
8
+ */
9
+
10
+ export type SecretShape =
11
+ | 'private_key'
12
+ | 'provider_token'
13
+ | 'jwt'
14
+ | 'credentialed_url'
15
+ | 'high_entropy'
16
+
17
+ const PATTERNS: readonly (readonly [SecretShape, RegExp])[] = [
18
+ ['private_key', /-----BEGIN [A-Z ]*PRIVATE KEY(?: BLOCK)?-----/],
19
+ [
20
+ 'provider_token',
21
+ new RegExp(
22
+ [
23
+ '\\bsk-(?:ant-|proj-|live-|or-v1-)?[A-Za-z0-9_-]{20,}',
24
+ '\\bgh[pousr]_[A-Za-z0-9]{30,}',
25
+ '\\bgithub_pat_[A-Za-z0-9_]{30,}',
26
+ '\\bglpat-[A-Za-z0-9_-]{16,}',
27
+ '\\bxox[abposr]-[A-Za-z0-9-]{10,}',
28
+ '\\b(?:AKIA|ASIA)[A-Z0-9]{16}\\b',
29
+ '\\bAIza[A-Za-z0-9_-]{35}\\b',
30
+ '\\bnvapi-[A-Za-z0-9_-]{20,}',
31
+ '\\bnpm_[A-Za-z0-9]{30,}',
32
+ '\\b[sr]k_(?:live|test)_[A-Za-z0-9]{16,}',
33
+ '\\bSG\\.[A-Za-z0-9_-]{16,}\\.[A-Za-z0-9_-]{16,}',
34
+ '\\bhf_[A-Za-z0-9]{30,}',
35
+ '\\bBearer\\s+[A-Za-z0-9._~+/-]{24,}=*',
36
+ ].join('|'),
37
+ ),
38
+ ],
39
+ ['jwt', /\beyJ[A-Za-z0-9_-]{8,}\.eyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}/],
40
+ ['credentialed_url', /\b[a-z][a-z0-9+.-]*:\/\/[^\s/:@]+:[^\s/@]+@[^\s/]+/i],
41
+ ]
42
+
43
+ /** Shannon entropy of `text`, in bits per character. */
44
+ function entropy(text: string): number {
45
+ const counts = new Map<string, number>()
46
+ for (const ch of text) counts.set(ch, (counts.get(ch) ?? 0) + 1)
47
+ let bits = 0
48
+ for (const n of counts.values()) {
49
+ const p = n / text.length
50
+ bits -= p * Math.log2(p)
51
+ }
52
+ return bits
53
+ }
54
+
55
+ /** How many times the token switches between a letter and a digit. */
56
+ function letterDigitSwitches(token: string): number {
57
+ let switches = 0
58
+ let previous: 'letter' | 'digit' | undefined
59
+ for (const ch of token) {
60
+ const kind = /\d/.test(ch) ? 'digit' : /[A-Za-z]/.test(ch) ? 'letter' : undefined
61
+ if (kind === undefined) continue
62
+ if (previous !== undefined && previous !== kind) switches += 1
63
+ previous = kind
64
+ }
65
+ return switches
66
+ }
67
+
68
+ /**
69
+ * Random, not merely long: a long identifier with a trailing number
70
+ * (`DecisionsAbcDefGhi1234567890`) has mixed case and a digit but few
71
+ * letter-digit switches and a low spread, a key has both. A path-like token
72
+ * is a path.
73
+ */
74
+ function looksRandom(token: string): boolean {
75
+ return (
76
+ !token.includes('/') &&
77
+ /[A-Z]/.test(token) &&
78
+ /[a-z]/.test(token) &&
79
+ letterDigitSwitches(token) >= 4 &&
80
+ entropy(token) >= 4.5
81
+ )
82
+ }
83
+
84
+ export interface SecretScanOptions {
85
+ /**
86
+ * Also flag a long unbroken run of mixed-case letters and digits that looks
87
+ * random. Off by default: it can flag an identifier a person meant to keep,
88
+ * which is right for text nobody chose and wrong for text somebody did.
89
+ */
90
+ readonly entropy?: boolean
91
+ }
92
+
93
+ /** The first secret shape `text` has, or `undefined`. */
94
+ export function findSecretShape(
95
+ text: string,
96
+ options: SecretScanOptions = {},
97
+ ): SecretShape | undefined {
98
+ for (const [shape, pattern] of PATTERNS) if (pattern.test(text)) return shape
99
+ if (options.entropy === true) {
100
+ for (const token of text.match(/[A-Za-z0-9+/_=-]{32,}/g) ?? []) {
101
+ if (looksRandom(token)) return 'high_entropy'
102
+ }
103
+ }
104
+ return undefined
105
+ }
@@ -5,6 +5,7 @@ import type { MemoryStore } from '../../types/memory/index.js'
5
5
  import type { ToolDefinition } from '../../types/tool/index.js'
6
6
  import { defineTool } from '../defineTool.js'
7
7
  import { memoryFieldsSchema } from './fields.js'
8
+ import { refuseSecret } from './secret-guard.js'
8
9
 
9
10
  export function buildSaveMemoryTool(store: MemoryStore): ToolDefinition {
10
11
  return defineTool({
@@ -13,6 +14,7 @@ export function buildSaveMemoryTool(store: MemoryStore): ToolDefinition {
13
14
  'Save one memory for later sessions in this project: a single fact that the code, the history and the docs do not already hold.',
14
15
  'Choose a type: user (who the person is and how they like to work), feedback (a rule they gave about how to work), project (a fact or decision about the work), reference (where to look for something).',
15
16
  'For feedback and project memories, write the rule or fact first, then a line starting "Why:" and a line starting "How to apply:".',
17
+ 'Never save a secret: an API key, token, password, private key or a URL with credentials in it is refused.',
16
18
  'Link a related memory by writing [[its-name]] in the content.',
17
19
  'The name must be new: a name another memory holds is refused, and the reply points to update_memory.',
18
20
  "Returns the saved memory's id and name.",
@@ -30,6 +32,8 @@ export function buildSaveMemoryTool(store: MemoryStore): ToolDefinition {
30
32
  destructive: false,
31
33
  concurrencySafe: true,
32
34
  async execute({ title, summary, content, tags, name, description, type }, context) {
35
+ const refused = refuseSecret([title, summary, content, description, ...(tags ?? [])])
36
+ if (refused) return refused
33
37
  try {
34
38
  const { entry } = await store.create({
35
39
  title,
@@ -0,0 +1,23 @@
1
+ import { findSecretShape } from '../../store/memory/secrets.js'
2
+ import type { ToolResult } from '../../types/tool/index.js'
3
+
4
+ /**
5
+ * A refusal when any field a model is saving holds a secret, else `undefined`.
6
+ * The reply names the shape, never the text, so the refusal does not repeat
7
+ * the secret into the transcript.
8
+ */
9
+ export function refuseSecret(fields: readonly (string | undefined)[]): ToolResult | undefined {
10
+ for (const field of fields) {
11
+ if (field === undefined) continue
12
+ const shape = findSecretShape(field)
13
+ if (shape === undefined) continue
14
+ return {
15
+ success: false,
16
+ output:
17
+ 'The memory looks like it contains a secret (an API key, token, password, private key or a URL with credentials). Memories are recalled into later prompts and kept on disk in the clear, so it was not saved. Save where the secret lives (the variable or file name) instead of its value.',
18
+ error: 'Memory content rejected',
19
+ data: { reason: 'secret', shape },
20
+ }
21
+ }
22
+ return undefined
23
+ }
@@ -7,6 +7,7 @@ import type { ToolDefinition } from '../../types/tool/index.js'
7
7
  import { defineTool } from '../defineTool.js'
8
8
  import { memoryFieldsSchema } from './fields.js'
9
9
  import { resolveMemoryReference } from './resolve.js'
10
+ import { refuseSecret } from './secret-guard.js'
10
11
 
11
12
  export function buildUpdateMemoryTool(store: MemoryStore): ToolDefinition {
12
13
  return defineTool({
@@ -52,6 +53,14 @@ export function buildUpdateMemoryTool(store: MemoryStore): ToolDefinition {
52
53
  error: 'Conditional memory update unavailable',
53
54
  }
54
55
  }
56
+ const refused = refuseSecret([
57
+ updates.title,
58
+ updates.summary,
59
+ updates.content,
60
+ updates.description,
61
+ ...(updates.tags ?? []),
62
+ ])
63
+ if (refused) return refused
55
64
  const reference = await resolveMemoryReference(store, id)
56
65
  if (!reference.found) {
57
66
  return {
package/src/turn/index.ts CHANGED
@@ -22,7 +22,7 @@ export { checkLimitsDetailed, buildLimitConfig } from './LimitChecker.js'
22
22
  export type { LimitCheckerState, LimitCheckResult } from './LimitChecker.js'
23
23
 
24
24
  export { SESSION_MEMORY_TAG, createMemoryPromoter } from './memory-promoter.js'
25
- export type { MemoryPromoterOptions } from './memory-promoter.js'
25
+ export type { MemoryPromoterOptions, SessionMemoryCategory } from './memory-promoter.js'
26
26
  export { createMemoryRecallStep } from './memory-recall.js'
27
27
  export type { MemoryRecallOptions } from './memory-recall.js'
28
28
  export { createJsonClaimVerifier } from './json-claim-verifier.js'
@@ -55,17 +55,33 @@ export interface MemoryPromoterOptions {
55
55
  * that costs context every time it is retrieved.
56
56
  */
57
57
  readonly maxPerCategory?: number
58
+ /**
59
+ * Decide whether one extracted claim may become part of a record. Called
60
+ * with the candidate's category (`userRequirements`, `decisions`,
61
+ * `discoveries`, `failures` or `environment`) and the claim text. A claim
62
+ * it rejects is left out; a record with no claim left is not written.
63
+ * Defaults to keeping every claim.
64
+ */
65
+ readonly acceptClaim?: (category: SessionMemoryCategory, text: string) => boolean
58
66
  }
59
67
 
68
+ /** The claim categories of a {@link SessionMemoryCandidate}. */
69
+ export type SessionMemoryCategory = (typeof KNOWLEDGE)[number][0]
70
+
60
71
  /** Everything the candidate knows, as `[heading, items]`, empties dropped. */
61
72
  function knowledge(
62
73
  candidate: SessionMemoryCandidate,
63
74
  cap: number,
75
+ accept: (category: SessionMemoryCategory, text: string) => boolean,
64
76
  ): readonly (readonly [string, readonly string[]])[] {
65
77
  const out: (readonly [string, readonly string[]])[] = []
66
78
  for (const [key, heading] of KNOWLEDGE) {
67
79
  const items = [
68
- ...new Set((candidate[key] as readonly string[]).map((item) => item.trim()).filter(Boolean)),
80
+ ...new Set(
81
+ (candidate[key] as readonly string[])
82
+ .map((item) => item.trim())
83
+ .filter((item) => item !== '' && accept(key, item)),
84
+ ),
69
85
  ]
70
86
  if (items.length > 0) out.push([heading, items.slice(0, cap)])
71
87
  }
@@ -115,10 +131,11 @@ export function createMemoryPromoter(options: MemoryPromoterOptions): PromoteMem
115
131
  const cap = options.maxPerCategory ?? 20
116
132
  if (!Number.isSafeInteger(cap) || cap < 1)
117
133
  throw new Error('maxPerCategory must be a positive integer')
134
+ const accept = options.acceptClaim ?? (() => true)
118
135
  const tags = [SESSION_MEMORY_TAG, ...(options.tags ?? [])]
119
136
 
120
137
  return async (candidate: SessionMemoryCandidate): Promise<void> => {
121
- const sections = knowledge(candidate, cap)
138
+ const sections = knowledge(candidate, cap, accept)
122
139
  // Nothing learned, nothing written. Not an empty record: a store full
123
140
  // of rows describing runs that discovered nothing is a store whose
124
141
  // search results are mostly noise, and the model reads that store.