@yeaft/webchat-agent 0.1.767 → 0.1.769
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/package.json +1 -1
- package/unify/vp/seed-topup.js +92 -20
package/package.json
CHANGED
package/unify/vp/seed-topup.js
CHANGED
|
@@ -142,30 +142,38 @@ function listExistingVpIds(libDir) {
|
|
|
142
142
|
}
|
|
143
143
|
|
|
144
144
|
/**
|
|
145
|
-
* Splice a single
|
|
145
|
+
* Splice a single `<key>: <value>` line into an existing role.md's YAML
|
|
146
146
|
* frontmatter, immediately after the `role:` line if one is present (else
|
|
147
147
|
* before the closing `---`). All other bytes are preserved.
|
|
148
148
|
*
|
|
149
|
-
* Returns `null` if the file already has
|
|
150
|
-
*
|
|
151
|
-
* touch this file."
|
|
149
|
+
* Returns `null` if the file already has the key, has no frontmatter, or
|
|
150
|
+
* any other parse anomaly — caller treats null as "don't touch this file."
|
|
152
151
|
*
|
|
153
152
|
* @param {string} source
|
|
154
|
-
* @param {string}
|
|
153
|
+
* @param {string} key YAML key to insert (e.g. 'area', 'nameZh')
|
|
154
|
+
* @param {string} value Scalar value (will be YAML-escaped only if it
|
|
155
|
+
* contains characters that need quoting)
|
|
155
156
|
* @returns {string|null}
|
|
156
157
|
*/
|
|
157
|
-
|
|
158
|
-
const
|
|
159
|
-
if (!
|
|
158
|
+
function insertFrontmatterLine(source, key, value) {
|
|
159
|
+
const valTrim = String(value || '').trim();
|
|
160
|
+
if (!valTrim) return null;
|
|
160
161
|
const fmMatch = source.match(/^(---\r?\n)([\s\S]*?)(\r?\n---\r?\n?)/);
|
|
161
162
|
if (!fmMatch) return null;
|
|
162
163
|
const [full, open, yaml, close] = fmMatch;
|
|
163
|
-
|
|
164
|
-
|
|
164
|
+
// Anchored at start-of-line so `nameZh` is never mistaken for `name`.
|
|
165
|
+
const keyPattern = new RegExp(`^${key}:\\s*`, 'm');
|
|
166
|
+
if (keyPattern.test(yaml)) return null; // user already set this key
|
|
165
167
|
const nl = yaml.includes('\r\n') ? '\r\n' : '\n';
|
|
166
168
|
const lines = yaml.split(/\r?\n/);
|
|
167
169
|
const roleIdx = lines.findIndex(l => /^role:\s*/.test(l));
|
|
168
|
-
|
|
170
|
+
// Quote the value when it contains characters YAML treats specially or
|
|
171
|
+
// non-ASCII bytes — the existing vp-crud writer does the same dance.
|
|
172
|
+
const needsQuote = /[:#"'\\\n]/.test(valTrim) || /[^\x20-\x7e]/.test(valTrim);
|
|
173
|
+
const yamlVal = needsQuote
|
|
174
|
+
? `"${valTrim.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`
|
|
175
|
+
: valTrim;
|
|
176
|
+
const newLine = `${key}: ${yamlVal}`;
|
|
169
177
|
if (roleIdx >= 0) {
|
|
170
178
|
lines.splice(roleIdx + 1, 0, newLine);
|
|
171
179
|
} else {
|
|
@@ -176,6 +184,30 @@ export function insertAreaLine(source, bucket) {
|
|
|
176
184
|
return open + newYaml + close + rest;
|
|
177
185
|
}
|
|
178
186
|
|
|
187
|
+
/**
|
|
188
|
+
* Backward-compatible wrapper kept for tests that pin the `area` shape.
|
|
189
|
+
* Equivalent to `insertFrontmatterLine(source, 'area', bucket)`.
|
|
190
|
+
*
|
|
191
|
+
* @param {string} source
|
|
192
|
+
* @param {string} bucket
|
|
193
|
+
* @returns {string|null}
|
|
194
|
+
*/
|
|
195
|
+
export function insertAreaLine(source, bucket) {
|
|
196
|
+
return insertFrontmatterLine(source, 'area', bucket);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* v0.1.768 — backfill `nameZh:` into a role.md frontmatter that predates
|
|
201
|
+
* the bilingual seed. Same byte-preserving semantics as insertAreaLine.
|
|
202
|
+
*
|
|
203
|
+
* @param {string} source
|
|
204
|
+
* @param {string} nameZh
|
|
205
|
+
* @returns {string|null}
|
|
206
|
+
*/
|
|
207
|
+
export function insertNameZhLine(source, nameZh) {
|
|
208
|
+
return insertFrontmatterLine(source, 'nameZh', nameZh);
|
|
209
|
+
}
|
|
210
|
+
|
|
179
211
|
/**
|
|
180
212
|
* Top-up the default VPs into an existing `libDir`.
|
|
181
213
|
*
|
|
@@ -183,6 +215,7 @@ export function insertAreaLine(source, bucket) {
|
|
|
183
215
|
* @returns {{
|
|
184
216
|
* added: string[],
|
|
185
217
|
* areaBackfilled: string[],
|
|
218
|
+
* nameZhBackfilled: string[],
|
|
186
219
|
* respectedDeletes: string[],
|
|
187
220
|
* skippedExisting: string[],
|
|
188
221
|
* errors: Array<{vpId:string, code:string, message:string}>,
|
|
@@ -191,6 +224,7 @@ export function insertAreaLine(source, bucket) {
|
|
|
191
224
|
export function topUpDefaultVps(libDir = DEFAULT_VP_LIB_DIR) {
|
|
192
225
|
const added = [];
|
|
193
226
|
const areaBackfilled = [];
|
|
227
|
+
const nameZhBackfilled = [];
|
|
194
228
|
const respectedDeletes = [];
|
|
195
229
|
const skippedExisting = [];
|
|
196
230
|
/** @type {Array<{vpId:string,code:string,message:string}>} */
|
|
@@ -200,7 +234,7 @@ export function topUpDefaultVps(libDir = DEFAULT_VP_LIB_DIR) {
|
|
|
200
234
|
// that case there's nothing to top up — seedDefaultVps will populate
|
|
201
235
|
// everything. We still return cleanly.
|
|
202
236
|
if (!existsSync(libDir)) {
|
|
203
|
-
return { added, areaBackfilled, respectedDeletes, skippedExisting, errors };
|
|
237
|
+
return { added, areaBackfilled, nameZhBackfilled, respectedDeletes, skippedExisting, errors };
|
|
204
238
|
}
|
|
205
239
|
|
|
206
240
|
let versions = readSeedVersions(libDir);
|
|
@@ -222,16 +256,28 @@ export function topUpDefaultVps(libDir = DEFAULT_VP_LIB_DIR) {
|
|
|
222
256
|
const inLedger = Object.prototype.hasOwnProperty.call(versions.seeded, vpId);
|
|
223
257
|
|
|
224
258
|
if (onDisk) {
|
|
225
|
-
// Already there — never overwrite. Possibly backfill `area
|
|
259
|
+
// Already there — never overwrite. Possibly backfill `area` /
|
|
260
|
+
// `nameZh`. Each backfill is independent: a role.md with `area`
|
|
261
|
+
// already set but no `nameZh` should still get `nameZh`.
|
|
226
262
|
skippedExisting.push(vpId);
|
|
263
|
+
let currentSrc = null;
|
|
264
|
+
const rolePath = join(libDir, vpId, 'role.md');
|
|
265
|
+
const readRole = () => {
|
|
266
|
+
if (currentSrc == null) {
|
|
267
|
+
try { currentSrc = readFileSync(rolePath, 'utf-8'); } catch { currentSrc = null; }
|
|
268
|
+
}
|
|
269
|
+
return currentSrc;
|
|
270
|
+
};
|
|
227
271
|
if (vp.area) {
|
|
228
272
|
try {
|
|
229
|
-
const
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
273
|
+
const src = readRole();
|
|
274
|
+
if (src != null) {
|
|
275
|
+
const patched = insertAreaLine(src, vp.area);
|
|
276
|
+
if (patched != null && patched !== src) {
|
|
277
|
+
writeFileSync(rolePath, patched, 'utf-8');
|
|
278
|
+
currentSrc = patched;
|
|
279
|
+
areaBackfilled.push(vpId);
|
|
280
|
+
}
|
|
235
281
|
}
|
|
236
282
|
} catch (err) {
|
|
237
283
|
errors.push({
|
|
@@ -241,6 +287,32 @@ export function topUpDefaultVps(libDir = DEFAULT_VP_LIB_DIR) {
|
|
|
241
287
|
});
|
|
242
288
|
}
|
|
243
289
|
}
|
|
290
|
+
// v0.1.768 — bilingual top-up. Existing VPs created from a
|
|
291
|
+
// pre-bilingual seed lack `nameZh:` in their role.md, which means
|
|
292
|
+
// serializeVpForWire ships `displayNameZh: ''` and the web
|
|
293
|
+
// `vpLabel` falls through to English even under zh locale. Splice
|
|
294
|
+
// the canonical Chinese display name into frontmatter without
|
|
295
|
+
// touching anything else — the user's persona body stays
|
|
296
|
+
// byte-identical.
|
|
297
|
+
if (vp.displayNameZh) {
|
|
298
|
+
try {
|
|
299
|
+
const src = readRole();
|
|
300
|
+
if (src != null) {
|
|
301
|
+
const patched = insertNameZhLine(src, vp.displayNameZh);
|
|
302
|
+
if (patched != null && patched !== src) {
|
|
303
|
+
writeFileSync(rolePath, patched, 'utf-8');
|
|
304
|
+
currentSrc = patched;
|
|
305
|
+
nameZhBackfilled.push(vpId);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
} catch (err) {
|
|
309
|
+
errors.push({
|
|
310
|
+
vpId,
|
|
311
|
+
code: 'name_zh_backfill_failed',
|
|
312
|
+
message: String(err?.message || err),
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
}
|
|
244
316
|
// Make sure the ledger records it (e.g. user-authored VP whose id
|
|
245
317
|
// collides with a default — we still want to skip future creates).
|
|
246
318
|
if (!inLedger) versions.seeded[vpId] = 'legacy';
|
|
@@ -274,5 +346,5 @@ export function topUpDefaultVps(libDir = DEFAULT_VP_LIB_DIR) {
|
|
|
274
346
|
}
|
|
275
347
|
|
|
276
348
|
writeSeedVersions(libDir, versions);
|
|
277
|
-
return { added, areaBackfilled, respectedDeletes, skippedExisting, errors };
|
|
349
|
+
return { added, areaBackfilled, nameZhBackfilled, respectedDeletes, skippedExisting, errors };
|
|
278
350
|
}
|