instar 0.28.82 → 0.28.83
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/server/routes.d.ts.map +1 -1
- package/dist/server/routes.js +124 -3
- package/dist/server/routes.js.map +1 -1
- package/dist/threadline/ThreadlineNicknames.d.ts +88 -0
- package/dist/threadline/ThreadlineNicknames.d.ts.map +1 -0
- package/dist/threadline/ThreadlineNicknames.js +195 -0
- package/dist/threadline/ThreadlineNicknames.js.map +1 -0
- package/package.json +1 -1
- package/src/data/builtin-manifest.json +46 -46
- package/upgrades/0.28.83.md +45 -0
- package/upgrades/side-effects/threadline-nickname-resolver-authority.md +81 -0
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ThreadlineNicknames — user-editable display names for threadline agents,
|
|
3
|
+
* keyed by fingerprint (or any stable id).
|
|
4
|
+
*
|
|
5
|
+
* Storage: .instar/threadline/nicknames.json
|
|
6
|
+
* {
|
|
7
|
+
* "version": 1,
|
|
8
|
+
* "nicknames": {
|
|
9
|
+
* "8c7928aa9f04fbda...": {
|
|
10
|
+
* "nickname": "Dawn",
|
|
11
|
+
* "source": "user" | "haiku" | "import",
|
|
12
|
+
* "updatedAt": "2026-05-06T17:00:00.000Z"
|
|
13
|
+
* }
|
|
14
|
+
* }
|
|
15
|
+
* }
|
|
16
|
+
*/
|
|
17
|
+
export type NicknameSource = 'user' | 'haiku' | 'import';
|
|
18
|
+
export interface NicknameEntry {
|
|
19
|
+
nickname: string;
|
|
20
|
+
source: NicknameSource;
|
|
21
|
+
updatedAt: string;
|
|
22
|
+
}
|
|
23
|
+
export interface ThreadlineNicknamesOptions {
|
|
24
|
+
stateDir: string;
|
|
25
|
+
}
|
|
26
|
+
export declare class ThreadlineNicknames {
|
|
27
|
+
private readonly stateDir;
|
|
28
|
+
private cache;
|
|
29
|
+
private cacheReadAt;
|
|
30
|
+
private static readonly CACHE_TTL_MS;
|
|
31
|
+
constructor(opts: ThreadlineNicknamesOptions);
|
|
32
|
+
/** Path to the nicknames JSON file. */
|
|
33
|
+
filePath(): string;
|
|
34
|
+
/** Returns the nickname for a fingerprint, or null if none set. */
|
|
35
|
+
get(fingerprint: string): NicknameEntry | null;
|
|
36
|
+
/** Returns all nicknames as a plain map. */
|
|
37
|
+
all(): Record<string, NicknameEntry>;
|
|
38
|
+
/**
|
|
39
|
+
* Canonicalize a name for authority-grade comparison.
|
|
40
|
+
*
|
|
41
|
+
* Applies Unicode NFC normalization, trims surrounding whitespace,
|
|
42
|
+
* collapses internal whitespace runs to a single space, and lowercases.
|
|
43
|
+
* This is what makes two visually-identical names compare equal even
|
|
44
|
+
* when one was hand-edited with stray whitespace, mixed case, or
|
|
45
|
+
* combining-character forms.
|
|
46
|
+
*
|
|
47
|
+
* Used by `resolveByName()` on BOTH the lookup key AND each stored
|
|
48
|
+
* entry's nickname at compare time — so we never mutate the user's
|
|
49
|
+
* chosen display string on disk (preserving their preferred casing
|
|
50
|
+
* and spacing for the dashboard), but lookups still match across
|
|
51
|
+
* cosmetic differences. `set()` does NOT pre-canonicalize on store
|
|
52
|
+
* for the same reason: the stored string is user-presentation; the
|
|
53
|
+
* canonical form is comparison-only.
|
|
54
|
+
*/
|
|
55
|
+
static canonicalizeName(name: string): string;
|
|
56
|
+
/**
|
|
57
|
+
* Reverse-lookup: nickname → fingerprint. Canonicalized exact match
|
|
58
|
+
* (NFC-normalized, internal-whitespace-collapsed, case-insensitive).
|
|
59
|
+
*
|
|
60
|
+
* Returns null if no nickname matches. If multiple fingerprints share
|
|
61
|
+
* the same nickname (rare; user shouldn't do this but the file allows it),
|
|
62
|
+
* returns { ambiguous: true, candidates: [...] } so callers can fail
|
|
63
|
+
* loudly instead of silently picking one.
|
|
64
|
+
*
|
|
65
|
+
* The send-path resolver consults this BEFORE relay discovery so that
|
|
66
|
+
* user-curated names take authority over potentially-stale or imposter
|
|
67
|
+
* presence entries. See routes.ts /threadline/relay-send.
|
|
68
|
+
*/
|
|
69
|
+
resolveByName(name: string): {
|
|
70
|
+
fingerprint: string;
|
|
71
|
+
entry: NicknameEntry;
|
|
72
|
+
} | {
|
|
73
|
+
ambiguous: true;
|
|
74
|
+
candidates: Array<{
|
|
75
|
+
fingerprint: string;
|
|
76
|
+
entry: NicknameEntry;
|
|
77
|
+
}>;
|
|
78
|
+
} | null;
|
|
79
|
+
/** Set a nickname for a fingerprint. Empty/whitespace nickname clears it. */
|
|
80
|
+
set(fingerprint: string, nickname: string, source?: NicknameSource): NicknameEntry | null;
|
|
81
|
+
/** Delete a nickname mapping. Returns true if one existed. */
|
|
82
|
+
delete(fingerprint: string): boolean;
|
|
83
|
+
/** Force-reload on next get(). */
|
|
84
|
+
invalidate(): void;
|
|
85
|
+
private load;
|
|
86
|
+
private persist;
|
|
87
|
+
}
|
|
88
|
+
//# sourceMappingURL=ThreadlineNicknames.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ThreadlineNicknames.d.ts","sourceRoot":"","sources":["../../src/threadline/ThreadlineNicknames.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAKH,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,OAAO,GAAG,QAAQ,CAAC;AAEzD,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,cAAc,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC;CACnB;AAOD,MAAM,WAAW,0BAA0B;IACzC,QAAQ,EAAE,MAAM,CAAC;CAClB;AAID,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,KAAK,CAA2C;IACxD,OAAO,CAAC,WAAW,CAAK;IACxB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,YAAY,CAAU;gBAElC,IAAI,EAAE,0BAA0B;IAI5C,uCAAuC;IACvC,QAAQ,IAAI,MAAM;IAIlB,mEAAmE;IACnE,GAAG,CAAC,WAAW,EAAE,MAAM,GAAG,aAAa,GAAG,IAAI;IAM9C,4CAA4C;IAC5C,GAAG,IAAI,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC;IAOpC;;;;;;;;;;;;;;;;OAgBG;IACH,MAAM,CAAC,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;IAS7C;;;;;;;;;;;;OAYG;IACH,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,aAAa,CAAA;KAAE,GAAG;QAAE,SAAS,EAAE,IAAI,CAAC;QAAC,UAAU,EAAE,KAAK,CAAC;YAAE,WAAW,EAAE,MAAM,CAAC;YAAC,KAAK,EAAE,aAAa,CAAA;SAAE,CAAC,CAAA;KAAE,GAAG,IAAI;IAezK,6EAA6E;IAC7E,GAAG,CAAC,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,GAAE,cAAuB,GAAG,aAAa,GAAG,IAAI;IAsBjG,8DAA8D;IAC9D,MAAM,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO;IAQpC,kCAAkC;IAClC,UAAU,IAAI,IAAI;IAOlB,OAAO,CAAC,IAAI;IAqCZ,OAAO,CAAC,OAAO;CAiBhB"}
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ThreadlineNicknames — user-editable display names for threadline agents,
|
|
3
|
+
* keyed by fingerprint (or any stable id).
|
|
4
|
+
*
|
|
5
|
+
* Storage: .instar/threadline/nicknames.json
|
|
6
|
+
* {
|
|
7
|
+
* "version": 1,
|
|
8
|
+
* "nicknames": {
|
|
9
|
+
* "8c7928aa9f04fbda...": {
|
|
10
|
+
* "nickname": "Dawn",
|
|
11
|
+
* "source": "user" | "haiku" | "import",
|
|
12
|
+
* "updatedAt": "2026-05-06T17:00:00.000Z"
|
|
13
|
+
* }
|
|
14
|
+
* }
|
|
15
|
+
* }
|
|
16
|
+
*/
|
|
17
|
+
import fs from 'node:fs';
|
|
18
|
+
import path from 'node:path';
|
|
19
|
+
const FILE_VERSION = 1;
|
|
20
|
+
export class ThreadlineNicknames {
|
|
21
|
+
stateDir;
|
|
22
|
+
cache = null;
|
|
23
|
+
cacheReadAt = 0;
|
|
24
|
+
static CACHE_TTL_MS = 30_000;
|
|
25
|
+
constructor(opts) {
|
|
26
|
+
this.stateDir = opts.stateDir;
|
|
27
|
+
}
|
|
28
|
+
/** Path to the nicknames JSON file. */
|
|
29
|
+
filePath() {
|
|
30
|
+
return path.join(this.stateDir, 'threadline', 'nicknames.json');
|
|
31
|
+
}
|
|
32
|
+
/** Returns the nickname for a fingerprint, or null if none set. */
|
|
33
|
+
get(fingerprint) {
|
|
34
|
+
if (!fingerprint)
|
|
35
|
+
return null;
|
|
36
|
+
const map = this.load();
|
|
37
|
+
return map.get(fingerprint) ?? null;
|
|
38
|
+
}
|
|
39
|
+
/** Returns all nicknames as a plain map. */
|
|
40
|
+
all() {
|
|
41
|
+
const map = this.load();
|
|
42
|
+
const out = {};
|
|
43
|
+
for (const [k, v] of map)
|
|
44
|
+
out[k] = v;
|
|
45
|
+
return out;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Canonicalize a name for authority-grade comparison.
|
|
49
|
+
*
|
|
50
|
+
* Applies Unicode NFC normalization, trims surrounding whitespace,
|
|
51
|
+
* collapses internal whitespace runs to a single space, and lowercases.
|
|
52
|
+
* This is what makes two visually-identical names compare equal even
|
|
53
|
+
* when one was hand-edited with stray whitespace, mixed case, or
|
|
54
|
+
* combining-character forms.
|
|
55
|
+
*
|
|
56
|
+
* Used by `resolveByName()` on BOTH the lookup key AND each stored
|
|
57
|
+
* entry's nickname at compare time — so we never mutate the user's
|
|
58
|
+
* chosen display string on disk (preserving their preferred casing
|
|
59
|
+
* and spacing for the dashboard), but lookups still match across
|
|
60
|
+
* cosmetic differences. `set()` does NOT pre-canonicalize on store
|
|
61
|
+
* for the same reason: the stored string is user-presentation; the
|
|
62
|
+
* canonical form is comparison-only.
|
|
63
|
+
*/
|
|
64
|
+
static canonicalizeName(name) {
|
|
65
|
+
if (!name)
|
|
66
|
+
return '';
|
|
67
|
+
return name
|
|
68
|
+
.normalize('NFC')
|
|
69
|
+
.trim()
|
|
70
|
+
.replace(/\s+/g, ' ')
|
|
71
|
+
.toLowerCase();
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Reverse-lookup: nickname → fingerprint. Canonicalized exact match
|
|
75
|
+
* (NFC-normalized, internal-whitespace-collapsed, case-insensitive).
|
|
76
|
+
*
|
|
77
|
+
* Returns null if no nickname matches. If multiple fingerprints share
|
|
78
|
+
* the same nickname (rare; user shouldn't do this but the file allows it),
|
|
79
|
+
* returns { ambiguous: true, candidates: [...] } so callers can fail
|
|
80
|
+
* loudly instead of silently picking one.
|
|
81
|
+
*
|
|
82
|
+
* The send-path resolver consults this BEFORE relay discovery so that
|
|
83
|
+
* user-curated names take authority over potentially-stale or imposter
|
|
84
|
+
* presence entries. See routes.ts /threadline/relay-send.
|
|
85
|
+
*/
|
|
86
|
+
resolveByName(name) {
|
|
87
|
+
const canonical = ThreadlineNicknames.canonicalizeName(name);
|
|
88
|
+
if (!canonical)
|
|
89
|
+
return null;
|
|
90
|
+
const map = this.load();
|
|
91
|
+
const matches = [];
|
|
92
|
+
for (const [fp, entry] of map) {
|
|
93
|
+
if (ThreadlineNicknames.canonicalizeName(entry.nickname) === canonical) {
|
|
94
|
+
matches.push({ fingerprint: fp, entry });
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
if (matches.length === 0)
|
|
98
|
+
return null;
|
|
99
|
+
if (matches.length === 1)
|
|
100
|
+
return matches[0];
|
|
101
|
+
return { ambiguous: true, candidates: matches };
|
|
102
|
+
}
|
|
103
|
+
/** Set a nickname for a fingerprint. Empty/whitespace nickname clears it. */
|
|
104
|
+
set(fingerprint, nickname, source = 'user') {
|
|
105
|
+
if (!fingerprint)
|
|
106
|
+
throw new Error('fingerprint required');
|
|
107
|
+
const trimmed = nickname.trim();
|
|
108
|
+
const map = this.load();
|
|
109
|
+
if (trimmed.length === 0) {
|
|
110
|
+
map.delete(fingerprint);
|
|
111
|
+
this.persist(map);
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
if (trimmed.length > 64) {
|
|
115
|
+
throw new Error('nickname too long (max 64 chars)');
|
|
116
|
+
}
|
|
117
|
+
const entry = {
|
|
118
|
+
nickname: trimmed,
|
|
119
|
+
source,
|
|
120
|
+
updatedAt: new Date().toISOString(),
|
|
121
|
+
};
|
|
122
|
+
map.set(fingerprint, entry);
|
|
123
|
+
this.persist(map);
|
|
124
|
+
return entry;
|
|
125
|
+
}
|
|
126
|
+
/** Delete a nickname mapping. Returns true if one existed. */
|
|
127
|
+
delete(fingerprint) {
|
|
128
|
+
const map = this.load();
|
|
129
|
+
if (!map.has(fingerprint))
|
|
130
|
+
return false;
|
|
131
|
+
map.delete(fingerprint);
|
|
132
|
+
this.persist(map);
|
|
133
|
+
return true;
|
|
134
|
+
}
|
|
135
|
+
/** Force-reload on next get(). */
|
|
136
|
+
invalidate() {
|
|
137
|
+
this.cache = null;
|
|
138
|
+
this.cacheReadAt = 0;
|
|
139
|
+
}
|
|
140
|
+
// ── internal ───────────────────────────────────────────────────
|
|
141
|
+
load() {
|
|
142
|
+
const fresh = Date.now() - this.cacheReadAt < ThreadlineNicknames.CACHE_TTL_MS;
|
|
143
|
+
if (this.cache && fresh)
|
|
144
|
+
return this.cache;
|
|
145
|
+
const map = new Map();
|
|
146
|
+
const file = this.filePath();
|
|
147
|
+
if (fs.existsSync(file)) {
|
|
148
|
+
try {
|
|
149
|
+
const raw = fs.readFileSync(file, 'utf-8');
|
|
150
|
+
const parsed = JSON.parse(raw);
|
|
151
|
+
const items = parsed?.nicknames ?? {};
|
|
152
|
+
for (const [k, v] of Object.entries(items)) {
|
|
153
|
+
if (v && typeof v.nickname === 'string') {
|
|
154
|
+
map.set(k, {
|
|
155
|
+
nickname: v.nickname,
|
|
156
|
+
source: v.source ?? 'user',
|
|
157
|
+
updatedAt: v.updatedAt ?? new Date().toISOString(),
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
catch (err) {
|
|
163
|
+
// Corrupt file — degrade to empty map (sends still flow via
|
|
164
|
+
// relay-discovery fallback) but make the silent authority loss
|
|
165
|
+
// visible. One log per cache cycle (every 30s at most), so a
|
|
166
|
+
// persistently-corrupt file doesn't spam logs but is observable.
|
|
167
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
168
|
+
console.warn(`[ThreadlineNicknames] nicknames.json parse failed at ${file}: ${msg}. ` +
|
|
169
|
+
`Treating as empty (no user-curated authority for this load cycle). ` +
|
|
170
|
+
`Outbound sends will fall back to relay-discovery for nicknamed names.`);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
this.cache = map;
|
|
174
|
+
this.cacheReadAt = Date.now();
|
|
175
|
+
return map;
|
|
176
|
+
}
|
|
177
|
+
persist(map) {
|
|
178
|
+
const file = this.filePath();
|
|
179
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
180
|
+
const obj = {
|
|
181
|
+
version: FILE_VERSION,
|
|
182
|
+
nicknames: Object.fromEntries(map),
|
|
183
|
+
};
|
|
184
|
+
// Atomic write: temp + rename. Prevents a concurrent reader from
|
|
185
|
+
// observing a half-written file (which would parse-fail and degrade
|
|
186
|
+
// to "no nicknames" — silent authority loss). rename(2) is atomic on
|
|
187
|
+
// POSIX when source and destination live on the same filesystem.
|
|
188
|
+
const tmp = `${file}.tmp-${process.pid}-${Date.now()}`;
|
|
189
|
+
fs.writeFileSync(tmp, JSON.stringify(obj, null, 2), 'utf-8');
|
|
190
|
+
fs.renameSync(tmp, file);
|
|
191
|
+
this.cache = map;
|
|
192
|
+
this.cacheReadAt = Date.now();
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
//# sourceMappingURL=ThreadlineNicknames.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ThreadlineNicknames.js","sourceRoot":"","sources":["../../src/threadline/ThreadlineNicknames.ts"],"names":[],"mappings":"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"}
|