@jingyi0605/codingns4dsh 0.1.7-rc.3 → 0.2.0-beta.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 (89) hide show
  1. package/LICENSE +695 -0
  2. package/README.en.md +23 -9
  3. package/README.md +23 -9
  4. package/data/build/dist/bootstrap/dsh-connection-adapter.d.ts +1 -1
  5. package/data/build/dist/bootstrap/index.d.ts +1 -1
  6. package/data/build/dist/client/bundle.js +1200 -170
  7. package/data/build/dist/client/context-breakdown-dom.d.ts +53 -0
  8. package/data/build/dist/client/context-breakdown-dom.js +171 -0
  9. package/data/build/dist/client/dsh-h5-bootstrap.js +7 -1
  10. package/data/build/dist/client/features/cli-adapters.js +11 -0
  11. package/data/build/dist/client/features/index.d.ts +1 -0
  12. package/data/build/dist/client/features/index.js +1 -0
  13. package/data/build/dist/client/features/subscription-usage-panel.d.ts +15 -0
  14. package/data/build/dist/client/features/subscription-usage-panel.js +117 -0
  15. package/data/build/dist/client/features/workspace-session-enhancement-panel.js +22 -5
  16. package/data/build/dist/client/features/workspace-session-enhancement.js +17 -2
  17. package/data/build/dist/client/git-management.js +80 -10
  18. package/data/build/dist/client/index.d.ts +1 -0
  19. package/data/build/dist/client/index.js +16 -49
  20. package/data/build/dist/client/locale.js +20 -2
  21. package/data/build/dist/client/remote-web-context.js +26 -2
  22. package/data/build/dist/client/settings-bridge.d.ts +20 -5
  23. package/data/build/dist/client/settings-bridge.js +30 -7
  24. package/data/build/dist/client/subscription-slot.d.ts +3 -1
  25. package/data/build/dist/client/subscription-slot.js +31 -6
  26. package/data/build/dist/client/terminal/ui.js +1 -1
  27. package/data/build/dist/client/terminal/xterm-view.js +1 -1
  28. package/data/build/dist/dsh-capabilities/client/config-forms-adapter.d.ts +10 -0
  29. package/data/build/dist/dsh-capabilities/client/config-forms-adapter.js +22 -0
  30. package/data/build/dist/dsh-capabilities/client/primitives-adapter.d.ts +5 -2
  31. package/data/build/dist/dsh-capabilities/client/primitives-adapter.js +3 -2
  32. package/data/build/dist/dsh-capabilities/client/settings-scope-adapter.d.ts +2 -2
  33. package/data/build/dist/dsh-capabilities/host/config-forms-adapter.d.ts +22 -1
  34. package/data/build/dist/dsh-capabilities/host/config-forms-adapter.js +29 -3
  35. package/data/build/dist/dsh-capabilities/host/settings-scope-adapter.d.ts +2 -2
  36. package/data/build/dist/dsh-capabilities/matrix.js +31 -1
  37. package/data/build/dist/dsh-capabilities/routes.d.ts +8 -1
  38. package/data/build/dist/dsh-capabilities/routes.js +173 -5
  39. package/data/build/dist/dsh-capabilities/types.d.ts +1 -1
  40. package/data/build/dist/features/registry.d.ts +7 -0
  41. package/data/build/dist/features/registry.js +20 -2
  42. package/data/build/dist/host/cli-adapters/feature.js +24 -2
  43. package/data/build/dist/host/cli-adapters/native-team-proxy.d.ts +36 -0
  44. package/data/build/dist/host/cli-adapters/native-team-proxy.js +108 -0
  45. package/data/build/dist/host/cli-adapters/provider-subscription.d.ts +35 -0
  46. package/data/build/dist/host/cli-adapters/provider-subscription.js +405 -5
  47. package/data/build/dist/host/cli-adapters/registry.d.ts +9 -3
  48. package/data/build/dist/host/cli-adapters/registry.js +30 -0
  49. package/data/build/dist/host/cli-adapters/session-store.d.ts +8 -2
  50. package/data/build/dist/host/cli-adapters/session-store.js +19 -0
  51. package/data/build/dist/host/features/git-management.js +41 -18
  52. package/data/build/dist/host/features/types.d.ts +6 -3
  53. package/data/build/dist/host/index-injection.d.ts +12 -5
  54. package/data/build/dist/host/index-injection.js +32 -11
  55. package/data/build/dist/host/index.js +15 -4
  56. package/data/build/dist/host/lan-access-dsh.d.ts +2 -2
  57. package/data/build/dist/host/native-session-bridge.d.ts +4 -0
  58. package/data/build/dist/host/native-session-bridge.js +51 -9
  59. package/data/build/dist/host/relay-tunnel-runtime.js +10 -1
  60. package/data/build/dist/host/remote-web-runtime.js +40 -17
  61. package/data/build/dist/host/rpc.d.ts +24 -5
  62. package/data/build/dist/host/rpc.js +38 -9
  63. package/data/build/dist/host/session-migration-repair.js +14 -0
  64. package/data/build/dist/host/settings.d.ts +2 -2
  65. package/data/build/dist/host/settings.js +11 -1
  66. package/data/build/dist/host/terminal/startup.d.ts +2 -2
  67. package/data/build/dist/shared/contracts/cli-adapter.d.ts +22 -1
  68. package/data/build/dist/shared/contracts/config.d.ts +21 -0
  69. package/data/build/dist/shared/contracts/config.js +17 -0
  70. package/data/build/dist/shared/contracts/errors.d.ts +2 -2
  71. package/data/build/dist/shared/contracts/subscription.d.ts +7 -0
  72. package/data/build/dist/shared/contracts/subscription.js +14 -1
  73. package/data/build/dist/shared/contracts/version.d.ts +4 -4
  74. package/data/build/dist/shared/contracts/version.js +3 -3
  75. package/data/build/dist/shared/index.d.ts +3 -2
  76. package/data/build/dist/shared/index.js +2 -1
  77. package/data/build/dist/transport/carrier.d.ts +11 -1
  78. package/data/build/dist/transport/carrier.js +77 -26
  79. package/data/build/dist/transport/debug.d.ts +5 -0
  80. package/data/build/dist/transport/debug.js +17 -2
  81. package/data/build/dist/transport/dsh-session.d.ts +18 -1
  82. package/data/build/dist/transport/dsh-session.js +48 -13
  83. package/data/build/dist/transport/index.d.ts +1 -1
  84. package/data/build/dist/transport/index.js +1 -1
  85. package/package.json +23 -19
  86. package/profile/README.md +2 -2
  87. package/profile/package.json +4 -3
  88. package/profile/version.json +3 -3
  89. package/version.json +3 -3
@@ -15,14 +15,20 @@ export class ProviderSubscriptionService {
15
15
  sub2api;
16
16
  deepseek;
17
17
  official;
18
+ kimi;
19
+ grok;
18
20
  constructor(options = {}) {
21
+ // 全局超时只作为缺省值;单项服务显式给出的 timeoutMs 优先。
22
+ const shared = options.timeoutMs === undefined ? {} : { timeoutMs: options.timeoutMs };
19
23
  this.commandCode = options.commandCode;
20
- this.codex = new CodexSubscriptionService(options.codex);
21
- this.claudeCode = new ClaudeCodeSubscriptionService(options.claudeCode);
24
+ this.codex = new CodexSubscriptionService({ ...shared, ...options.codex });
25
+ this.claudeCode = new ClaudeCodeSubscriptionService({ ...shared, ...options.claudeCode });
22
26
  this.opencode = new OpenCodeSubscriptionService(options.opencode);
23
- this.sub2api = new Sub2ApiUsageService(options.sub2api);
24
- this.deepseek = new DeepseekSubscriptionService(options.deepseek);
25
- this.official = new OfficialProviderSubscriptionService(options.official);
27
+ this.sub2api = new Sub2ApiUsageService({ ...shared, ...options.sub2api });
28
+ this.deepseek = new DeepseekSubscriptionService({ ...shared, ...options.deepseek });
29
+ this.official = new OfficialProviderSubscriptionService({ ...shared, ...options.official });
30
+ this.kimi = new KimiSubscriptionService({ ...shared, ...options.kimi });
31
+ this.grok = new GrokSubscriptionService({ ...shared, ...options.grok });
26
32
  }
27
33
  read(adapterId, providerId) {
28
34
  if (adapterId === 'dsh')
@@ -35,6 +41,7 @@ export class ProviderSubscriptionService {
35
41
  case 'codex': return this.codex.read();
36
42
  case 'claude-code': return this.claudeCode.read();
37
43
  case 'opencode': return this.opencode.read();
44
+ case 'kimi': return this.kimi.read().then((usage) => usage === null ? null : withProvider(usage, identifyModelProvider({ name: 'kimi-coding' }), ''));
38
45
  default: return Promise.resolve(null);
39
46
  }
40
47
  }
@@ -71,6 +78,8 @@ export class ProviderSubscriptionService {
71
78
  return this.codex.read().then((usage) => usage === null ? null : withProvider(usage, identifyModelProvider({ name: 'openai-codex' }), ''));
72
79
  if (adapterId === 'claude-code')
73
80
  return this.claudeCode.read().then((usage) => usage === null ? null : withProvider(usage, identifyModelProvider({ name: 'anthropic' }), ''));
81
+ if (adapterId === 'grok')
82
+ return this.grok.read().then((usage) => usage === null ? null : withProvider(usage, identifyModelProvider({ name: 'xai' }), ''));
74
83
  return null;
75
84
  }
76
85
  }
@@ -304,6 +313,338 @@ export class OpenCodeSubscriptionService {
304
313
  return null;
305
314
  }
306
315
  }
316
+ /** 读取 Kimi Code 官方套餐额度;本地 OAuth 凭据只在 Host 内使用,不做刷新只读访问令牌。 */
317
+ export class KimiSubscriptionService {
318
+ request;
319
+ timeoutMs;
320
+ credentialPaths;
321
+ constructor(options = {}) {
322
+ this.request = options.fetch ?? fetch;
323
+ this.timeoutMs = options.timeoutMs ?? 8_000;
324
+ this.credentialPaths = options.credentials ?? defaultKimiCredentialPaths();
325
+ }
326
+ async read() {
327
+ const token = this.readAccessToken();
328
+ if (token === null)
329
+ return null;
330
+ for (const baseUrl of kimiCodeBaseUrls(token.region)) {
331
+ const usage = await this.readUsage(baseUrl, token.value);
332
+ if (usage !== null)
333
+ return usage;
334
+ }
335
+ return null;
336
+ }
337
+ readAccessToken() {
338
+ for (const path of this.credentialPaths) {
339
+ const credentials = readJson(path);
340
+ const token = textValue(credentials?.access_token);
341
+ if (token === null)
342
+ continue;
343
+ const expiresAt = numberValue(credentials?.expires_at);
344
+ // 访问令牌有效期很短;已过期或临期的令牌直接跳过,由 CLI 自己负责刷新。
345
+ if (expiresAt !== null && expiresAt * 1000 <= Date.now() + 30_000)
346
+ continue;
347
+ return { value: token, region: kimiTokenRegion(token) };
348
+ }
349
+ return null;
350
+ }
351
+ async readUsage(baseUrl, token) {
352
+ const controller = new AbortController();
353
+ const timer = setTimeout(() => controller.abort(), this.timeoutMs);
354
+ try {
355
+ const response = await this.request(`${baseUrl}/usages`, {
356
+ headers: { Authorization: `Bearer ${token}`, Accept: 'application/json' },
357
+ signal: controller.signal,
358
+ });
359
+ if (!response.ok)
360
+ return null;
361
+ return normalizeKimiUsage(await response.json());
362
+ }
363
+ catch {
364
+ return null;
365
+ }
366
+ finally {
367
+ clearTimeout(timer);
368
+ }
369
+ }
370
+ }
371
+ const GROK_BILLING_ENDPOINT = 'https://grok.com/grok_api_v2.GrokBuildBilling/GetGrokCreditsConfig';
372
+ const GROK_OIDC_SCOPE_PREFIX = 'https://auth.x.ai::';
373
+ const GROK_LEGACY_SESSION_SCOPE = 'https://accounts.x.ai/sign-in';
374
+ /** 读取 Grok(xAI)官方 SuperGrok 订阅额度;只读 Grok CLI 的 OAuth 凭据(~/.grok/auth.json),不做刷新,过期令牌也照常尝试以容忍时钟偏差。 */
375
+ export class GrokSubscriptionService {
376
+ request;
377
+ timeoutMs;
378
+ credentialPaths;
379
+ constructor(options = {}) {
380
+ this.request = options.fetch ?? fetch;
381
+ this.timeoutMs = options.timeoutMs ?? 15_000;
382
+ this.credentialPaths = options.credentials ?? defaultGrokCredentialPaths();
383
+ }
384
+ async read() {
385
+ const token = this.readAccessToken();
386
+ if (token === null)
387
+ return null;
388
+ const controller = new AbortController();
389
+ const timer = setTimeout(() => controller.abort(), this.timeoutMs);
390
+ try {
391
+ // 空 gRPC-web 帧(1 字节 flags + 4 字节大端长度 0)即可取回账单快照。
392
+ const response = await this.request(GROK_BILLING_ENDPOINT, {
393
+ method: 'POST',
394
+ headers: {
395
+ Authorization: `Bearer ${token}`,
396
+ Origin: 'https://grok.com',
397
+ Referer: 'https://grok.com/?_s=usage',
398
+ Accept: '*/*',
399
+ 'Content-Type': 'application/grpc-web+proto',
400
+ 'x-grpc-web': '1',
401
+ 'x-user-agent': 'connect-es/2.1.1',
402
+ },
403
+ body: new Uint8Array(5),
404
+ signal: controller.signal,
405
+ });
406
+ // 鉴权失败与其他 HTTP 错误都不展示陈旧数据;令牌过期时由 Grok CLI 重新登录后恢复。
407
+ if (!response.ok)
408
+ return null;
409
+ const headerStatus = numberValue(response.headers.get('grpc-status') ?? undefined);
410
+ if (headerStatus !== null && headerStatus !== 0)
411
+ return null;
412
+ const raw = new Uint8Array(await response.arrayBuffer());
413
+ const trailerStatus = numberValue(grpcWebTrailerFields(raw)['grpc-status']);
414
+ if (trailerStatus !== null && trailerStatus !== 0)
415
+ return null;
416
+ const nowSecs = Math.floor(Date.now() / 1000);
417
+ const snapshot = parseGrokBillingPayload(raw, nowSecs);
418
+ if (snapshot === null)
419
+ return null;
420
+ const usedPercent = clamp(snapshot.usedPercent);
421
+ return {
422
+ authenticated: true,
423
+ planType: null,
424
+ primary: { usedPercent, remainingPercent: 100 - usedPercent, windowDurationMins: grokWindowDurationMins(snapshot.resetsAt, nowSecs), resetsAt: snapshot.resetsAt },
425
+ secondary: null,
426
+ monthly: null,
427
+ rateLimitReachedType: null,
428
+ resetCredits: null,
429
+ capturedAt: new Date().toISOString(),
430
+ };
431
+ }
432
+ catch {
433
+ return null;
434
+ }
435
+ finally {
436
+ clearTimeout(timer);
437
+ }
438
+ }
439
+ readAccessToken() {
440
+ for (const path of this.credentialPaths) {
441
+ const auth = readJson(path);
442
+ if (auth === null)
443
+ continue;
444
+ const token = selectGrokAuthToken(auth);
445
+ if (token !== null)
446
+ return token;
447
+ }
448
+ return null;
449
+ }
450
+ }
451
+ function defaultGrokCredentialPaths() {
452
+ return [join(process.env.GROK_HOME ?? join(homedir(), '.grok'), 'auth.json')];
453
+ }
454
+ /** auth.json 以 OIDC scope URL 为键;优先 SuperGrok 的 auth.x.ai 条目,回退 legacy 登录条目,残缺条目不遮蔽健康条目。 */
455
+ function selectGrokAuthToken(auth) {
456
+ let oidc = null;
457
+ let legacy = null;
458
+ for (const [scope, value] of Object.entries(auth)) {
459
+ const key = textValue(recordValue(value)?.key);
460
+ if (key === null)
461
+ continue;
462
+ if (scope.startsWith(GROK_OIDC_SCOPE_PREFIX))
463
+ oidc = key;
464
+ else if (scope === GROK_LEGACY_SESSION_SCOPE || scope.includes('/sign-in'))
465
+ legacy = key;
466
+ }
467
+ return oidc ?? legacy;
468
+ }
469
+ /** 按重置时间距今的天数推断窗口长度(4–12 天按周窗口、20–45 天按月窗口),无法推断时留空由客户端使用兜底标签。 */
470
+ function grokWindowDurationMins(resetsAt, nowSecs) {
471
+ if (resetsAt === null)
472
+ return null;
473
+ const days = Math.round((resetsAt - nowSecs) / 86_400);
474
+ if (days >= 4 && days <= 12)
475
+ return 7 * 24 * 60;
476
+ if (days >= 20 && days <= 45)
477
+ return 30 * 24 * 60;
478
+ return null;
479
+ }
480
+ /**
481
+ * 账单端点没有公开 .proto,按字段路径启发式提取(移植自 cc-switch / CodexBar):
482
+ * 百分比取路径末段为 1、值域 [0,100] 的最浅 fixed32 字段;重置时间取落在合理 Unix 秒区间且
483
+ * 晚于当前时刻的 varint(优先路径 [1,5,1]);proto3 会省略 0 值字段,存在重置时间与周期标记时按 0% 处理。
484
+ */
485
+ function parseGrokBillingPayload(data, nowSecs) {
486
+ let payloads = grpcWebDataFrames(data);
487
+ if (payloads.length === 0 && looksLikeProtobufPayload(data))
488
+ payloads = [data];
489
+ if (payloads.length === 0)
490
+ return null;
491
+ const scan = { fixed32: [], varint: [] };
492
+ for (const payload of payloads)
493
+ scanGrokProtobuf(payload, 0, [], 0, scan);
494
+ const percent = scan.fixed32
495
+ .filter((field) => field.path.at(-1) === 1 && Number.isFinite(field.value) && field.value >= 0 && field.value <= 100)
496
+ .sort((left, right) => (left.path.length - right.path.length) || (left.order - right.order))[0]?.value;
497
+ const resetCandidates = scan.varint.filter((field) => field.value >= 1_700_000_000 && field.value <= 2_100_000_000 && field.value > nowSecs);
498
+ const preferred = resetCandidates.filter((field) => field.path.length === 3 && field.path[0] === 1 && field.path[1] === 5 && field.path[2] === 1);
499
+ const resetPool = preferred.length > 0 ? preferred : resetCandidates;
500
+ const reset = resetPool.length > 0 ? Math.min(...resetPool.map((field) => field.value)) : null;
501
+ const hasUsagePeriod = scan.varint.some((field) => (field.path.length >= 2 && field.path[0] === 1 && field.path[1] === 6)
502
+ || (field.path.length === 3 && field.path[0] === 1 && field.path[1] === 8 && field.path[2] === 1 && (field.value === 1 || field.value === 2)));
503
+ const usedPercent = percent ?? (scan.fixed32.length === 0 && reset !== null && hasUsagePeriod ? 0 : undefined);
504
+ if (usedPercent === undefined)
505
+ return null;
506
+ return { usedPercent, resetsAt: reset };
507
+ }
508
+ /** 拆出 gRPC-web data 帧(flags 高位 0x80 的 trailer 帧跳过);任一帧长度非法时返回空,调用方再按裸 protobuf 兜底。 */
509
+ function grpcWebDataFrames(data) {
510
+ const frames = [];
511
+ let index = 0;
512
+ while (index < data.length) {
513
+ if (index + 5 > data.length)
514
+ return [];
515
+ const flags = data[index];
516
+ const length = data[index + 1] * 16_777_216 + data[index + 2] * 65_536 + data[index + 3] * 256 + data[index + 4];
517
+ const start = index + 5;
518
+ const end = start + length;
519
+ if (end > data.length)
520
+ return [];
521
+ if ((flags & 0x80) === 0)
522
+ frames.push(data.subarray(start, end));
523
+ index = end;
524
+ }
525
+ return frames;
526
+ }
527
+ /** 响应体没有帧头时,看首字节是否像合法 protobuf tag(某些成功请求直接返回裸 protobuf)。 */
528
+ function looksLikeProtobufPayload(data) {
529
+ const first = data[0];
530
+ if (first === undefined)
531
+ return false;
532
+ const wireType = first & 0x07;
533
+ return first >> 3 > 0 && (wireType === 0 || wireType === 1 || wireType === 2 || wireType === 5);
534
+ }
535
+ /** 从 trailer 帧(flags & 0x80)解析 grpc-status / grpc-message 等字段。 */
536
+ function grpcWebTrailerFields(data) {
537
+ const fields = {};
538
+ let index = 0;
539
+ while (index + 5 <= data.length) {
540
+ const flags = data[index];
541
+ const length = data[index + 1] * 16_777_216 + data[index + 2] * 65_536 + data[index + 3] * 256 + data[index + 4];
542
+ const start = index + 5;
543
+ const end = start + length;
544
+ if (end > data.length)
545
+ break;
546
+ if ((flags & 0x80) !== 0) {
547
+ for (const line of new TextDecoder().decode(data.subarray(start, end)).split(/\r?\n/u)) {
548
+ const separator = line.indexOf(':');
549
+ if (separator <= 0)
550
+ continue;
551
+ fields[line.slice(0, separator).trim().toLowerCase()] = grokPercentDecode(line.slice(separator + 1).trim());
552
+ }
553
+ }
554
+ index = end;
555
+ }
556
+ return fields;
557
+ }
558
+ /** gRPC message 使用 percent-encoding;解码失败的序列原样保留。 */
559
+ function grokPercentDecode(input) {
560
+ const bytes = new TextEncoder().encode(input);
561
+ const out = [];
562
+ let index = 0;
563
+ while (index < bytes.length) {
564
+ if (bytes[index] === 0x25 && index + 2 < bytes.length) {
565
+ const hex = String.fromCharCode(bytes[index + 1], bytes[index + 2]);
566
+ if (/^[0-9a-fA-F]{2}$/u.test(hex)) {
567
+ out.push(parseInt(hex, 16));
568
+ index += 3;
569
+ continue;
570
+ }
571
+ }
572
+ out.push(bytes[index]);
573
+ index += 1;
574
+ }
575
+ return new TextDecoder().decode(new Uint8Array(out));
576
+ }
577
+ function readGrokVarint(bytes, state) {
578
+ let value = 0;
579
+ let shift = 0;
580
+ while (state.index < bytes.length && shift < 64) {
581
+ const byte = bytes[state.index];
582
+ state.index += 1;
583
+ value += (byte & 0x7f) * 2 ** shift;
584
+ if ((byte & 0x80) === 0)
585
+ return value;
586
+ shift += 7;
587
+ }
588
+ return null;
589
+ }
590
+ /** 递归扫描 protobuf 消息,收集 varint 与 fixed32 字段;length-delimited 一律按嵌套消息试扫(深度 ≤4),无法解析时从字段起点 +1 重新同步。 */
591
+ function scanGrokProtobuf(bytes, depth, path, order, scan) {
592
+ let index = 0;
593
+ let nextOrder = order;
594
+ while (index < bytes.length) {
595
+ const fieldStart = index;
596
+ const keyState = { index };
597
+ const key = readGrokVarint(bytes, keyState);
598
+ index = keyState.index;
599
+ if (key === null || key === 0) {
600
+ index = fieldStart + 1;
601
+ continue;
602
+ }
603
+ const fieldPath = [...path, Math.floor(key / 8)];
604
+ const wireType = key & 0x07;
605
+ if (wireType === 0) {
606
+ const valueState = { index };
607
+ const value = readGrokVarint(bytes, valueState);
608
+ if (value === null) {
609
+ index = fieldStart + 1;
610
+ continue;
611
+ }
612
+ index = valueState.index;
613
+ scan.varint.push({ path: fieldPath, value });
614
+ continue;
615
+ }
616
+ if (wireType === 1) {
617
+ if (index + 8 > bytes.length)
618
+ return nextOrder;
619
+ index += 8;
620
+ continue;
621
+ }
622
+ if (wireType === 2) {
623
+ const lengthState = { index };
624
+ const length = readGrokVarint(bytes, lengthState);
625
+ index = lengthState.index;
626
+ if (length === null || length > bytes.length - index) {
627
+ index = fieldStart + 1;
628
+ continue;
629
+ }
630
+ const end = index + length;
631
+ if (depth < 4)
632
+ nextOrder = scanGrokProtobuf(bytes.subarray(index, end), depth + 1, fieldPath, nextOrder, scan);
633
+ index = end;
634
+ continue;
635
+ }
636
+ if (wireType === 5) {
637
+ if (index + 4 > bytes.length)
638
+ return nextOrder;
639
+ scan.fixed32.push({ path: fieldPath, value: new DataView(bytes.buffer, bytes.byteOffset + index, 4).getFloat32(0, true), order: nextOrder });
640
+ nextOrder += 1;
641
+ index += 4;
642
+ continue;
643
+ }
644
+ index = fieldStart + 1;
645
+ }
646
+ return nextOrder;
647
+ }
307
648
  function normalizeCodexSnapshot(value) {
308
649
  const root = recordValue(value);
309
650
  const source = recordValue(root?.rateLimits ?? root?.rate_limits) ?? root;
@@ -376,6 +717,65 @@ function normalizeOpenCodeSnapshot(value) {
376
717
  function hasSubscriptionWindow(value) {
377
718
  return value !== null && (value.primary !== null || value.secondary !== null || value.monthly !== null);
378
719
  }
720
+ function defaultKimiCredentialPaths() {
721
+ return [
722
+ join(process.env.KIMI_CODE_HOME ?? join(homedir(), '.kimi-code'), 'credentials', 'kimi-code.json'),
723
+ join(process.env.KIMI_HOME ?? join(homedir(), '.kimi'), 'credentials', 'kimi-code.json'),
724
+ ];
725
+ }
726
+ /** 只解析 JWT payload 中的 region 字段用于选择就近接入点;签名校验由服务端负责。 */
727
+ function kimiTokenRegion(token) {
728
+ const parts = token.split('.');
729
+ const payloadPart = parts[1];
730
+ if (payloadPart === undefined)
731
+ return null;
732
+ try {
733
+ const payload = recordValue(JSON.parse(Buffer.from(payloadPart.replace(/-/gu, '+').replace(/_/gu, '/'), 'base64').toString('utf8')));
734
+ return textValue(payload?.region);
735
+ }
736
+ catch {
737
+ return null;
738
+ }
739
+ }
740
+ function kimiCodeBaseUrls(region) {
741
+ const override = textValue(process.env.KIMI_CODE_BASE_URL);
742
+ if (override !== null)
743
+ return [override.replace(/\/+$/u, '')];
744
+ const mainland = 'https://api.kimi.com/coding/v1';
745
+ const global = 'https://api.kimi.ai/coding/v1';
746
+ return region === 'cn' ? [mainland, global] : [global, mainland];
747
+ }
748
+ function normalizeKimiUsage(value) {
749
+ const usages = recordValue(recordValue(value)?.usages);
750
+ if (usages === null)
751
+ return null;
752
+ const primary = normalizeKimiQuotaWindow(usages.limit_5h ?? usages.limit5h, 5 * 60);
753
+ const secondary = normalizeKimiQuotaWindow(usages.limit_7d ?? usages.limit7d, 7 * 24 * 60);
754
+ const monthly = normalizeKimiQuotaWindow(usages.limit_month_code ?? usages.limitMonthCode, null)
755
+ ?? normalizeKimiQuotaWindow(usages.limit_month_total ?? usages.limitMonthTotal, null);
756
+ if (primary === null && secondary === null && monthly === null)
757
+ return null;
758
+ return {
759
+ authenticated: true,
760
+ planType: null,
761
+ primary,
762
+ secondary,
763
+ monthly,
764
+ rateLimitReachedType: null,
765
+ resetCredits: null,
766
+ capturedAt: new Date().toISOString(),
767
+ };
768
+ }
769
+ function normalizeKimiQuotaWindow(value, windowDurationMins) {
770
+ const source = recordValue(value);
771
+ if (source === null)
772
+ return null;
773
+ const usedRatio = numberValue(source.used_ratio ?? source.usedRatio);
774
+ if (usedRatio === null)
775
+ return null;
776
+ const usedPercent = clamp(usedRatio * 100);
777
+ return { usedPercent, remainingPercent: 100 - usedPercent, windowDurationMins, resetsAt: timestampValue(source.reset_time ?? source.resetAt) };
778
+ }
379
779
  function hasThirdPartyCodexConfig(homeDirectory) {
380
780
  const config = readText(join(homeDirectory, 'config.toml'));
381
781
  const configuredBaseUrl = config === null ? null : readCodexBaseUrl(config);
@@ -1,9 +1,9 @@
1
- import type { CodingNsAgentEvent, CodingNsAgentQuestionResponse, CodingNsCliAdapterDescriptor, CodingNsCliAdapterId, CodingNsCliModelCatalog, CodingNsAgentPermissionResponse, CodingNsCliSessionConfig, CodingNsCliSessionRecord, CodingNsCliTurnInput } from '../../shared/contracts/cli-adapter.js';
1
+ import type { CodingNsAgentEvent, CodingNsAgentQuestionResponse, CodingNsCliAdapterDescriptor, CodingNsCliAdapterId, CodingNsCliModelCatalog, CodingNsAgentPermissionResponse, CodingNsCliSessionConfig, CodingNsCliSessionRecord, CodingNsCliTurnInput, CodingNsCliTeamDiagnostic } from '../../shared/contracts/cli-adapter.js';
2
2
  import type { CodingNsCliDriver } from './driver.js';
3
3
  import { CodingNsCliSessionStore } from './session-store.js';
4
4
  import type { CodingNsNativeSessionBridge } from '../native-session-bridge.js';
5
5
  import type { CodingNsSettings, CodingNsCliAdapterPreference } from '../../shared/contracts/config.js';
6
- import type { SettingsScope } from '@deepseek-ai/dsh-settings';
6
+ import type { DshHostSettingsScope } from '../../dsh-capabilities/host/config-forms-adapter.js';
7
7
  export declare class CodingNsCliAdapterRegistry {
8
8
  private readonly drivers;
9
9
  private readonly sessions;
@@ -49,7 +49,7 @@ export declare class CodingNsCliAdapterRegistry {
49
49
  readonly modelCacheTtlMs?: number;
50
50
  readonly modelRetryTtlMs?: number;
51
51
  /** 适配器级最近模型选择的持久化设置。 */
52
- readonly settings?: SettingsScope<CodingNsSettings>;
52
+ readonly settings?: DshHostSettingsScope<CodingNsSettings>;
53
53
  });
54
54
  private readonly sessionStore;
55
55
  private readonly nativeSessions;
@@ -63,6 +63,12 @@ export declare class CodingNsCliAdapterRegistry {
63
63
  applyEnabledSettings(settings: Readonly<Record<string, boolean>> | undefined): void;
64
64
  isEnabled(adapterId: CodingNsCliAdapterId): boolean;
65
65
  enabledSnapshot(): Record<string, boolean>;
66
+ /**
67
+ * DSH 0.2 的 Team 必须由原生 TeamService 创建 continuable child、mailbox
68
+ * 和 task board。当前外部 CLI 仍是独立进程,不能伪装成原生成员;统一返回
69
+ * 可诊断结果,避免 Client 把普通 CLI 会话误显示成 Team 成员。
70
+ */
71
+ teamDiagnostic(): CodingNsCliTeamDiagnostic;
66
72
  setSession(sessionId: string, config: CodingNsCliSessionConfig): CodingNsCliSessionConfig;
67
73
  /** 设置服务变更后重新载入适配器级默认选择。 */
68
74
  syncPreferences(value: Readonly<Record<string, CodingNsCliAdapterPreference>> | undefined): void;
@@ -125,6 +125,18 @@ export class CodingNsCliAdapterRegistry {
125
125
  enabledSnapshot() {
126
126
  return Object.fromEntries(this.enabled.entries());
127
127
  }
128
+ /**
129
+ * DSH 0.2 的 Team 必须由原生 TeamService 创建 continuable child、mailbox
130
+ * 和 task board。当前外部 CLI 仍是独立进程,不能伪装成原生成员;统一返回
131
+ * 可诊断结果,避免 Client 把普通 CLI 会话误显示成 Team 成员。
132
+ */
133
+ teamDiagnostic() {
134
+ return {
135
+ supported: false,
136
+ code: 'DSH_TEAM_NATIVE_UNAVAILABLE',
137
+ message: '当前插件仅提供外部 CLI 会话适配器,尚未接入 DSH 0.2 原生 Agent Team 生命周期。',
138
+ };
139
+ }
128
140
  setSession(sessionId, config) {
129
141
  if (sessionId.trim() === '')
130
142
  throw new CodingNsRpcError('CODINGNS_CLI_INVALID_SESSION', 'sessionId 不能为空');
@@ -165,6 +177,24 @@ export class CodingNsCliAdapterRegistry {
165
177
  : sameAdapter && !providerIdentityChanged && previous?.rawStoreRef
166
178
  ? { rawStoreRef: previous.rawStoreRef }
167
179
  : {}),
180
+ ...(config.parentSessionId?.trim()
181
+ ? { parentSessionId: config.parentSessionId.trim() }
182
+ : sameAdapter && previous?.parentSessionId ? { parentSessionId: previous.parentSessionId } : {}),
183
+ ...(config.origin === 'user' || config.origin === 'subagent' || config.origin === 'plugin'
184
+ ? { origin: config.origin }
185
+ : sameAdapter && previous?.origin ? { origin: previous.origin } : {}),
186
+ ...(config.delegationDepth !== undefined && Number.isSafeInteger(config.delegationDepth) && config.delegationDepth >= 0
187
+ ? { delegationDepth: config.delegationDepth }
188
+ : sameAdapter && previous?.delegationDepth !== undefined ? { delegationDepth: previous.delegationDepth } : {}),
189
+ ...(config.continuationId?.trim()
190
+ ? { continuationId: config.continuationId.trim() }
191
+ : sameAdapter && previous?.continuationId ? { continuationId: previous.continuationId } : {}),
192
+ ...(config.teamId?.trim()
193
+ ? { teamId: config.teamId.trim() }
194
+ : sameAdapter && previous?.teamId ? { teamId: previous.teamId } : {}),
195
+ ...(config.teamMemberId?.trim()
196
+ ? { teamMemberId: config.teamMemberId.trim() }
197
+ : sameAdapter && previous?.teamMemberId ? { teamMemberId: previous.teamMemberId } : {}),
168
198
  };
169
199
  this.sessions.set(sessionId, normalized);
170
200
  this.sessionStore?.upsert(sessionId, normalized);
@@ -1,4 +1,4 @@
1
- import type { SettingsScope } from '@deepseek-ai/dsh-settings';
1
+ import type { DshHostSettingsScope } from '../../dsh-capabilities/host/config-forms-adapter.js';
2
2
  import type { CodingNsCliAdapterId, CodingNsCliSessionConfig, CodingNsCliProviderSessionState, CodingNsCliSessionRecord, CodingNsCliSessionStatus, CodingNsSessionAdapterBinding } from '../../shared/contracts/cli-adapter.js';
3
3
  import type { CodingNsSettings } from '../../shared/contracts/config.js';
4
4
  /**
@@ -11,7 +11,7 @@ export interface CodingNsCliSessionPersistence {
11
11
  write(records: readonly CodingNsCliSessionRecord[]): Promise<void>;
12
12
  }
13
13
  export interface CodingNsCliSessionStoreOptions {
14
- readonly settings?: SettingsScope<CodingNsSettings>;
14
+ readonly settings?: DshHostSettingsScope<CodingNsSettings>;
15
15
  readonly persistence?: CodingNsCliSessionPersistence;
16
16
  }
17
17
  export interface CodingNsCliSessionPatch {
@@ -21,6 +21,12 @@ export interface CodingNsCliSessionPatch {
21
21
  readonly providerId?: string;
22
22
  readonly providerSessionId?: string;
23
23
  readonly rawStoreRef?: string;
24
+ readonly parentSessionId?: string;
25
+ readonly origin?: CodingNsCliSessionRecord['origin'];
26
+ readonly delegationDepth?: number;
27
+ readonly continuationId?: string;
28
+ readonly teamId?: string;
29
+ readonly teamMemberId?: string;
24
30
  readonly title?: string;
25
31
  readonly cwd?: string;
26
32
  readonly status?: CodingNsCliSessionStatus;
@@ -94,6 +94,12 @@ export class CodingNsCliSessionStore {
94
94
  ...(patch.providerId?.trim() ? { providerId: patch.providerId.trim() } : base?.providerId ? { providerId: base.providerId } : {}),
95
95
  ...(providerSessionId ? { providerSessionId } : base?.providerSessionId ? { providerSessionId: base.providerSessionId } : {}),
96
96
  ...(patch.rawStoreRef?.trim() ? { rawStoreRef: patch.rawStoreRef.trim() } : providerBase?.rawStoreRef ? { rawStoreRef: providerBase.rawStoreRef } : {}),
97
+ ...(stringValue(patch.parentSessionId) ? { parentSessionId: stringValue(patch.parentSessionId) } : base?.parentSessionId ? { parentSessionId: base.parentSessionId } : {}),
98
+ ...(isSessionOrigin(patch.origin) ? { origin: patch.origin } : base?.origin ? { origin: base.origin } : {}),
99
+ ...(finiteNonNegativeInteger(patch.delegationDepth) ? { delegationDepth: patch.delegationDepth } : base?.delegationDepth === undefined ? {} : { delegationDepth: base.delegationDepth }),
100
+ ...(stringValue(patch.continuationId) ? { continuationId: stringValue(patch.continuationId) } : base?.continuationId ? { continuationId: base.continuationId } : {}),
101
+ ...(stringValue(patch.teamId) ? { teamId: stringValue(patch.teamId) } : base?.teamId ? { teamId: base.teamId } : {}),
102
+ ...(stringValue(patch.teamMemberId) ? { teamMemberId: stringValue(patch.teamMemberId) } : base?.teamMemberId ? { teamMemberId: base.teamMemberId } : {}),
97
103
  ...(patch.title?.trim() ? { title: patch.title.trim() } : base?.title ? { title: base.title } : {}),
98
104
  ...(patch.cwd?.trim() ? { cwd: patch.cwd.trim() } : base?.cwd ? { cwd: base.cwd } : {}),
99
105
  ...(patch.lastError?.trim() ? { lastError: patch.lastError.trim() } : {}),
@@ -160,6 +166,7 @@ export class CodingNsCliSessionStore {
160
166
  return;
161
167
  if (!isStatus(value.status) || typeof value.createdAt !== 'string' || typeof value.updatedAt !== 'string')
162
168
  return;
169
+ const origin = isSessionOrigin(value.origin) ? value.origin : undefined;
163
170
  this.records.set(value.dshSessionId, {
164
171
  dshSessionId: value.dshSessionId,
165
172
  adapterId: value.adapterId,
@@ -168,6 +175,12 @@ export class CodingNsCliSessionStore {
168
175
  ...(stringValue(value.providerId) ? { providerId: stringValue(value.providerId) } : {}),
169
176
  ...(stringValue(value.providerSessionId) ? { providerSessionId: stringValue(value.providerSessionId) } : {}),
170
177
  ...(stringValue(value.rawStoreRef) ? { rawStoreRef: stringValue(value.rawStoreRef) } : {}),
178
+ ...(stringValue(value.parentSessionId) ? { parentSessionId: stringValue(value.parentSessionId) } : {}),
179
+ ...(origin === undefined ? {} : { origin }),
180
+ ...(finiteNonNegativeInteger(value.delegationDepth) ? { delegationDepth: value.delegationDepth } : {}),
181
+ ...(stringValue(value.continuationId) ? { continuationId: stringValue(value.continuationId) } : {}),
182
+ ...(stringValue(value.teamId) ? { teamId: stringValue(value.teamId) } : {}),
183
+ ...(stringValue(value.teamMemberId) ? { teamMemberId: stringValue(value.teamMemberId) } : {}),
171
184
  ...(stringValue(value.title) ? { title: stringValue(value.title) } : {}),
172
185
  ...(stringValue(value.cwd) ? { cwd: stringValue(value.cwd) } : {}),
173
186
  ...(stringValue(value.lastError) ? { lastError: stringValue(value.lastError) } : {}),
@@ -212,6 +225,12 @@ function isProviderState(value) {
212
225
  function stringValue(value) {
213
226
  return typeof value === 'string' && value.trim() !== '' ? value : undefined;
214
227
  }
228
+ function isSessionOrigin(value) {
229
+ return value === 'user' || value === 'subagent' || value === 'plugin';
230
+ }
231
+ function finiteNonNegativeInteger(value) {
232
+ return typeof value === 'number' && Number.isSafeInteger(value) && value >= 0;
233
+ }
215
234
  function isRecord(value) {
216
235
  return typeof value === 'object' && value !== null && !Array.isArray(value);
217
236
  }