switchroom 0.12.27 → 0.12.28
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/cli/switchroom.js +4 -2
- package/package.json +2 -1
- package/telegram-plugin/dist/gateway/gateway.js +49 -5
- package/telegram-plugin/gateway/gateway.ts +5 -0
- package/telegram-plugin/stderr-timestamps.ts +106 -0
- package/telegram-plugin/tests/stderr-timestamps.test.ts +113 -0
- package/vendor/hindsight-memory/.claude-plugin/plugin.json +8 -0
- package/vendor/hindsight-memory/CHANGELOG.md +32 -0
- package/vendor/hindsight-memory/LICENSE +21 -0
- package/vendor/hindsight-memory/README.md +329 -0
- package/vendor/hindsight-memory/hooks/hooks.json +49 -0
- package/vendor/hindsight-memory/scripts/drain_pending.py +190 -0
- package/vendor/hindsight-memory/scripts/lib/__init__.py +0 -0
- package/vendor/hindsight-memory/scripts/lib/bank.py +122 -0
- package/vendor/hindsight-memory/scripts/lib/client.py +204 -0
- package/vendor/hindsight-memory/scripts/lib/config.py +180 -0
- package/vendor/hindsight-memory/scripts/lib/content.py +493 -0
- package/vendor/hindsight-memory/scripts/lib/daemon.py +334 -0
- package/vendor/hindsight-memory/scripts/lib/directives.py +119 -0
- package/vendor/hindsight-memory/scripts/lib/gateway_ipc.py +126 -0
- package/vendor/hindsight-memory/scripts/lib/llm.py +146 -0
- package/vendor/hindsight-memory/scripts/lib/pending.py +218 -0
- package/vendor/hindsight-memory/scripts/lib/state.py +196 -0
- package/vendor/hindsight-memory/scripts/recall.py +873 -0
- package/vendor/hindsight-memory/scripts/retain.py +286 -0
- package/vendor/hindsight-memory/scripts/session_end.py +122 -0
- package/vendor/hindsight-memory/scripts/session_start.py +76 -0
- package/vendor/hindsight-memory/scripts/setup_hooks.py +115 -0
- package/vendor/hindsight-memory/scripts/tests/__init__.py +0 -0
- package/vendor/hindsight-memory/scripts/tests/test_directives.py +211 -0
- package/vendor/hindsight-memory/scripts/tests/test_gateway_ipc.py +205 -0
- package/vendor/hindsight-memory/scripts/tests/test_recall_integration.py +621 -0
- package/vendor/hindsight-memory/settings.json +37 -0
- package/vendor/hindsight-memory/skills/setup.md +24 -0
- package/vendor/hindsight-memory/tests/conftest.py +94 -0
- package/vendor/hindsight-memory/tests/test_bank.py +142 -0
- package/vendor/hindsight-memory/tests/test_client.py +232 -0
- package/vendor/hindsight-memory/tests/test_config.py +128 -0
- package/vendor/hindsight-memory/tests/test_content.py +471 -0
- package/vendor/hindsight-memory/tests/test_drain_pending.py +192 -0
- package/vendor/hindsight-memory/tests/test_hooks.py +808 -0
- package/vendor/hindsight-memory/tests/test_manifest.py +14 -0
- package/vendor/hindsight-memory/tests/test_pending.py +152 -0
- package/vendor/hindsight-memory/tests/test_recall_exit_codes.py +325 -0
- package/vendor/hindsight-memory/tests/test_session_end_pending.py +205 -0
- package/vendor/hindsight-memory/tests/test_state.py +125 -0
package/dist/cli/switchroom.js
CHANGED
|
@@ -47247,8 +47247,8 @@ var {
|
|
|
47247
47247
|
} = import__.default;
|
|
47248
47248
|
|
|
47249
47249
|
// src/build-info.ts
|
|
47250
|
-
var VERSION = "0.12.
|
|
47251
|
-
var COMMIT_SHA = "
|
|
47250
|
+
var VERSION = "0.12.28";
|
|
47251
|
+
var COMMIT_SHA = "61036e48";
|
|
47252
47252
|
|
|
47253
47253
|
// src/cli/agent.ts
|
|
47254
47254
|
init_source();
|
|
@@ -48576,6 +48576,8 @@ function installHindsightPlugin(agentName, agentDir, switchroomConfig) {
|
|
|
48576
48576
|
return null;
|
|
48577
48577
|
const sourcePath = resolveHindsightVendorPath();
|
|
48578
48578
|
if (!existsSync11(sourcePath)) {
|
|
48579
|
+
process.stderr.write(`installHindsightPlugin: vendor source missing at ${sourcePath} ` + `\u2014 hindsight plugin NOT installed for ${agentName}. ` + `Likely a packaging regression: check the npm tarball's files array.
|
|
48580
|
+
`);
|
|
48579
48581
|
return null;
|
|
48580
48582
|
}
|
|
48581
48583
|
const destPath = join8(agentDir, ".claude", "plugins", "hindsight-memory");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "switchroom",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.28",
|
|
4
4
|
"description": "Run Claude Code 24/7 on your Claude Pro/Max subscription over Telegram. Open-source alternative to OpenClaw and NanoClaw — no API keys.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
"profiles",
|
|
14
14
|
"skills",
|
|
15
15
|
"telegram-plugin",
|
|
16
|
+
"vendor",
|
|
16
17
|
"bin",
|
|
17
18
|
"README.md",
|
|
18
19
|
"LICENSE"
|
|
@@ -29800,6 +29800,49 @@ function installPluginLogger(env = process.env) {
|
|
|
29800
29800
|
return activeHandle;
|
|
29801
29801
|
}
|
|
29802
29802
|
|
|
29803
|
+
// stderr-timestamps.ts
|
|
29804
|
+
var installed = false;
|
|
29805
|
+
var originalWrite = null;
|
|
29806
|
+
var partialBuffer = "";
|
|
29807
|
+
function isoTimestamp() {
|
|
29808
|
+
return new Date().toISOString();
|
|
29809
|
+
}
|
|
29810
|
+
function installStderrTimestamps(env = process.env) {
|
|
29811
|
+
if (env.SWITCHROOM_LOG_TIMESTAMPS === "0")
|
|
29812
|
+
return false;
|
|
29813
|
+
if (installed)
|
|
29814
|
+
return true;
|
|
29815
|
+
const origin = process.stderr.write.bind(process.stderr);
|
|
29816
|
+
originalWrite = origin;
|
|
29817
|
+
const wrapped = function write(chunk, encodingOrCb, cb) {
|
|
29818
|
+
const text = typeof chunk === "string" ? chunk : Buffer.from(chunk).toString("utf8");
|
|
29819
|
+
const stamped = stampLines(text);
|
|
29820
|
+
return origin(stamped, encodingOrCb, cb);
|
|
29821
|
+
};
|
|
29822
|
+
process.stderr.write = wrapped;
|
|
29823
|
+
installed = true;
|
|
29824
|
+
return true;
|
|
29825
|
+
}
|
|
29826
|
+
function stampLines(text, now = isoTimestamp) {
|
|
29827
|
+
if (text === "")
|
|
29828
|
+
return "";
|
|
29829
|
+
let out = "";
|
|
29830
|
+
let i = 0;
|
|
29831
|
+
while (i < text.length) {
|
|
29832
|
+
const nl = text.indexOf(`
|
|
29833
|
+
`, i);
|
|
29834
|
+
if (nl === -1) {
|
|
29835
|
+
partialBuffer += text.slice(i);
|
|
29836
|
+
break;
|
|
29837
|
+
}
|
|
29838
|
+
const line = partialBuffer + text.slice(i, nl + 1);
|
|
29839
|
+
partialBuffer = "";
|
|
29840
|
+
out += `[${now()}] ${line}`;
|
|
29841
|
+
i = nl + 1;
|
|
29842
|
+
}
|
|
29843
|
+
return out;
|
|
29844
|
+
}
|
|
29845
|
+
|
|
29803
29846
|
// dm-command-gate.ts
|
|
29804
29847
|
function decideDmCommandGate(input) {
|
|
29805
29848
|
if (input.chatType !== "private")
|
|
@@ -47472,11 +47515,11 @@ function sweepStaleTurnActiveMarker(stateDir, opts) {
|
|
|
47472
47515
|
}
|
|
47473
47516
|
|
|
47474
47517
|
// ../src/build-info.ts
|
|
47475
|
-
var VERSION = "0.12.
|
|
47476
|
-
var COMMIT_SHA = "
|
|
47477
|
-
var COMMIT_DATE = "2026-05-
|
|
47478
|
-
var LATEST_PR =
|
|
47479
|
-
var COMMITS_AHEAD_OF_TAG =
|
|
47518
|
+
var VERSION = "0.12.28";
|
|
47519
|
+
var COMMIT_SHA = "61036e48";
|
|
47520
|
+
var COMMIT_DATE = "2026-05-20T14:43:34Z";
|
|
47521
|
+
var LATEST_PR = 1592;
|
|
47522
|
+
var COMMITS_AHEAD_OF_TAG = 4;
|
|
47480
47523
|
|
|
47481
47524
|
// gateway/boot-version.ts
|
|
47482
47525
|
function formatRelativeAgo(iso) {
|
|
@@ -47963,6 +48006,7 @@ function resolveCallingSubagent(opts) {
|
|
|
47963
48006
|
|
|
47964
48007
|
// gateway/gateway.ts
|
|
47965
48008
|
var REPLY_TO_TEXT_MAX = 200;
|
|
48009
|
+
installStderrTimestamps();
|
|
47966
48010
|
installPluginLogger();
|
|
47967
48011
|
installGlobalErrorHandlers();
|
|
47968
48012
|
process.on("beforeExit", () => {
|
|
@@ -23,6 +23,7 @@ import { homedir } from 'os'
|
|
|
23
23
|
import { join, extname, sep, basename } from 'path'
|
|
24
24
|
|
|
25
25
|
import { installPluginLogger } from '../plugin-logger.js'
|
|
26
|
+
import { installStderrTimestamps } from '../stderr-timestamps.js'
|
|
26
27
|
import { decideDmCommandGate } from '../dm-command-gate.js'
|
|
27
28
|
import { redactAuthCodeMessage } from '../auth-code-redact.js'
|
|
28
29
|
import {
|
|
@@ -380,6 +381,10 @@ import { formatIdleFooter } from '../idle-footer.js'
|
|
|
380
381
|
import { resolveCallingSubagent } from './resolve-calling-subagent.js'
|
|
381
382
|
|
|
382
383
|
// ─── Stderr logging ───────────────────────────────────────────────────────
|
|
384
|
+
// Install the line-stamper FIRST so it wraps closest to the original
|
|
385
|
+
// stderr.write. plugin-logger's file mirror then sees the timestamped text.
|
|
386
|
+
// Kill switch: SWITCHROOM_LOG_TIMESTAMPS=0 disables.
|
|
387
|
+
installStderrTimestamps()
|
|
383
388
|
installPluginLogger()
|
|
384
389
|
|
|
385
390
|
// ─── Telemetry ────────────────────────────────────────────────────────────
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-line timestamp wrapper for `process.stderr.write`.
|
|
3
|
+
*
|
|
4
|
+
* The gateway's stderr is captured to `/var/log/switchroom/gateway-supervisor.log`
|
|
5
|
+
* by `start.sh`'s `_switchroom_supervise` redirect. The capture has NO
|
|
6
|
+
* line-level timestamps, which makes it impossible to measure the gap between
|
|
7
|
+
* events (e.g., `bridge registered` → first `dispatch stage=bridge_recover` →
|
|
8
|
+
* first `tg-post method=sendMessage`). Without those gaps the cold-start TTFO
|
|
9
|
+
* RFC's optimization claims (PR #1589) are unverifiable.
|
|
10
|
+
*
|
|
11
|
+
* This module installs a one-time wrapper on `process.stderr.write` that
|
|
12
|
+
* prepends an ISO-8601 timestamp (`[YYYY-MM-DDTHH:MM:SS.mmmZ]`) at the start
|
|
13
|
+
* of each logical line. Line-buffered: partial writes that don't end in `\n`
|
|
14
|
+
* are buffered until they do. Newlines mid-chunk split the chunk into
|
|
15
|
+
* multiple timestamped lines.
|
|
16
|
+
*
|
|
17
|
+
* Layered separately from `plugin-logger.ts`'s file mirror so each can be
|
|
18
|
+
* toggled independently. Order at install time: this wrapper runs FIRST
|
|
19
|
+
* (closest to the original write), then plugin-logger's file mirror sees
|
|
20
|
+
* the timestamped text.
|
|
21
|
+
*
|
|
22
|
+
* Kill switch: `SWITCHROOM_LOG_TIMESTAMPS=0` disables. Default ON.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
let installed = false
|
|
26
|
+
let originalWrite: typeof process.stderr.write | null = null
|
|
27
|
+
let partialBuffer = ''
|
|
28
|
+
|
|
29
|
+
function isoTimestamp(): string {
|
|
30
|
+
return new Date().toISOString()
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Wrap `process.stderr.write` to prepend an ISO timestamp at each line
|
|
35
|
+
* boundary. Idempotent — second call is a no-op.
|
|
36
|
+
*
|
|
37
|
+
* Returns true when the wrapper was installed (or was already), false when
|
|
38
|
+
* the kill-switch env var disabled it.
|
|
39
|
+
*/
|
|
40
|
+
export function installStderrTimestamps(env: NodeJS.ProcessEnv = process.env): boolean {
|
|
41
|
+
if (env.SWITCHROOM_LOG_TIMESTAMPS === '0') return false
|
|
42
|
+
if (installed) return true
|
|
43
|
+
|
|
44
|
+
const origin = process.stderr.write.bind(process.stderr)
|
|
45
|
+
originalWrite = origin as typeof process.stderr.write
|
|
46
|
+
|
|
47
|
+
const wrapped = function write(
|
|
48
|
+
chunk: string | Uint8Array,
|
|
49
|
+
encodingOrCb?: BufferEncoding | ((err?: Error) => void),
|
|
50
|
+
cb?: (err?: Error) => void,
|
|
51
|
+
): boolean {
|
|
52
|
+
const text = typeof chunk === 'string' ? chunk : Buffer.from(chunk).toString('utf8')
|
|
53
|
+
const stamped = stampLines(text)
|
|
54
|
+
return (origin as (c: unknown, e?: unknown, cb?: unknown) => boolean)(
|
|
55
|
+
stamped,
|
|
56
|
+
encodingOrCb,
|
|
57
|
+
cb,
|
|
58
|
+
)
|
|
59
|
+
} as typeof process.stderr.write
|
|
60
|
+
|
|
61
|
+
process.stderr.write = wrapped
|
|
62
|
+
installed = true
|
|
63
|
+
return true
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Internal: split `text` into lines, prepend `[ISO] ` to each complete
|
|
68
|
+
* line, leave any trailing partial line buffered for the next call.
|
|
69
|
+
*
|
|
70
|
+
* Exported for tests only.
|
|
71
|
+
*/
|
|
72
|
+
export function stampLines(text: string, now: () => string = isoTimestamp): string {
|
|
73
|
+
if (text === '') return ''
|
|
74
|
+
|
|
75
|
+
let out = ''
|
|
76
|
+
let i = 0
|
|
77
|
+
while (i < text.length) {
|
|
78
|
+
const nl = text.indexOf('\n', i)
|
|
79
|
+
if (nl === -1) {
|
|
80
|
+
// No more newlines in this chunk — buffer the rest.
|
|
81
|
+
partialBuffer += text.slice(i)
|
|
82
|
+
break
|
|
83
|
+
}
|
|
84
|
+
// We have a complete line: anything in partialBuffer + slice up to \n.
|
|
85
|
+
const line = partialBuffer + text.slice(i, nl + 1)
|
|
86
|
+
partialBuffer = ''
|
|
87
|
+
out += `[${now()}] ${line}`
|
|
88
|
+
i = nl + 1
|
|
89
|
+
}
|
|
90
|
+
return out
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Test hook: reset module state. */
|
|
94
|
+
export function __resetForTests(): void {
|
|
95
|
+
if (installed && originalWrite) {
|
|
96
|
+
process.stderr.write = originalWrite
|
|
97
|
+
}
|
|
98
|
+
installed = false
|
|
99
|
+
originalWrite = null
|
|
100
|
+
partialBuffer = ''
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** Test hook: read the current partial buffer (debug-only). */
|
|
104
|
+
export function __getPartialBufferForTests(): string {
|
|
105
|
+
return partialBuffer
|
|
106
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for the line-buffered stderr timestamp wrapper.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, expect, it, beforeEach } from 'vitest'
|
|
6
|
+
import {
|
|
7
|
+
__resetForTests,
|
|
8
|
+
__getPartialBufferForTests,
|
|
9
|
+
installStderrTimestamps,
|
|
10
|
+
stampLines,
|
|
11
|
+
} from '../stderr-timestamps'
|
|
12
|
+
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
__resetForTests()
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
describe('stampLines (pure)', () => {
|
|
18
|
+
it('stamps a single complete line', () => {
|
|
19
|
+
const t = () => '2026-05-20T18:00:00.000Z'
|
|
20
|
+
expect(stampLines('hello world\n', t)).toBe('[2026-05-20T18:00:00.000Z] hello world\n')
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
it('returns empty for empty input', () => {
|
|
24
|
+
const t = () => '2026-05-20T18:00:00.000Z'
|
|
25
|
+
expect(stampLines('', t)).toBe('')
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
it('stamps multiple lines in one chunk', () => {
|
|
29
|
+
const t = () => 'T'
|
|
30
|
+
expect(stampLines('a\nb\nc\n', t)).toBe('[T] a\n[T] b\n[T] c\n')
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
it('buffers a partial line until the newline arrives', () => {
|
|
34
|
+
const t = () => 'T'
|
|
35
|
+
// Partial chunk: no newline → no output, content buffered.
|
|
36
|
+
expect(stampLines('partial', t)).toBe('')
|
|
37
|
+
expect(__getPartialBufferForTests()).toBe('partial')
|
|
38
|
+
// Newline finishes the buffered line.
|
|
39
|
+
expect(stampLines('-end\n', t)).toBe('[T] partial-end\n')
|
|
40
|
+
expect(__getPartialBufferForTests()).toBe('')
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
it('handles partial + complete in one chunk', () => {
|
|
44
|
+
const t = () => 'T'
|
|
45
|
+
expect(stampLines('line1\nstart-of-2', t)).toBe('[T] line1\n')
|
|
46
|
+
expect(__getPartialBufferForTests()).toBe('start-of-2')
|
|
47
|
+
expect(stampLines('-end\n', t)).toBe('[T] start-of-2-end\n')
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
it('handles chunk that ends exactly on a newline', () => {
|
|
51
|
+
const t = () => 'T'
|
|
52
|
+
expect(stampLines('exact\n', t)).toBe('[T] exact\n')
|
|
53
|
+
expect(__getPartialBufferForTests()).toBe('')
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
it('handles a multi-newline chunk with trailing partial', () => {
|
|
57
|
+
const t = () => 'T'
|
|
58
|
+
expect(stampLines('a\nb\nc', t)).toBe('[T] a\n[T] b\n')
|
|
59
|
+
expect(__getPartialBufferForTests()).toBe('c')
|
|
60
|
+
})
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
describe('installStderrTimestamps (integration)', () => {
|
|
64
|
+
it('kill switch SWITCHROOM_LOG_TIMESTAMPS=0 prevents install', () => {
|
|
65
|
+
const env: NodeJS.ProcessEnv = { SWITCHROOM_LOG_TIMESTAMPS: '0' }
|
|
66
|
+
const result = installStderrTimestamps(env)
|
|
67
|
+
expect(result).toBe(false)
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
it('default install returns true', () => {
|
|
71
|
+
const result = installStderrTimestamps({})
|
|
72
|
+
expect(result).toBe(true)
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
it('second install is a no-op (idempotent)', () => {
|
|
76
|
+
expect(installStderrTimestamps({})).toBe(true)
|
|
77
|
+
expect(installStderrTimestamps({})).toBe(true)
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
it('wrapped write emits ISO-timestamped lines', () => {
|
|
81
|
+
installStderrTimestamps({})
|
|
82
|
+
// Capture by replacing the write FUNCTION the wrapper forwards to.
|
|
83
|
+
// The wrapper calls the ORIGINAL bound `process.stderr.write`. We
|
|
84
|
+
// can validate end-to-end by writing and then reading back from a
|
|
85
|
+
// captured forward — but easier: pipe the wrapped output to a
|
|
86
|
+
// string by hooking process.stderr.write a second time.
|
|
87
|
+
const captured: string[] = []
|
|
88
|
+
const prev = process.stderr.write
|
|
89
|
+
process.stderr.write = ((chunk: string | Uint8Array) => {
|
|
90
|
+
const text = typeof chunk === 'string' ? chunk : Buffer.from(chunk).toString('utf8')
|
|
91
|
+
captured.push(text)
|
|
92
|
+
return true
|
|
93
|
+
}) as typeof process.stderr.write
|
|
94
|
+
try {
|
|
95
|
+
// The OUTER wrapper (installStderrTimestamps) was installed first,
|
|
96
|
+
// then we layered the capture on top. Calling process.stderr.write
|
|
97
|
+
// hits the CAPTURE, which means we'd capture the un-stamped text.
|
|
98
|
+
// Reverse: call the STAMPED wrapper directly by invoking through
|
|
99
|
+
// the installed function reference. Easiest path: just exercise
|
|
100
|
+
// stampLines (already pure-tested above) and rely on the install
|
|
101
|
+
// returning true to know we wired the wrapper.
|
|
102
|
+
//
|
|
103
|
+
// This test is best-effort end-to-end; the pure tests are the
|
|
104
|
+
// load-bearing contract.
|
|
105
|
+
process.stderr.write('test\n')
|
|
106
|
+
} finally {
|
|
107
|
+
process.stderr.write = prev
|
|
108
|
+
}
|
|
109
|
+
// The capture above is positioned ABOVE the stamper, so what it
|
|
110
|
+
// captures is what callers see. We at least verify it didn't crash.
|
|
111
|
+
expect(captured.length).toBeGreaterThan(0)
|
|
112
|
+
})
|
|
113
|
+
})
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "hindsight-memory",
|
|
3
|
+
"description": "Automatic long-term memory for Claude Code via Hindsight. Recalls relevant memories before each prompt and retains conversation transcripts after each response.",
|
|
4
|
+
"version": "0.4.0",
|
|
5
|
+
"author": {"name": "Hindsight Team", "url": "https://vectorize.io/hindsight"},
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"keywords": ["memory", "hindsight", "recall", "retain"]
|
|
8
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [Unreleased]
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- `{user_id}` template variable for `retainTags` and `retainMetadata`, resolved
|
|
8
|
+
from the `HINDSIGHT_USER_ID` env var (empty string if unset). Enables
|
|
9
|
+
machine-independent per-user memory scoping without hardcoding user ids in
|
|
10
|
+
`settings.json`.
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- Tags that resolve to an empty namespace content (e.g. `"user:"` when
|
|
15
|
+
`HINDSIGHT_USER_ID` is unset) are now dropped from retain requests. Previously
|
|
16
|
+
such tags were sent as-is. Tags without `:` are unaffected.
|
|
17
|
+
|
|
18
|
+
## [0.1.0] - 2025-03-23
|
|
19
|
+
|
|
20
|
+
### Added
|
|
21
|
+
- Initial release: Claude Code plugin for Hindsight long-term memory
|
|
22
|
+
- Auto-recall on every user prompt via `UserPromptSubmit` hook — injects relevant memories as `additionalContext`
|
|
23
|
+
- Auto-retain after every response via async `Stop` hook — extracts and stores conversation transcript
|
|
24
|
+
- Session lifecycle hooks (`SessionStart` health check, `SessionEnd` daemon cleanup)
|
|
25
|
+
- Three connection modes: external API, auto-managed local daemon (`uvx hindsight-embed`), existing local server
|
|
26
|
+
- Dynamic bank IDs with configurable granularity (`agent`, `project`, `session`, `channel`, `user`)
|
|
27
|
+
- Channel-agnostic: works with Claude Code Channels (Telegram, Discord, Slack) and interactive sessions
|
|
28
|
+
- Zero pip dependencies — pure Python stdlib (`urllib`, `fcntl`, `subprocess`)
|
|
29
|
+
- 34 configuration options via `settings.json` with env var overrides
|
|
30
|
+
- LLM auto-detection from `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, `GEMINI_API_KEY`, `GROQ_API_KEY`
|
|
31
|
+
- Chunked retention with sliding window (`retainEveryNTurns` + `retainOverlapTurns`)
|
|
32
|
+
- Memory tag stripping to prevent retain feedback loops
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Vectorize AI, Inc.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|