@wowyuarm/dsh-agent-team 0.1.15 → 0.2.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 (99) hide show
  1. package/README.md +8 -4
  2. package/README.zh.md +8 -4
  3. package/cordis.patch.yml +26 -12
  4. package/package.json +52 -47
  5. package/packages/agent-team/lib/attachments.js +63 -4
  6. package/packages/agent-team/lib/compaction-engine.js +18 -0
  7. package/packages/agent-team/lib/context-continuity-host.js +198 -4
  8. package/packages/agent-team/lib/context-judge.js +150 -0
  9. package/packages/agent-team/lib/context-source.js +11 -6
  10. package/packages/agent-team/lib/environment-check.js +102 -0
  11. package/packages/agent-team/lib/human-profile.js +23 -37
  12. package/packages/agent-team/lib/index.js +510 -99
  13. package/packages/agent-team/lib/installed-manifest.js +32 -0
  14. package/packages/agent-team/lib/invariant.js +36 -12
  15. package/packages/agent-team/lib/ledger.js +37 -1
  16. package/packages/agent-team/lib/member-context.js +2 -4
  17. package/packages/agent-team/lib/member-runtime.js +2 -1
  18. package/packages/agent-team/lib/member-time-context.js +2 -4
  19. package/packages/agent-team/lib/recovery.js +53 -4
  20. package/packages/agent-team/lib/typert.host.js +225 -54
  21. package/packages/agent-team/lib/typert.remote-client.d.ts +5 -1
  22. package/packages/agent-team/lib/typert.remote-client.d.ts.map +1 -1
  23. package/packages/agent-team/lib/typert.remote-client.js +127 -39
  24. package/packages/agent-team/lib/types/attachments.d.ts +32 -2
  25. package/packages/agent-team/lib/types/attachments.d.ts.map +1 -1
  26. package/packages/agent-team/lib/types/compaction-engine.d.ts +19 -0
  27. package/packages/agent-team/lib/types/compaction-engine.d.ts.map +1 -0
  28. package/packages/agent-team/lib/types/context-continuity-host.d.ts +131 -4
  29. package/packages/agent-team/lib/types/context-continuity-host.d.ts.map +1 -1
  30. package/packages/agent-team/lib/types/context-judge.d.ts +82 -0
  31. package/packages/agent-team/lib/types/context-judge.d.ts.map +1 -0
  32. package/packages/agent-team/lib/types/context-source.d.ts +7 -4
  33. package/packages/agent-team/lib/types/context-source.d.ts.map +1 -1
  34. package/packages/agent-team/lib/types/environment-check.d.ts +66 -0
  35. package/packages/agent-team/lib/types/environment-check.d.ts.map +1 -0
  36. package/packages/agent-team/lib/types/human-profile.d.ts +21 -27
  37. package/packages/agent-team/lib/types/human-profile.d.ts.map +1 -1
  38. package/packages/agent-team/lib/types/index.d.ts +281 -18
  39. package/packages/agent-team/lib/types/index.d.ts.map +1 -1
  40. package/packages/agent-team/lib/types/installed-manifest.d.ts +16 -0
  41. package/packages/agent-team/lib/types/installed-manifest.d.ts.map +1 -0
  42. package/packages/agent-team/lib/types/invariant.d.ts +27 -5
  43. package/packages/agent-team/lib/types/invariant.d.ts.map +1 -1
  44. package/packages/agent-team/lib/types/ledger.d.ts +12 -0
  45. package/packages/agent-team/lib/types/ledger.d.ts.map +1 -1
  46. package/packages/agent-team/lib/types/member-context.d.ts.map +1 -1
  47. package/packages/agent-team/lib/types/member-runtime.d.ts +1 -1
  48. package/packages/agent-team/lib/types/member-runtime.d.ts.map +1 -1
  49. package/packages/agent-team/lib/types/member-time-context.d.ts.map +1 -1
  50. package/packages/agent-team/lib/types/recovery.d.ts +19 -6
  51. package/packages/agent-team/lib/types/recovery.d.ts.map +1 -1
  52. package/packages/agent-team/lib/types/types/entities.d.ts +1 -8
  53. package/packages/agent-team/lib/types/types/entities.d.ts.map +1 -1
  54. package/packages/agent-team/lib/types/types/requests-results.d.ts +94 -2
  55. package/packages/agent-team/lib/types/types/requests-results.d.ts.map +1 -1
  56. package/packages/agent-team/lib/types/types.d.ts +9 -0
  57. package/packages/agent-team/lib/types/types.d.ts.map +1 -1
  58. package/packages/client-agent-team/lib/client.js +1264 -383
  59. package/packages/client-agent-team/lib/client.js.map +1 -1
  60. package/packages/client-agent-team/lib/types/client/EnvironmentCheck.d.ts +18 -0
  61. package/packages/client-agent-team/lib/types/client/EnvironmentCheck.d.ts.map +1 -0
  62. package/packages/client-agent-team/lib/types/client/EnvironmentCheck.js +58 -0
  63. package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.js +0 -5
  64. package/packages/client-agent-team/lib/types/client/TeamPresenceDot.d.ts +1 -1
  65. package/packages/client-agent-team/lib/types/client/TeamPresenceDot.d.ts.map +1 -1
  66. package/packages/client-agent-team/lib/types/client/TeamPresenceDot.js +2 -7
  67. package/packages/client-agent-team/lib/types/client/TeamSettingsSection.d.ts +26 -0
  68. package/packages/client-agent-team/lib/types/client/TeamSettingsSection.d.ts.map +1 -0
  69. package/packages/client-agent-team/lib/types/client/TeamSettingsSection.js +195 -0
  70. package/packages/client-agent-team/lib/types/client/attachment-preview.d.ts +7 -11
  71. package/packages/client-agent-team/lib/types/client/attachment-preview.d.ts.map +1 -1
  72. package/packages/client-agent-team/lib/types/client/attachment-preview.js +13 -3
  73. package/packages/client-agent-team/lib/types/client/context-judge.d.ts +64 -0
  74. package/packages/client-agent-team/lib/types/client/context-judge.d.ts.map +1 -0
  75. package/packages/client-agent-team/lib/types/client/context-judge.js +78 -0
  76. package/packages/client-agent-team/lib/types/client/environment-check.d.ts +65 -0
  77. package/packages/client-agent-team/lib/types/client/environment-check.d.ts.map +1 -0
  78. package/packages/client-agent-team/lib/types/client/environment-check.js +78 -0
  79. package/packages/client-agent-team/lib/types/client/index.d.ts.map +1 -1
  80. package/packages/client-agent-team/lib/types/client/index.js +84 -11
  81. package/packages/client-agent-team/lib/types/client/judge-form.d.ts +99 -0
  82. package/packages/client-agent-team/lib/types/client/judge-form.d.ts.map +1 -0
  83. package/packages/client-agent-team/lib/types/client/judge-form.js +208 -0
  84. package/packages/client-agent-team/lib/types/client/locales.d.ts +70 -14
  85. package/packages/client-agent-team/lib/types/client/locales.d.ts.map +1 -1
  86. package/packages/client-agent-team/lib/types/client/locales.js +76 -14
  87. package/packages/tool-agent-team/README.md +6 -3
  88. package/packages/tool-agent-team/README.zh.md +6 -3
  89. package/packages/tool-agent-team/lib/context-tools.js +148 -164
  90. package/packages/tool-agent-team/lib/index.js +17 -2
  91. package/packages/tool-agent-team/lib/types/context-tools.d.ts +32 -7
  92. package/packages/tool-agent-team/lib/types/context-tools.d.ts.map +1 -1
  93. package/packages/tool-agent-team/lib/types/index.d.ts.map +1 -1
  94. package/packages/agent-team/lib/pressure-policy.js +0 -230
  95. package/packages/agent-team/lib/types/pressure-policy.d.ts +0 -130
  96. package/packages/agent-team/lib/types/pressure-policy.d.ts.map +0 -1
  97. package/packages/client-agent-team/lib/types/client/HumanSettingsSection.d.ts +0 -15
  98. package/packages/client-agent-team/lib/types/client/HumanSettingsSection.d.ts.map +0 -1
  99. package/packages/client-agent-team/lib/types/client/HumanSettingsSection.js +0 -115
@@ -0,0 +1,18 @@
1
+ import type { PropsLocale } from '@deepseek-ai/dsh-client-ui-slots';
2
+ import { type TeamEnvironmentSource } from './environment-check.ts';
3
+ /**
4
+ * The local environment check: which DSH line this installation runs against,
5
+ * and whether that line is inside the range the bundle declares.
6
+ *
7
+ * Three verdicts, never a fourth, and never a guess: a fact the Host could not
8
+ * establish is `undetermined`, not a mismatch. Every verdict is stated as text
9
+ * plus an icon, so the state survives a reader who cannot tell the colors
10
+ * apart. The support line is written in words rather than as a bare semver
11
+ * range, and the tested combination is printed only when the Host could derive
12
+ * both of its versions — the page never supplies a version of its own.
13
+ */
14
+ export type EnvironmentCheckProps = PropsLocale<'team'> & {
15
+ readonly environment: TeamEnvironmentSource;
16
+ };
17
+ export declare function EnvironmentCheck({ t, environment }: EnvironmentCheckProps): import("react").JSX.Element | null;
18
+ //# sourceMappingURL=EnvironmentCheck.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EnvironmentCheck.d.ts","sourceRoot":"","sources":["../../../src/client/EnvironmentCheck.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kCAAkC,CAAA;AACnE,OAAO,EAAuB,KAAK,qBAAqB,EAAE,MAAM,wBAAwB,CAAA;AAGxF;;;;;;;;;;GAUG;AAEH,MAAM,MAAM,qBAAqB,GAC7B,WAAW,CAAC,MAAM,CAAC,GACnB;IAAE,QAAQ,CAAC,WAAW,EAAE,qBAAqB,CAAA;CAAE,CAAA;AAyBnD,wBAAgB,gBAAgB,CAAC,EAAE,CAAC,EAAE,WAAW,EAAE,EAAE,qBAAqB,sCAkDzE"}
@@ -0,0 +1,58 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEnvironmentCheck } from "./environment-check.js";
3
+ import css from './environment-check.module.css';
4
+ /** The three glyphs, drawn at the 16px seat the settings page uses for state marks. */
5
+ const ICONS = {
6
+ ok: _jsx("path", { d: "M3 8.5 6.5 12 13 4.5" }),
7
+ warn: _jsxs(_Fragment, { children: [_jsx("path", { d: "M8 2.5 14.6 13.6H1.4z" }), _jsx("path", { d: "M8 6.6v3.2" }), _jsx("path", { d: "M8 11.9h.01" })] }),
8
+ unknown: _jsxs(_Fragment, { children: [_jsx("circle", { cx: "8", cy: "8", r: "6" }), _jsx("path", { d: "M5.6 8h4.8" })] }),
9
+ };
10
+ function EnvironmentIcon({ glyph }) {
11
+ return _jsx("svg", { className: css.icon, viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: ICONS[glyph] });
12
+ }
13
+ /** The release notes this bundle's own repository keeps, one click from the version footnote. */
14
+ const RELEASE_NOTES_URL = 'https://github.com/wowyuarm/dsh-agent-team/releases';
15
+ export function EnvironmentCheck({ t, environment }) {
16
+ const { report } = useEnvironmentCheck(environment);
17
+ // Nothing to state before the first read settles, and nothing to state if it
18
+ // never lands: the block is informational and its absence is not an error.
19
+ if (report === undefined)
20
+ return null;
21
+ const range = report.supportRange;
22
+ const running = report.dshVersion;
23
+ // The tested combination appears only when both of its versions were derived:
24
+ // the installed manifest's own version, and the range's lower bound, which is
25
+ // the certified line. A missing one withholds the line rather than inviting a
26
+ // hand-written version onto the page.
27
+ const certified = report.certifiedDshVersion;
28
+ const bundleVersion = report.bundleVersion;
29
+ // Both sides of the line have to be derived: `'unknown'` is the data layer's
30
+ // word for a version that could not be read, and it never reaches the page.
31
+ const tested = certified !== undefined && bundleVersion !== undefined && bundleVersion !== 'unknown';
32
+ const title = report.verdict === 'ok'
33
+ ? t('environmentOkTitle')
34
+ : report.verdict === 'out-of-range'
35
+ ? t('environmentOutOfRangeTitle')
36
+ : t('environmentUndeterminedTitle');
37
+ const lines = [];
38
+ if (report.verdict === 'ok') {
39
+ if (running !== undefined)
40
+ lines.push(t('environmentOkDetail', { version: running }));
41
+ }
42
+ else if (report.verdict === 'out-of-range') {
43
+ if (running !== undefined)
44
+ lines.push(t('environmentOutOfRangeDetail', { version: running }));
45
+ if (range !== undefined)
46
+ lines.push(t('environmentRange', { lower: range.lower, upper: range.upper }));
47
+ if (tested && certified !== undefined && bundleVersion !== undefined) {
48
+ lines.push(t('environmentTested', { bundle: bundleVersion, dsh: certified }));
49
+ }
50
+ }
51
+ else {
52
+ lines.push(t('environmentUndeterminedDetail'));
53
+ }
54
+ // `data-environment` carries the verdict the way the rest of the Client marks
55
+ // a state for assertions; the block also renders nothing at all before a read
56
+ // lands, so a journey can wait on this attribute rather than on prose.
57
+ return _jsxs("div", { className: css.block, "data-environment": report.verdict, children: [_jsx(EnvironmentIcon, { glyph: report.verdict === 'ok' ? 'ok' : report.verdict === 'out-of-range' ? 'warn' : 'unknown' }), _jsxs("div", { className: css.body, children: [_jsx("p", { className: css.title, children: title }), lines.map((line, index) => _jsx("p", { className: css.detail, children: line }, index)), report.verdict !== 'ok' && _jsx("p", { className: css.action, children: _jsx("a", { className: css.link, href: RELEASE_NOTES_URL, target: "_blank", rel: "noreferrer", children: t('environmentReleaseNotes') }) })] })] });
58
+ }
@@ -230,11 +230,6 @@ function AgentRow({ workspaceId, leaveWorkspace, status, current, updateMember,
230
230
  return;
231
231
  }
232
232
  if (result.value.status.availability === 'unavailable') {
233
- const diagnostic = result.value.status.diagnostic;
234
- if (diagnostic?.class === 'session-refused' && diagnostic.remediable === false) {
235
- setRowAlert(t('restartRefusedNoRestart', { detail: diagnosticText(result.value.status) }));
236
- return;
237
- }
238
233
  setRowAlert(t('restartStillUnavailable', { diagnostic: diagnosticText(result.value.status) || t('statusUnavailable') }));
239
234
  return;
240
235
  }
@@ -7,7 +7,7 @@ export declare function diagnosticText(status: Pick<AgentTeamClientMemberStatus,
7
7
  /**
8
8
  * Whether the restart action can help an unavailable Member: it heals
9
9
  * transient and repairable failures, but not a transient rollover window
10
- * (which resolves on its own) or a refusal already proven non-remediable.
10
+ * (which resolves on its own).
11
11
  */
12
12
  export declare function restartOffered(status: AgentTeamClientMemberStatus): boolean;
13
13
  /** Shared presence → indicator mapping for dots and avatar badges. */
@@ -1 +1 @@
1
- {"version":3,"file":"TeamPresenceDot.d.ts","sourceRoot":"","sources":["../../../src/client/TeamPresenceDot.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,gCAAgC,CAAA;AACjF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAElD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAA;AAG3D,wBAAgB,aAAa,CAAC,MAAM,EAAE,2BAA2B,EAAE,CAAC,EAAE,gBAAgB,CAAC,GAAG,CAAC,GAAG,MAAM,CAKnG;AAED,oHAAoH;AACpH,wBAAgB,cAAc,CAAC,MAAM,EAAE,IAAI,CAAC,2BAA2B,EAAE,YAAY,CAAC,GAAG,MAAM,CAI9F;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,2BAA2B,GAAG,OAAO,CAK3E;AAED,sEAAsE;AACtE,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,2BAA2B,CAAC,UAAU,CAAC,GAAG,iBAAiB,CAErG;AAED,wBAAgB,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,EAAE;IAC7C,QAAQ,CAAC,MAAM,EAAE,2BAA2B,CAAA;IAC5C,QAAQ,CAAC,CAAC,EAAE,gBAAgB,CAAC,GAAG,CAAC,CAAA;CAClC,+BASA"}
1
+ {"version":3,"file":"TeamPresenceDot.d.ts","sourceRoot":"","sources":["../../../src/client/TeamPresenceDot.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,gCAAgC,CAAA;AACjF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAElD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAA;AAG3D,wBAAgB,aAAa,CAAC,MAAM,EAAE,2BAA2B,EAAE,CAAC,EAAE,gBAAgB,CAAC,GAAG,CAAC,GAAG,MAAM,CAKnG;AAED,oHAAoH;AACpH,wBAAgB,cAAc,CAAC,MAAM,EAAE,IAAI,CAAC,2BAA2B,EAAE,YAAY,CAAC,GAAG,MAAM,CAI9F;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,2BAA2B,GAAG,OAAO,CAE3E;AAED,sEAAsE;AACtE,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,2BAA2B,CAAC,UAAU,CAAC,GAAG,iBAAiB,CAErG;AAED,wBAAgB,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,EAAE;IAC7C,QAAQ,CAAC,MAAM,EAAE,2BAA2B,CAAA;IAC5C,QAAQ,CAAC,CAAC,EAAE,gBAAgB,CAAC,GAAG,CAAC,CAAA;CAClC,+BASA"}
@@ -18,15 +18,10 @@ export function diagnosticText(status) {
18
18
  /**
19
19
  * Whether the restart action can help an unavailable Member: it heals
20
20
  * transient and repairable failures, but not a transient rollover window
21
- * (which resolves on its own) or a refusal already proven non-remediable.
21
+ * (which resolves on its own).
22
22
  */
23
23
  export function restartOffered(status) {
24
- const diagnostic = status.diagnostic;
25
- if (diagnostic === undefined)
26
- return true;
27
- if (diagnostic.class === 'rollover')
28
- return false;
29
- return !(diagnostic.class === 'session-refused' && diagnostic.remediable === false);
24
+ return status.diagnostic?.class !== 'rollover';
30
25
  }
31
26
  /** Shared presence → indicator mapping for dots and avatar badges. */
32
27
  export function presenceDotState(presence) {
@@ -0,0 +1,26 @@
1
+ import type { PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots';
2
+ import { type TeamHumanIdentityFace } from './human-identity.ts';
3
+ import type { TeamEnvironmentSource } from './environment-check.ts';
4
+ import { type TeamContextJudgeSource } from './context-judge.ts';
5
+ import type { TeamJudgeFormSource } from './judge-form.ts';
6
+ export interface TeamSettingsInjected {
7
+ /** The shared Human identity projection: profile facts plus post-write refresh. */
8
+ identity: TeamHumanIdentityFace;
9
+ /** Persist one renamed display name; the failure message when the Host refuses it. */
10
+ saveName: (name: string) => Promise<string | undefined>;
11
+ /** Upload one image file, persist its reference; the failure message when it does not stick. */
12
+ uploadAvatar: (file: File) => Promise<string | undefined>;
13
+ /** Clear the avatar; the identity falls back to the initial. */
14
+ removeAvatar: () => Promise<string | undefined>;
15
+ /** The local environment check, a read-only projection of the installation. */
16
+ environment: TeamEnvironmentSource;
17
+ /** The judge's live status: whether one is reachable right now. */
18
+ judge: TeamContextJudgeSource;
19
+ /** The staged form over the judge's endpoint; it holds nothing while this deployment serves no settings document. */
20
+ judgeForm: TeamJudgeFormSource;
21
+ /** Whether the settings document holds a key literal for the judge. */
22
+ keyConfigured: () => boolean;
23
+ }
24
+ export type TeamSettingsProps = PropsRuntime<'settings.section'> & PropsLocale<'team'> & TeamSettingsInjected;
25
+ export declare function TeamSettingsSection(props: TeamSettingsProps): import("react").JSX.Element;
26
+ //# sourceMappingURL=TeamSettingsSection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TeamSettingsSection.d.ts","sourceRoot":"","sources":["../../../src/client/TeamSettingsSection.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,kCAAkC,CAAA;AAMjF,OAAO,EAAoB,KAAK,qBAAqB,EAAE,MAAM,qBAAqB,CAAA;AAElF,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAA;AAEnE,OAAO,EAAmB,KAAK,sBAAsB,EAAE,MAAM,oBAAoB,CAAA;AACjF,OAAO,KAAK,EAAiC,mBAAmB,EAAE,MAAM,iBAAiB,CAAA;AAoBzF,MAAM,WAAW,oBAAoB;IACnC,mFAAmF;IACnF,QAAQ,EAAE,qBAAqB,CAAA;IAC/B,sFAAsF;IACtF,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAA;IACvD,gGAAgG;IAChG,YAAY,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAA;IACzD,gEAAgE;IAChE,YAAY,EAAE,MAAM,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAA;IAC/C,+EAA+E;IAC/E,WAAW,EAAE,qBAAqB,CAAA;IAClC,mEAAmE;IACnE,KAAK,EAAE,sBAAsB,CAAA;IAC7B,qHAAqH;IACrH,SAAS,EAAE,mBAAmB,CAAA;IAC9B,uEAAuE;IACvE,aAAa,EAAE,MAAM,OAAO,CAAA;CAC7B;AAED,MAAM,MAAM,iBAAiB,GACzB,YAAY,CAAC,kBAAkB,CAAC,GAChC,WAAW,CAAC,MAAM,CAAC,GACnB,oBAAoB,CAAA;AAsBxB,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,iBAAiB,+BA+K3D"}
@@ -0,0 +1,195 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useRef, useState, useSyncExternalStore } from 'react';
3
+ import { Button, IconChevronDownOutlineRegular, IconChevronRightOutlineRegular, Input, SettingsForm, SettingsSecretField, SettingsValueField, } from '@deepseek-ai/dsh-client-ui-primitives';
4
+ import { useHumanIdentity } from "./human-identity.js";
5
+ import { EnvironmentCheck } from "./EnvironmentCheck.js";
6
+ import { useAvatarImage } from "./avatar-image.js";
7
+ import { useContextJudge } from "./context-judge.js";
8
+ import css from './team-settings.module.css';
9
+ /**
10
+ * The Team's settings page: the Human's display name and avatar, the endpoint
11
+ * the relatedness judge calls, the local environment check, and the version
12
+ * footnote.
13
+ *
14
+ * The page owns no durable fact and no copy of one, and it holds its two
15
+ * writable groups apart because they are two different documents: identity
16
+ * arrives from the shared identity projection and writes through the Team
17
+ * Remote, while the judge's endpoint is one group of the Team row's settings
18
+ * section and writes through the settings provider. Neither group renders the
19
+ * other's fields, and a failed write in either keeps the typed value and
20
+ * reports the reason it was refused.
21
+ */
22
+ /** Host-side avatar ceiling (`ATTACHMENT_MAX_BYTES`): the settings page refuses larger files before the round trip. */
23
+ const AVATAR_MAX_BYTES = 10 * 1024 * 1024;
24
+ /**
25
+ * Run one durable write and reduce every failure to the message the page
26
+ * reports. A rejected Remote call (a dropped carrier, a refused write) is a
27
+ * failure like any other: without this the field would sit on "saving" forever
28
+ * and report nothing.
29
+ */
30
+ async function failureOf(action) {
31
+ try {
32
+ return await action();
33
+ }
34
+ catch (error) {
35
+ return error instanceof Error ? error.message : String(error);
36
+ }
37
+ }
38
+ /** First visible character of a display name, or the neutral `H` before one is known. */
39
+ function avatarInitial(name) {
40
+ const trimmed = (name ?? '').replace(/^@/, '').trim();
41
+ return trimmed === '' ? 'H' : trimmed.slice(0, 1).toUpperCase();
42
+ }
43
+ export function TeamSettingsSection(props) {
44
+ const { t, identity } = props;
45
+ const profile = useHumanIdentity(identity);
46
+ // The endpoint form is observed rather than received: the settings service it
47
+ // is built over can arrive after this entry's injected props were computed,
48
+ // and those props are cached for the entry's lifetime, so a value handed over
49
+ // once would never appear.
50
+ const judgeForm = useSyncExternalStore(props.judgeForm.subscribe, props.judgeForm.getSnapshot);
51
+ // Undefined means "follow the Host value": the field re-syncs whenever the
52
+ // profile changes underneath, without a background read clobbering a name
53
+ // the reader is still editing.
54
+ const [draft, setDraft] = useState(undefined);
55
+ const [saving, setSaving] = useState(false);
56
+ const [uploading, setUploading] = useState(false);
57
+ const [notice, setNotice] = useState(null);
58
+ const filePicker = useRef(null);
59
+ const name = draft ?? profile.name ?? '';
60
+ const dirty = draft !== undefined && draft.trim() !== (profile.name ?? '');
61
+ const emptyName = draft !== undefined && draft.trim() === '';
62
+ const hasAvatar = profile.avatarRef !== undefined;
63
+ // The circle answers to decoded bytes, not to a stored reference: the Host
64
+ // accepts any `image/…` payload, so a file this browser cannot read has to
65
+ // fall back to the initial exactly as a removed one does.
66
+ const avatar = useAvatarImage(profile.avatarUrl);
67
+ const submitName = async () => {
68
+ if (!dirty || emptyName || saving)
69
+ return;
70
+ setSaving(true);
71
+ setNotice(null);
72
+ const failure = await failureOf(() => props.saveName(name.trim()));
73
+ setSaving(false);
74
+ if (failure !== undefined) {
75
+ setNotice(t('humanSettingsNameFailed', { message: failure }));
76
+ return;
77
+ }
78
+ setDraft(undefined);
79
+ };
80
+ const pickAvatar = async (file) => {
81
+ if (file === undefined)
82
+ return;
83
+ setNotice(null);
84
+ if (!file.type.startsWith('image/')) {
85
+ setNotice(t('humanSettingsAvatarNotImage'));
86
+ return;
87
+ }
88
+ if (file.size > AVATAR_MAX_BYTES) {
89
+ setNotice(t('humanSettingsAvatarTooLarge'));
90
+ return;
91
+ }
92
+ setUploading(true);
93
+ const failure = await failureOf(() => props.uploadAvatar(file));
94
+ setUploading(false);
95
+ if (failure !== undefined)
96
+ setNotice(t('humanSettingsAvatarFailed', { message: failure }));
97
+ };
98
+ const removeAvatar = async () => {
99
+ setNotice(null);
100
+ const failure = await failureOf(() => props.removeAvatar());
101
+ if (failure !== undefined)
102
+ setNotice(t('humanSettingsAvatarFailed', { message: failure }));
103
+ };
104
+ // The page says what it is before it says what happened: the settings nav
105
+ // lists it beside the Harness's own pages, so the title alone leaves "whose
106
+ // Team is this, and where does a change apply?" unanswered — and every state,
107
+ // including a failed read, has to answer it.
108
+ const pageHeader = _jsxs(_Fragment, { children: [_jsx("h2", { className: css.heading, children: t('teamSettingsTitle') }), _jsx("p", { className: css.intro, children: t('teamSettingsIntro') })] });
109
+ if (profile.status === 'loading' && profile.name === undefined) {
110
+ return _jsxs("div", { className: css.section, children: [pageHeader, _jsx("p", { className: css.state, role: "status", children: t('humanSettingsLoading') })] });
111
+ }
112
+ if (profile.name === undefined) {
113
+ return _jsxs("div", { className: css.section, children: [pageHeader, _jsx("p", { className: css.state, role: "alert", children: t('humanSettingsUnavailable', { message: profile.error ?? '' }) }), _jsx("div", { className: css.stateAction, children: _jsx(Button, { variant: "outline", onClick: () => { void identity.refresh(); }, children: t('retry') }) })] });
114
+ }
115
+ return _jsxs("div", { className: css.section, children: [pageHeader, _jsxs("div", { className: css.rows, children: [_jsxs("div", { className: css.row, children: [_jsxs("div", { className: css.rowText, children: [_jsx("div", { className: css.title, children: t('humanSettingsName') }), _jsx("div", { className: css.desc, children: t('humanSettingsNameHint') })] }), _jsxs("form", { className: css.controls, onSubmit: (event) => {
116
+ event.preventDefault();
117
+ void submitName();
118
+ }, children: [_jsx(Input, { className: css.nameInput, "aria-label": t('humanSettingsName'), "aria-invalid": emptyName || undefined, value: name, disabled: saving, onChange: (event) => { setDraft(event.target.value); } }), _jsx(Button, { type: "submit", variant: "primary", disabled: saving || !dirty || emptyName, children: saving ? t('humanSettingsSaving') : t('humanSettingsSave') })] })] }), _jsxs("div", { className: css.row, children: [_jsxs("div", { className: css.rowText, children: [_jsx("div", { className: css.title, children: t('humanSettingsAvatar') }), _jsx("div", { className: css.desc, children: t('humanSettingsAvatarHint') })] }), _jsxs("div", { className: css.controls, children: [avatar.src === undefined
119
+ ? _jsx("span", { className: `${css.identity} ${css.identityFallback}`, "data-avatar": "initial", "aria-hidden": "true", children: avatarInitial(profile.name) })
120
+ : _jsx("img", { className: `${css.identity} ${css.identityImage}`, "data-avatar": "image", src: avatar.src, alt: "", onError: avatar.failed }), _jsx("input", { ref: filePicker, type: "file", accept: "image/*", tabIndex: -1, "aria-hidden": "true", hidden: true, onChange: (event) => {
121
+ void pickAvatar(event.target.files?.[0]);
122
+ event.target.value = '';
123
+ } }), _jsx(Button, { variant: "outline", disabled: uploading, onClick: () => { filePicker.current?.click(); }, children: uploading ? t('humanSettingsUploading') : hasAvatar ? t('humanSettingsReplace') : t('humanSettingsUpload') }), hasAvatar && _jsx(Button, { disabled: uploading, onClick: () => { void removeAvatar(); }, children: t('humanSettingsRemoveAvatar') })] })] })] }), judgeForm === undefined
124
+ ? null
125
+ : _jsx(JudgeGroup, { t: t, form: judgeForm, judge: props.judge, keyConfigured: props.keyConfigured }), _jsx("div", { className: css.environment, children: _jsx(EnvironmentCheck, { t: t, environment: props.environment }) }), _jsxs("div", { className: css.footnote, children: [_jsx("span", { children: t('humanSettingsVersion', { version: profile.version ?? '' }) }), _jsx("span", { "aria-hidden": "true", children: "\u00B7" }), _jsx("a", { className: css.link, href: profile.repoUrl ?? '', target: "_blank", rel: "noreferrer", children: "GitHub" }), profile.updateAvailable && profile.latestVersion !== undefined
126
+ ? _jsx("a", { className: css.link, href: `${profile.repoUrl ?? ''}/releases`, target: "_blank", rel: "noreferrer", children: t('humanSettingsUpdateAvailable', { version: profile.latestVersion }) })
127
+ : null] }), emptyName && _jsx("p", { className: css.notice, children: t('humanSettingsNameEmpty') }), notice === null ? null : _jsx("p", { className: css.notice, role: "alert", children: notice })] });
128
+ }
129
+ /**
130
+ * The judge's endpoint as one collapsed row: the state of jev on the line
131
+ * itself, and the three controls that decide it behind a disclosure.
132
+ *
133
+ * Collapsed is the default because the endpoint is set once and then left
134
+ * alone, while its state is worth seeing every time the page opens — so the
135
+ * row carries the answer and the controls wait. The status is read rather than
136
+ * derived, because a deployment may mount a judge from configuration this page
137
+ * cannot see, and it states what the Host answered rather than predicting it.
138
+ */
139
+ function JudgeGroup(props) {
140
+ const { t, form, judge, keyConfigured } = props;
141
+ const { report } = useContextJudge(judge);
142
+ const state = useSyncExternalStore(form.subscribe, form.getSnapshot, form.getSnapshot);
143
+ const actions = form.actions();
144
+ const [open, setOpen] = useState(false);
145
+ const [clearing, setClearing] = useState(false);
146
+ const [clearFailed, setClearFailed] = useState(false);
147
+ const configured = keyConfigured();
148
+ // Every control answers to the document: a read-only document disables them
149
+ // all rather than letting a save fail after the fact.
150
+ const disabled = !state.shell.writable;
151
+ const labels = {
152
+ overridden: t('teamSettingsOverridden'),
153
+ reset: t('teamSettingsReset'),
154
+ invalid: t('teamSettingsInvalid'),
155
+ };
156
+ const field = (name) => state.fields[name];
157
+ const clear = async () => {
158
+ setClearing(true);
159
+ setClearFailed(false);
160
+ const landed = await form.clearKey();
161
+ setClearing(false);
162
+ if (!landed)
163
+ setClearFailed(true);
164
+ };
165
+ return _jsxs("div", { className: css.group, children: [_jsxs("button", { type: "button", className: css.groupToggle, "aria-expanded": open, onClick: () => { setOpen(value => !value); }, children: [_jsx("span", { className: css.groupChevron, "aria-hidden": "true", children: open ? _jsx(IconChevronDownOutlineRegular, { size: 14 }) : _jsx(IconChevronRightOutlineRegular, { size: 14 }) }), _jsx("span", { className: css.groupTitle, children: t('teamJudgeGroupTitle') }), _jsx(JudgeStatus, { t: t, report: report })] }), open
166
+ ? _jsxs("div", { className: css.groupBody, role: "group", "aria-label": t('teamJudgeGroupTitle'), "data-team-judge": true, children: [_jsx("p", { className: css.groupHint, children: t('teamJudgeGroupHint') }), _jsxs(SettingsForm, { labels: {
167
+ unavailable: t('teamSettingsUnavailable'),
168
+ readOnly: t('teamSettingsReadOnly'),
169
+ saveFailed: t('teamSettingsSaveFailed'),
170
+ save: t('teamSettingsSave'),
171
+ saving: t('teamSettingsSaving'),
172
+ }, state: state.shell, onSave: actions.save, onDiscard: actions.discard, children: [_jsx(SettingsValueField, { id: "team-judge-api-base", label: t('teamJudgeApiBase'), text: field('apiBase').text, overridden: field('apiBase').overridden, invalid: field('apiBase').invalid, overriddenLabel: labels.overridden, resetLabel: labels.reset, invalidLabel: labels.invalid, disabled: disabled, onEdit: text => { actions.edit('apiBase', text); }, onReset: () => { actions.resetField('apiBase'); } }), _jsx(SettingsValueField, { id: "team-judge-model", label: t('teamJudgeModel'), text: field('model').text, overridden: field('model').overridden, invalid: field('model').invalid, overriddenLabel: labels.overridden, resetLabel: labels.reset, invalidLabel: labels.invalid, disabled: disabled, onEdit: text => { actions.edit('model', text); }, onReset: () => { actions.resetField('model'); } }), _jsx(SettingsSecretField, { id: "team-judge-api-key", label: t('teamJudgeKey'), hint: t('teamJudgeKeyHint'), text: field('apiKey').text, configured: configured, stateLabel: configured ? t('teamJudgeKeySet') : t('teamJudgeKeyUnset'), disabled: disabled, onEdit: text => { actions.edit('apiKey', text); } }), configured
173
+ ? _jsxs("div", { className: css.clearRow, children: [_jsx(Button, { variant: "outline", size: "sm", disabled: disabled || state.shell.dirty || clearing, onClick: () => { void clear(); }, children: t('teamJudgeClearKey') }), _jsx("p", { className: css.clearHint, children: state.shell.dirty ? t('teamJudgeClearBlocked') : t('teamJudgeClearHint') }), clearFailed ? _jsx("p", { className: css.notice, role: "status", children: t('teamJudgeClearFailed') }) : null] })
174
+ : null] })] })
175
+ : null] });
176
+ }
177
+ /** The three glyphs, drawn at the 16px seat the settings page uses for state marks. */
178
+ const ICONS = {
179
+ ok: _jsx("path", { d: "M3 8.5 6.5 12 13 4.5" }),
180
+ off: _jsxs(_Fragment, { children: [_jsx("circle", { cx: "8", cy: "8", r: "6" }), _jsx("path", { d: "M5.6 8h4.8" })] }),
181
+ warn: _jsxs(_Fragment, { children: [_jsx("path", { d: "M8 2.5 14.6 13.6H1.4z" }), _jsx("path", { d: "M8 6.6v3.2" }), _jsx("path", { d: "M8 11.9h.01" })] }),
182
+ };
183
+ /**
184
+ * The judge's live status in one line, inside the row that carries it. The
185
+ * verdict is stated as text and an icon, never by color alone, and nothing is
186
+ * said before the first read settles: the row then stands as a bare title.
187
+ */
188
+ function JudgeStatus({ t, report }) {
189
+ if (report === undefined)
190
+ return null;
191
+ const verdict = report.enabled ? 'enabled' : report.reason ?? 'unavailable';
192
+ return _jsxs("span", { className: css.status, "data-context-judge": verdict, children: [_jsx("svg", { className: css.statusIcon, viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: ICONS[report.enabled ? 'ok' : report.reason === 'no-key' ? 'off' : 'warn'] }), _jsx("span", { children: report.enabled
193
+ ? t('teamJudgeReady')
194
+ : report.reason === 'no-key' ? t('teamJudgeNoKey') : t('teamJudgeUnavailable') })] });
195
+ }
@@ -1,16 +1,13 @@
1
+ import type { RemoteResult } from '@deepseek-ai/dsh-typert-protocol';
1
2
  import type { AgentTeamAttachmentId, AgentTeamGetAttachmentRequest, AgentTeamGetAttachmentResult } from '@wowyuarm/dsh-agent-team/types';
2
3
  /** Base64 one file payload in chunks so large uploads stay off the call-stack limit. */
3
4
  export declare function bytesToBase64(bytes: Uint8Array): string;
4
- /** Mirrors the slot's Remote result union without importing the slots module. */
5
- type GetAttachment = (request: AgentTeamGetAttachmentRequest) => Promise<{
6
- ok: true;
7
- value: AgentTeamGetAttachmentResult;
8
- } | {
9
- ok: false;
10
- error: {
11
- message: string;
12
- };
13
- }>;
5
+ /**
6
+ * The slot's Remote result union without importing the slots module: an
7
+ * expected failure arrives as a stable `code`, and any other error stays
8
+ * unknown to this cache.
9
+ */
10
+ export type GetAttachment = (request: AgentTeamGetAttachmentRequest) => Promise<RemoteResult<AgentTeamGetAttachmentResult>>;
14
11
  export declare function cachedAttachmentDataUrl(attachmentId: AgentTeamAttachmentId): string | null | undefined;
15
12
  export declare function loadAttachmentDataUrl(getAttachment: GetAttachment, attachment: {
16
13
  attachmentId: AgentTeamAttachmentId;
@@ -18,5 +15,4 @@ export declare function loadAttachmentDataUrl(getAttachment: GetAttachment, atta
18
15
  }): Promise<string | null>;
19
16
  /** Human-readable byte size for attachment chips. */
20
17
  export declare function formatByteSize(byteSize: number): string;
21
- export {};
22
18
  //# sourceMappingURL=attachment-preview.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"attachment-preview.d.ts","sourceRoot":"","sources":["../../../src/client/attachment-preview.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,6BAA6B,EAAE,4BAA4B,EAAE,MAAM,gCAAgC,CAAA;AAExI,wFAAwF;AACxF,wBAAgB,aAAa,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAOvD;AAED,iFAAiF;AACjF,KAAK,aAAa,GAAG,CAAC,OAAO,EAAE,6BAA6B,KAAK,OAAO,CAAC;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,4BAA4B,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,CAAC,CAAA;AASvK,wBAAgB,uBAAuB,CAAC,YAAY,EAAE,qBAAqB,GAAG,MAAM,GAAG,IAAI,GAAG,SAAS,CAEtG;AAED,wBAAsB,qBAAqB,CAAC,aAAa,EAAE,aAAa,EAAE,UAAU,EAAE;IAAE,YAAY,EAAE,qBAAqB,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAOxK;AAED,qDAAqD;AACrD,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAIvD"}
1
+ {"version":3,"file":"attachment-preview.d.ts","sourceRoot":"","sources":["../../../src/client/attachment-preview.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kCAAkC,CAAA;AACpE,OAAO,KAAK,EAAE,qBAAqB,EAAE,6BAA6B,EAAE,4BAA4B,EAAE,MAAM,gCAAgC,CAAA;AAExI,wFAAwF;AACxF,wBAAgB,aAAa,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAOvD;AAED;;;;GAIG;AACH,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,EAAE,6BAA6B,KAAK,OAAO,CAAC,YAAY,CAAC,4BAA4B,CAAC,CAAC,CAAA;AAW3H,wBAAgB,uBAAuB,CAAC,YAAY,EAAE,qBAAqB,GAAG,MAAM,GAAG,IAAI,GAAG,SAAS,CAEtG;AAED,wBAAsB,qBAAqB,CAAC,aAAa,EAAE,aAAa,EAAE,UAAU,EAAE;IAAE,YAAY,EAAE,qBAAqB,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAcxK;AAED,qDAAqD;AACrD,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAIvD"}
@@ -9,8 +9,10 @@ export function bytesToBase64(bytes) {
9
9
  }
10
10
  /**
11
11
  * Thumbnail data URLs live in one session-wide cache: message lists re-render
12
- * often, and each miss costs a Host round-trip plus a base64 decode. Failed
13
- * loads (bytes already GC'd) are cached as `null` so the chip fallback is stable.
12
+ * often, and each miss costs a Host round-trip plus a base64 decode. Only a
13
+ * settled answer is cached: a gone entry resolves to `null` so the chip stays
14
+ * on the expired label, while an unknown failure caches nothing and lets a
15
+ * later mount retry the Host.
14
16
  */
15
17
  const dataUrlCache = new Map();
16
18
  export function cachedAttachmentDataUrl(attachmentId) {
@@ -21,7 +23,15 @@ export async function loadAttachmentDataUrl(getAttachment, attachment) {
21
23
  if (cached !== undefined)
22
24
  return cached;
23
25
  const result = await getAttachment({ attachmentId: attachment.attachmentId });
24
- const url = result.ok && attachment.mediaType.startsWith('image/') ? `data:${attachment.mediaType};base64,${result.value.bytesBase64}` : null;
26
+ if (!result.ok) {
27
+ // Discriminate by code, never by message: the stable not-found code is a
28
+ // settled fact, everything else (carrier, Host, unknown future codes) is
29
+ // transient from this cache's point of view.
30
+ if (result.error.code === 'team/attachment-not-found')
31
+ dataUrlCache.set(attachment.attachmentId, null);
32
+ return null;
33
+ }
34
+ const url = attachment.mediaType.startsWith('image/') ? `data:${attachment.mediaType};base64,${result.value.bytesBase64}` : null;
25
35
  dataUrlCache.set(attachment.attachmentId, url);
26
36
  return url;
27
37
  }
@@ -0,0 +1,64 @@
1
+ import type { RemoteResult } from '@deepseek-ai/dsh-typert-protocol';
2
+ import type { AgentTeamContextJudgeResult } from '@wowyuarm/dsh-agent-team/types';
3
+ /**
4
+ * The Client's one projection of the long-gap gate's judge.
5
+ *
6
+ * The settings page stages the row configuration that mounts the judge, but it
7
+ * cannot state whether a judge is actually reachable: `enabled` is a fact about
8
+ * the running Host, and a deployment may mount one from configuration the page
9
+ * never sees. So this projection is read rather than derived, and every write
10
+ * that can mount or unmount the judge re-reads it instead of predicting the
11
+ * outcome.
12
+ *
13
+ * Reads are demand-driven off the first subscriber, so an ordinary conversation
14
+ * never calls the Remote.
15
+ */
16
+ /** One read of the judge status, replaced wholesale on every change. */
17
+ export interface TeamContextJudgeSnapshot {
18
+ /** `loading` until the first read settles, `ready` once it has. */
19
+ readonly status: 'loading' | 'ready';
20
+ /** The status to render; undefined until the first read settles. */
21
+ readonly report?: AgentTeamContextJudgeResult | undefined;
22
+ }
23
+ /** Read-side face the settings page binds. */
24
+ export interface TeamContextJudgeSource {
25
+ getSnapshot(): TeamContextJudgeSnapshot;
26
+ subscribe(listener: () => void): () => void;
27
+ }
28
+ /** Host call the projection reads through; one loader per Client context. */
29
+ export interface TeamContextJudgeLoader {
30
+ loadContextJudge: () => Promise<RemoteResult<AgentTeamContextJudgeResult>>;
31
+ }
32
+ export declare class TeamContextJudgeCheck implements TeamContextJudgeSource {
33
+ private snapshot;
34
+ private readonly listeners;
35
+ private reading;
36
+ private readonly loader;
37
+ constructor(loader: TeamContextJudgeLoader);
38
+ readonly getSnapshot: () => TeamContextJudgeSnapshot;
39
+ /**
40
+ * Observe the status, starting the first read when nobody has read yet.
41
+ * @param listener - invoked after every snapshot replacement.
42
+ * @returns the disposer removing this listener.
43
+ */
44
+ readonly subscribe: (listener: () => void) => (() => void);
45
+ /**
46
+ * Read the Host status. Concurrent callers share one round trip, and a failed
47
+ * read keeps the last accepted report — the block never blanks out over a
48
+ * background read.
49
+ * @returns settlement of this read (or of the read already in flight).
50
+ */
51
+ refresh(): Promise<void>;
52
+ dispose(): void;
53
+ private read;
54
+ /**
55
+ * Settle a read that produced no report, so a reader stops waiting on a
56
+ * `loading` that will never finish. A report that already stands is left
57
+ * untouched: a failed background read never blanks the block.
58
+ */
59
+ private fail;
60
+ private commit;
61
+ }
62
+ /** Subscribe one rendered block to the judge status. */
63
+ export declare function useContextJudge(judge: TeamContextJudgeSource): TeamContextJudgeSnapshot;
64
+ //# sourceMappingURL=context-judge.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context-judge.d.ts","sourceRoot":"","sources":["../../../src/client/context-judge.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kCAAkC,CAAA;AACpE,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,gCAAgC,CAAA;AAEjF;;;;;;;;;;;;GAYG;AAEH,wEAAwE;AACxE,MAAM,WAAW,wBAAwB;IACvC,mEAAmE;IACnE,QAAQ,CAAC,MAAM,EAAE,SAAS,GAAG,OAAO,CAAA;IACpC,oEAAoE;IACpE,QAAQ,CAAC,MAAM,CAAC,EAAE,2BAA2B,GAAG,SAAS,CAAA;CAC1D;AAED,8CAA8C;AAC9C,MAAM,WAAW,sBAAsB;IACrC,WAAW,IAAI,wBAAwB,CAAA;IACvC,SAAS,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAAA;CAC5C;AAED,6EAA6E;AAC7E,MAAM,WAAW,sBAAsB;IACrC,gBAAgB,EAAE,MAAM,OAAO,CAAC,YAAY,CAAC,2BAA2B,CAAC,CAAC,CAAA;CAC3E;AAID,qBAAa,qBAAsB,YAAW,sBAAsB;IAClE,OAAO,CAAC,QAAQ,CAAoC;IACpD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAwB;IAClD,OAAO,CAAC,OAAO,CAA2B;IAC1C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAwB;gBAEnC,MAAM,EAAE,sBAAsB;IAI1C,QAAQ,CAAC,WAAW,QAAO,wBAAwB,CAAiB;IAEpE;;;;OAIG;IACH,QAAQ,CAAC,SAAS,GAAI,UAAU,MAAM,IAAI,KAAG,CAAC,MAAM,IAAI,CAAC,CAIxD;IAED;;;;;OAKG;IACH,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IASxB,OAAO,IAAI,IAAI;YAID,IAAI;IAkBlB;;;;OAIG;IACH,OAAO,CAAC,IAAI;IAIZ,OAAO,CAAC,MAAM;CAIf;AAED,wDAAwD;AACxD,wBAAgB,eAAe,CAAC,KAAK,EAAE,sBAAsB,GAAG,wBAAwB,CAEvF"}
@@ -0,0 +1,78 @@
1
+ import { useSyncExternalStore } from 'react';
2
+ const INITIAL = { status: 'loading' };
3
+ export class TeamContextJudgeCheck {
4
+ snapshot = INITIAL;
5
+ listeners = new Set();
6
+ reading;
7
+ loader;
8
+ constructor(loader) {
9
+ this.loader = loader;
10
+ }
11
+ getSnapshot = () => this.snapshot;
12
+ /**
13
+ * Observe the status, starting the first read when nobody has read yet.
14
+ * @param listener - invoked after every snapshot replacement.
15
+ * @returns the disposer removing this listener.
16
+ */
17
+ subscribe = (listener) => {
18
+ this.listeners.add(listener);
19
+ if (this.snapshot.status === 'loading' && this.reading === undefined)
20
+ void this.refresh();
21
+ return () => { this.listeners.delete(listener); };
22
+ };
23
+ /**
24
+ * Read the Host status. Concurrent callers share one round trip, and a failed
25
+ * read keeps the last accepted report — the block never blanks out over a
26
+ * background read.
27
+ * @returns settlement of this read (or of the read already in flight).
28
+ */
29
+ refresh() {
30
+ if (this.reading !== undefined)
31
+ return this.reading;
32
+ const reading = this.read().finally(() => {
33
+ if (this.reading === reading)
34
+ this.reading = undefined;
35
+ });
36
+ this.reading = reading;
37
+ return reading;
38
+ }
39
+ dispose() {
40
+ this.listeners.clear();
41
+ }
42
+ async read() {
43
+ let report;
44
+ try {
45
+ const result = await this.loader.loadContextJudge();
46
+ if (!result.ok) {
47
+ this.fail();
48
+ return;
49
+ }
50
+ report = result.value;
51
+ }
52
+ catch {
53
+ // A dropped connection surfaces as a thrown carrier error, not a result:
54
+ // both are read failures and both keep whatever report already stands.
55
+ this.fail();
56
+ return;
57
+ }
58
+ this.commit({ status: 'ready', report });
59
+ }
60
+ /**
61
+ * Settle a read that produced no report, so a reader stops waiting on a
62
+ * `loading` that will never finish. A report that already stands is left
63
+ * untouched: a failed background read never blanks the block.
64
+ */
65
+ fail() {
66
+ if (this.snapshot.report === undefined)
67
+ this.commit({ status: 'ready' });
68
+ }
69
+ commit(snapshot) {
70
+ this.snapshot = snapshot;
71
+ for (const listener of this.listeners)
72
+ listener();
73
+ }
74
+ }
75
+ /** Subscribe one rendered block to the judge status. */
76
+ export function useContextJudge(judge) {
77
+ return useSyncExternalStore(judge.subscribe, judge.getSnapshot, judge.getSnapshot);
78
+ }