dsh-ntr 0.1.0 → 0.1.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.
- package/lib/client/acp-view.js +5 -4
- package/lib/client/index.js +30 -3
- package/lib/client/store.js +22 -1
- package/lib/client.js +62 -8
- package/lib/client.js.map +1 -1
- package/lib/types/client/store.d.ts +16 -0
- package/locale/en.json +5 -0
- package/locale/zh.json +5 -0
- package/package.json +4 -2
- package/src/client/acp-view.tsx +6 -5
- package/src/client/index.tsx +44 -3
- package/src/client/store.ts +33 -4
package/lib/client/acp-view.js
CHANGED
|
@@ -179,9 +179,10 @@ function PlanCard({ entries, t }) {
|
|
|
179
179
|
return (_jsxs("div", { className: "ntr-acp-plan", children: [_jsx("div", { className: "ntr-acp-plan-head", children: t(`计划 ${done}/${entries.length}`, `Plan ${done}/${entries.length}`) }), entries.map((entry, index) => (_jsxs("div", { className: `ntr-acp-plan-row is-${entry.status}`, children: [_jsx("span", { className: "ntr-acp-plan-glyph", "aria-hidden": true, children: entry.status === 'completed' ? '✓' : entry.status === 'in_progress' ? '▸' : '○' }), _jsx("span", { children: entry.content }), entry.priority !== undefined && _jsx("span", { className: "ntr-acp-plan-prio", children: entry.priority })] }, index)))] }));
|
|
180
180
|
}
|
|
181
181
|
/**
|
|
182
|
-
* One stream block, dispatched by kind. Memoized on its props — `block
|
|
183
|
-
*
|
|
184
|
-
*
|
|
182
|
+
* One stream block, dispatched by kind. Memoized on its props — `block`, the
|
|
183
|
+
* store's instance-bound `label`, and `localeRev` (it moves on a language
|
|
184
|
+
* switch, so translated copy re-renders) — so a folded batch re-renders only
|
|
185
|
+
* the rows whose block object changed (`foldUpdate` replaces blocks immutably)
|
|
185
186
|
* instead of the whole transcript on every store notification. `key` stays the
|
|
186
187
|
* index on purpose: blocks fold append-only, and identity is the block object.
|
|
187
188
|
*/
|
|
@@ -327,5 +328,5 @@ export function AcpConversationView(props) {
|
|
|
327
328
|
pinned.current = node.scrollHeight - node.scrollTop - node.clientHeight < 40;
|
|
328
329
|
}, children: transcript.blocks.length === 0
|
|
329
330
|
? _jsx("div", { className: "ntr-acp-empty-hint", children: t('还没有消息:在下面的输入框里发给它。', 'Nothing yet — send it a message below.') })
|
|
330
|
-
: transcript.blocks.map((block, index) => _jsx(BlockView, { block: block, label: acp.label }, index)) })] }));
|
|
331
|
+
: transcript.blocks.map((block, index) => _jsx(BlockView, { block: block, label: acp.label, localeRev: ui.localeRev }, index)) })] }));
|
|
331
332
|
}
|
package/lib/client/index.js
CHANGED
|
@@ -15,7 +15,7 @@ import { acpModeFromPresetEvent, rowAgentPreset, stagedIsAcpMode, stagedSessionO
|
|
|
15
15
|
import { createSlashIsolation, slashAcpVerdict } from "./slash-isolation.js";
|
|
16
16
|
export const name = 'dsh-ntr-client';
|
|
17
17
|
export const inject = ['slots', 'connection'];
|
|
18
|
-
const
|
|
18
|
+
const browserPrefersZh = typeof navigator !== 'undefined' && /^zh\b/i.test(navigator.language);
|
|
19
19
|
/**
|
|
20
20
|
* How long a host verdict may stand as the gate's ONLY fact source before the
|
|
21
21
|
* gate asks the host again.
|
|
@@ -50,8 +50,6 @@ export function apply(ctx) {
|
|
|
50
50
|
throw new Error('dsh-ntr: browser connection.rpc is unavailable');
|
|
51
51
|
}
|
|
52
52
|
const call = (channel, endpoint, payload) => rpc.call(channel, endpoint, payload);
|
|
53
|
-
const store = new AcpStore(call, (zhText, enText) => (zh ? zhText : enText));
|
|
54
|
-
store.start();
|
|
55
53
|
/**
|
|
56
54
|
* Resolve one optional service without injecting it. A plain `ctx.get` at
|
|
57
55
|
* call time keeps the plugin loadable when the service is absent AND picks it
|
|
@@ -59,6 +57,35 @@ export function apply(ctx) {
|
|
|
59
57
|
* also making its absence fatal.
|
|
60
58
|
*/
|
|
61
59
|
const service = (name) => ctx.get(name);
|
|
60
|
+
/**
|
|
61
|
+
* Resolve a Chinese/English pair against the shell's active locale. Read per
|
|
62
|
+
* call, so a language switch reaches every render-time `label()` call without
|
|
63
|
+
* rebuilding the store. A host without the locale service keeps the
|
|
64
|
+
* browser-derived choice the pairs shipped with.
|
|
65
|
+
*/
|
|
66
|
+
const label = (zhText, enText) => {
|
|
67
|
+
const resolved = service('locale')?.resolveText?.({ zh: zhText, en: enText });
|
|
68
|
+
return typeof resolved === 'string' && resolved !== '' ? resolved : browserPrefersZh ? zhText : enText;
|
|
69
|
+
};
|
|
70
|
+
const store = new AcpStore(call, label);
|
|
71
|
+
store.start();
|
|
72
|
+
// A locale switch changes no data, only which side of each pair renders;
|
|
73
|
+
// republishing the snapshot is what re-runs them. Attached through
|
|
74
|
+
// `internal/service` so a late-arriving locale service is still hooked.
|
|
75
|
+
ctx.effect(() => {
|
|
76
|
+
let stop;
|
|
77
|
+
const attach = () => {
|
|
78
|
+
if (stop !== undefined)
|
|
79
|
+
return;
|
|
80
|
+
stop = service('locale')?.subscribe?.(() => { store.refreshLocale(); });
|
|
81
|
+
};
|
|
82
|
+
attach();
|
|
83
|
+
const stopService = ctx.on('internal/service', attach);
|
|
84
|
+
return () => {
|
|
85
|
+
stopService();
|
|
86
|
+
stop?.();
|
|
87
|
+
};
|
|
88
|
+
}, 'dsh-ntr: locale switch re-render');
|
|
62
89
|
/** This session's command/projection face (`ctx.sessions`); `undefined` before it materializes. */
|
|
63
90
|
const faceFor = (sessionId) => {
|
|
64
91
|
try {
|
package/lib/client/store.js
CHANGED
|
@@ -284,6 +284,7 @@ export class AcpStore {
|
|
|
284
284
|
locale;
|
|
285
285
|
state = {
|
|
286
286
|
open: false,
|
|
287
|
+
localeRev: 0,
|
|
287
288
|
stream: 'init',
|
|
288
289
|
agents: [],
|
|
289
290
|
sessions: [],
|
|
@@ -357,6 +358,8 @@ export class AcpStore {
|
|
|
357
358
|
seatCatalogReads = new Set();
|
|
358
359
|
/** In-flight `agent/cached-models` reads, keyed by agent id. */
|
|
359
360
|
cachedModelReads = new Set();
|
|
361
|
+
/** Counts behind the last successful explicit re-detect, so a locale switch can re-render its summary. */
|
|
362
|
+
detectCounts;
|
|
360
363
|
constructor(rpc, locale) {
|
|
361
364
|
this.rpc = rpc;
|
|
362
365
|
this.locale = locale;
|
|
@@ -535,10 +538,11 @@ export class AcpStore {
|
|
|
535
538
|
const result = await this.call('agents/detect');
|
|
536
539
|
const agents = result.agents.length > 0 ? result.agents : [...DEFAULT_KNOWN_PRESETS];
|
|
537
540
|
const installed = agents.filter(agent => agent.detected).length;
|
|
541
|
+
this.detectCounts = { total: agents.length, installed };
|
|
538
542
|
this.mutate({
|
|
539
543
|
detectedAgents: agents,
|
|
540
544
|
detecting: false,
|
|
541
|
-
detectSummary: this.
|
|
545
|
+
detectSummary: this.detectSummaryOf(agents.length, installed),
|
|
542
546
|
});
|
|
543
547
|
}
|
|
544
548
|
catch (error) {
|
|
@@ -546,6 +550,7 @@ export class AcpStore {
|
|
|
546
550
|
const notice = msg.includes('unknown endpoint agents/detect')
|
|
547
551
|
? this.locale('宿主服务运行着旧版本代码,请在终端重启 dsh web 服务以激活主机检测功能。', 'Host running older version. Please restart dsh web to enable agent detection.')
|
|
548
552
|
: msg;
|
|
553
|
+
this.detectCounts = undefined;
|
|
549
554
|
this.mutate({
|
|
550
555
|
detecting: false,
|
|
551
556
|
notice,
|
|
@@ -555,6 +560,9 @@ export class AcpStore {
|
|
|
555
560
|
});
|
|
556
561
|
}
|
|
557
562
|
}
|
|
563
|
+
detectSummaryOf(total, installed) {
|
|
564
|
+
return this.locale(`已检测 ${String(total)} 个预设:${String(installed)} 个已安装,其余可从「添加智能体」启用。`, `Detected ${String(total)} presets: ${String(installed)} installed, enable the rest via "Add agent".`);
|
|
565
|
+
}
|
|
558
566
|
async saveAgent(payload) {
|
|
559
567
|
try {
|
|
560
568
|
await this.call('agent/save', payload);
|
|
@@ -1366,6 +1374,19 @@ export class AcpStore {
|
|
|
1366
1374
|
* renders or every row re-renders on every store notification.
|
|
1367
1375
|
*/
|
|
1368
1376
|
label = (zh, en) => this.locale(zh, en);
|
|
1377
|
+
/**
|
|
1378
|
+
* Republish the snapshot after a locale switch. Every `label()` resolves
|
|
1379
|
+
* against the current language while rendering, so a notification is all
|
|
1380
|
+
* that is needed — except the detect summary, which the store owns as an
|
|
1381
|
+
* already-resolved string and rebuilds from its counts here.
|
|
1382
|
+
*/
|
|
1383
|
+
refreshLocale() {
|
|
1384
|
+
const counts = this.detectCounts;
|
|
1385
|
+
this.mutate({
|
|
1386
|
+
localeRev: this.state.localeRev + 1,
|
|
1387
|
+
...(counts === undefined ? {} : { detectSummary: this.detectSummaryOf(counts.total, counts.installed) }),
|
|
1388
|
+
});
|
|
1389
|
+
}
|
|
1369
1390
|
clearNotice() {
|
|
1370
1391
|
this.mutate({ notice: undefined });
|
|
1371
1392
|
}
|
package/lib/client.js
CHANGED
|
@@ -270,6 +270,7 @@ window.__ModuleLoader__.load({
|
|
|
270
270
|
locale;
|
|
271
271
|
state = {
|
|
272
272
|
open: false,
|
|
273
|
+
localeRev: 0,
|
|
273
274
|
stream: "init",
|
|
274
275
|
agents: [],
|
|
275
276
|
sessions: [],
|
|
@@ -343,6 +344,8 @@ window.__ModuleLoader__.load({
|
|
|
343
344
|
seatCatalogReads = /* @__PURE__ */ new Set();
|
|
344
345
|
/** In-flight `agent/cached-models` reads, keyed by agent id. */
|
|
345
346
|
cachedModelReads = /* @__PURE__ */ new Set();
|
|
347
|
+
/** Counts behind the last successful explicit re-detect, so a locale switch can re-render its summary. */
|
|
348
|
+
detectCounts;
|
|
346
349
|
constructor(rpc, locale) {
|
|
347
350
|
this.rpc = rpc;
|
|
348
351
|
this.locale = locale;
|
|
@@ -495,14 +498,19 @@ window.__ModuleLoader__.load({
|
|
|
495
498
|
const result = await this.call("agents/detect");
|
|
496
499
|
const agents = result.agents.length > 0 ? result.agents : [...DEFAULT_KNOWN_PRESETS];
|
|
497
500
|
const installed = agents.filter((agent) => agent.detected).length;
|
|
501
|
+
this.detectCounts = {
|
|
502
|
+
total: agents.length,
|
|
503
|
+
installed
|
|
504
|
+
};
|
|
498
505
|
this.mutate({
|
|
499
506
|
detectedAgents: agents,
|
|
500
507
|
detecting: false,
|
|
501
|
-
detectSummary: this.
|
|
508
|
+
detectSummary: this.detectSummaryOf(agents.length, installed)
|
|
502
509
|
});
|
|
503
510
|
} catch (error) {
|
|
504
511
|
const msg = error instanceof Error ? error.message : String(error);
|
|
505
512
|
const notice = msg.includes("unknown endpoint agents/detect") ? this.locale("宿主服务运行着旧版本代码,请在终端重启 dsh web 服务以激活主机检测功能。", "Host running older version. Please restart dsh web to enable agent detection.") : msg;
|
|
513
|
+
this.detectCounts = void 0;
|
|
506
514
|
this.mutate({
|
|
507
515
|
detecting: false,
|
|
508
516
|
notice,
|
|
@@ -511,6 +519,9 @@ window.__ModuleLoader__.load({
|
|
|
511
519
|
});
|
|
512
520
|
}
|
|
513
521
|
}
|
|
522
|
+
detectSummaryOf(total, installed) {
|
|
523
|
+
return this.locale(`已检测 ${String(total)} 个预设:${String(installed)} 个已安装,其余可从「添加智能体」启用。`, `Detected ${String(total)} presets: ${String(installed)} installed, enable the rest via "Add agent".`);
|
|
524
|
+
}
|
|
514
525
|
async saveAgent(payload) {
|
|
515
526
|
try {
|
|
516
527
|
await this.call("agent/save", payload);
|
|
@@ -1254,6 +1265,19 @@ window.__ModuleLoader__.load({
|
|
|
1254
1265
|
* renders or every row re-renders on every store notification.
|
|
1255
1266
|
*/
|
|
1256
1267
|
label = (zh, en) => this.locale(zh, en);
|
|
1268
|
+
/**
|
|
1269
|
+
* Republish the snapshot after a locale switch. Every `label()` resolves
|
|
1270
|
+
* against the current language while rendering, so a notification is all
|
|
1271
|
+
* that is needed — except the detect summary, which the store owns as an
|
|
1272
|
+
* already-resolved string and rebuilds from its counts here.
|
|
1273
|
+
*/
|
|
1274
|
+
refreshLocale() {
|
|
1275
|
+
const counts = this.detectCounts;
|
|
1276
|
+
this.mutate({
|
|
1277
|
+
localeRev: this.state.localeRev + 1,
|
|
1278
|
+
...counts === void 0 ? {} : { detectSummary: this.detectSummaryOf(counts.total, counts.installed) }
|
|
1279
|
+
});
|
|
1280
|
+
}
|
|
1257
1281
|
clearNotice() {
|
|
1258
1282
|
this.mutate({ notice: void 0 });
|
|
1259
1283
|
}
|
|
@@ -5436,9 +5460,10 @@ window.__ModuleLoader__.load({
|
|
|
5436
5460
|
});
|
|
5437
5461
|
}
|
|
5438
5462
|
/**
|
|
5439
|
-
* One stream block, dispatched by kind. Memoized on its props — `block
|
|
5440
|
-
*
|
|
5441
|
-
*
|
|
5463
|
+
* One stream block, dispatched by kind. Memoized on its props — `block`, the
|
|
5464
|
+
* store's instance-bound `label`, and `localeRev` (it moves on a language
|
|
5465
|
+
* switch, so translated copy re-renders) — so a folded batch re-renders only
|
|
5466
|
+
* the rows whose block object changed (`foldUpdate` replaces blocks immutably)
|
|
5442
5467
|
* instead of the whole transcript on every store notification. `key` stays the
|
|
5443
5468
|
* index on purpose: blocks fold append-only, and identity is the block object.
|
|
5444
5469
|
*/
|
|
@@ -5685,7 +5710,8 @@ window.__ModuleLoader__.load({
|
|
|
5685
5710
|
children: t("还没有消息:在下面的输入框里发给它。", "Nothing yet — send it a message below.")
|
|
5686
5711
|
}) : transcript.blocks.map((block, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BlockView, {
|
|
5687
5712
|
block,
|
|
5688
|
-
label: acp.label
|
|
5713
|
+
label: acp.label,
|
|
5714
|
+
localeRev: ui.localeRev
|
|
5689
5715
|
}, index))
|
|
5690
5716
|
})]
|
|
5691
5717
|
});
|
|
@@ -6237,7 +6263,7 @@ window.__ModuleLoader__.load({
|
|
|
6237
6263
|
//#region src/client/index.tsx
|
|
6238
6264
|
const name = "dsh-ntr-client";
|
|
6239
6265
|
const inject = ["slots", "connection"];
|
|
6240
|
-
const
|
|
6266
|
+
const browserPrefersZh = typeof navigator !== "undefined" && /^zh\b/i.test(navigator.language);
|
|
6241
6267
|
/**
|
|
6242
6268
|
* How long a host verdict may stand as the gate's ONLY fact source before the
|
|
6243
6269
|
* gate asks the host again.
|
|
@@ -6269,8 +6295,6 @@ window.__ModuleLoader__.load({
|
|
|
6269
6295
|
const rpc = ctx.get("connection")?.rpc;
|
|
6270
6296
|
if (rpc === void 0) throw new Error("dsh-ntr: browser connection.rpc is unavailable");
|
|
6271
6297
|
const call = (channel, endpoint, payload) => rpc.call(channel, endpoint, payload);
|
|
6272
|
-
const store = new AcpStore(call, (zhText, enText) => zh ? zhText : enText);
|
|
6273
|
-
store.start();
|
|
6274
6298
|
/**
|
|
6275
6299
|
* Resolve one optional service without injecting it. A plain `ctx.get` at
|
|
6276
6300
|
* call time keeps the plugin loadable when the service is absent AND picks it
|
|
@@ -6278,6 +6302,36 @@ window.__ModuleLoader__.load({
|
|
|
6278
6302
|
* also making its absence fatal.
|
|
6279
6303
|
*/
|
|
6280
6304
|
const service = (name) => ctx.get(name);
|
|
6305
|
+
/**
|
|
6306
|
+
* Resolve a Chinese/English pair against the shell's active locale. Read per
|
|
6307
|
+
* call, so a language switch reaches every render-time `label()` call without
|
|
6308
|
+
* rebuilding the store. A host without the locale service keeps the
|
|
6309
|
+
* browser-derived choice the pairs shipped with.
|
|
6310
|
+
*/
|
|
6311
|
+
const label = (zhText, enText) => {
|
|
6312
|
+
const resolved = service("locale")?.resolveText?.({
|
|
6313
|
+
zh: zhText,
|
|
6314
|
+
en: enText
|
|
6315
|
+
});
|
|
6316
|
+
return typeof resolved === "string" && resolved !== "" ? resolved : browserPrefersZh ? zhText : enText;
|
|
6317
|
+
};
|
|
6318
|
+
const store = new AcpStore(call, label);
|
|
6319
|
+
store.start();
|
|
6320
|
+
ctx.effect(() => {
|
|
6321
|
+
let stop;
|
|
6322
|
+
const attach = () => {
|
|
6323
|
+
if (stop !== void 0) return;
|
|
6324
|
+
stop = service("locale")?.subscribe?.(() => {
|
|
6325
|
+
store.refreshLocale();
|
|
6326
|
+
});
|
|
6327
|
+
};
|
|
6328
|
+
attach();
|
|
6329
|
+
const stopService = ctx.on("internal/service", attach);
|
|
6330
|
+
return () => {
|
|
6331
|
+
stopService();
|
|
6332
|
+
stop?.();
|
|
6333
|
+
};
|
|
6334
|
+
}, "dsh-ntr: locale switch re-render");
|
|
6281
6335
|
/** This session's command/projection face (`ctx.sessions`); `undefined` before it materializes. */
|
|
6282
6336
|
const faceFor = (sessionId) => {
|
|
6283
6337
|
try {
|