@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.
- package/README.md +8 -4
- package/README.zh.md +8 -4
- package/cordis.patch.yml +26 -12
- package/package.json +52 -47
- package/packages/agent-team/lib/attachments.js +63 -4
- package/packages/agent-team/lib/compaction-engine.js +18 -0
- package/packages/agent-team/lib/context-continuity-host.js +198 -4
- package/packages/agent-team/lib/context-judge.js +150 -0
- package/packages/agent-team/lib/context-source.js +11 -6
- package/packages/agent-team/lib/environment-check.js +102 -0
- package/packages/agent-team/lib/human-profile.js +23 -37
- package/packages/agent-team/lib/index.js +510 -99
- package/packages/agent-team/lib/installed-manifest.js +32 -0
- package/packages/agent-team/lib/invariant.js +36 -12
- package/packages/agent-team/lib/ledger.js +37 -1
- package/packages/agent-team/lib/member-context.js +2 -4
- package/packages/agent-team/lib/member-runtime.js +2 -1
- package/packages/agent-team/lib/member-time-context.js +2 -4
- package/packages/agent-team/lib/recovery.js +53 -4
- package/packages/agent-team/lib/typert.host.js +225 -54
- package/packages/agent-team/lib/typert.remote-client.d.ts +5 -1
- package/packages/agent-team/lib/typert.remote-client.d.ts.map +1 -1
- package/packages/agent-team/lib/typert.remote-client.js +127 -39
- package/packages/agent-team/lib/types/attachments.d.ts +32 -2
- package/packages/agent-team/lib/types/attachments.d.ts.map +1 -1
- package/packages/agent-team/lib/types/compaction-engine.d.ts +19 -0
- package/packages/agent-team/lib/types/compaction-engine.d.ts.map +1 -0
- package/packages/agent-team/lib/types/context-continuity-host.d.ts +131 -4
- package/packages/agent-team/lib/types/context-continuity-host.d.ts.map +1 -1
- package/packages/agent-team/lib/types/context-judge.d.ts +82 -0
- package/packages/agent-team/lib/types/context-judge.d.ts.map +1 -0
- package/packages/agent-team/lib/types/context-source.d.ts +7 -4
- package/packages/agent-team/lib/types/context-source.d.ts.map +1 -1
- package/packages/agent-team/lib/types/environment-check.d.ts +66 -0
- package/packages/agent-team/lib/types/environment-check.d.ts.map +1 -0
- package/packages/agent-team/lib/types/human-profile.d.ts +21 -27
- package/packages/agent-team/lib/types/human-profile.d.ts.map +1 -1
- package/packages/agent-team/lib/types/index.d.ts +281 -18
- package/packages/agent-team/lib/types/index.d.ts.map +1 -1
- package/packages/agent-team/lib/types/installed-manifest.d.ts +16 -0
- package/packages/agent-team/lib/types/installed-manifest.d.ts.map +1 -0
- package/packages/agent-team/lib/types/invariant.d.ts +27 -5
- package/packages/agent-team/lib/types/invariant.d.ts.map +1 -1
- package/packages/agent-team/lib/types/ledger.d.ts +12 -0
- package/packages/agent-team/lib/types/ledger.d.ts.map +1 -1
- package/packages/agent-team/lib/types/member-context.d.ts.map +1 -1
- package/packages/agent-team/lib/types/member-runtime.d.ts +1 -1
- package/packages/agent-team/lib/types/member-runtime.d.ts.map +1 -1
- package/packages/agent-team/lib/types/member-time-context.d.ts.map +1 -1
- package/packages/agent-team/lib/types/recovery.d.ts +19 -6
- package/packages/agent-team/lib/types/recovery.d.ts.map +1 -1
- package/packages/agent-team/lib/types/types/entities.d.ts +1 -8
- package/packages/agent-team/lib/types/types/entities.d.ts.map +1 -1
- package/packages/agent-team/lib/types/types/requests-results.d.ts +94 -2
- package/packages/agent-team/lib/types/types/requests-results.d.ts.map +1 -1
- package/packages/agent-team/lib/types/types.d.ts +9 -0
- package/packages/agent-team/lib/types/types.d.ts.map +1 -1
- package/packages/client-agent-team/lib/client.js +1264 -383
- package/packages/client-agent-team/lib/client.js.map +1 -1
- package/packages/client-agent-team/lib/types/client/EnvironmentCheck.d.ts +18 -0
- package/packages/client-agent-team/lib/types/client/EnvironmentCheck.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/EnvironmentCheck.js +58 -0
- package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.js +0 -5
- package/packages/client-agent-team/lib/types/client/TeamPresenceDot.d.ts +1 -1
- package/packages/client-agent-team/lib/types/client/TeamPresenceDot.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamPresenceDot.js +2 -7
- package/packages/client-agent-team/lib/types/client/TeamSettingsSection.d.ts +26 -0
- package/packages/client-agent-team/lib/types/client/TeamSettingsSection.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/TeamSettingsSection.js +195 -0
- package/packages/client-agent-team/lib/types/client/attachment-preview.d.ts +7 -11
- package/packages/client-agent-team/lib/types/client/attachment-preview.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/attachment-preview.js +13 -3
- package/packages/client-agent-team/lib/types/client/context-judge.d.ts +64 -0
- package/packages/client-agent-team/lib/types/client/context-judge.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/context-judge.js +78 -0
- package/packages/client-agent-team/lib/types/client/environment-check.d.ts +65 -0
- package/packages/client-agent-team/lib/types/client/environment-check.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/environment-check.js +78 -0
- package/packages/client-agent-team/lib/types/client/index.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/index.js +84 -11
- package/packages/client-agent-team/lib/types/client/judge-form.d.ts +99 -0
- package/packages/client-agent-team/lib/types/client/judge-form.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/judge-form.js +208 -0
- package/packages/client-agent-team/lib/types/client/locales.d.ts +70 -14
- package/packages/client-agent-team/lib/types/client/locales.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/locales.js +76 -14
- package/packages/tool-agent-team/README.md +6 -3
- package/packages/tool-agent-team/README.zh.md +6 -3
- package/packages/tool-agent-team/lib/context-tools.js +148 -164
- package/packages/tool-agent-team/lib/index.js +17 -2
- package/packages/tool-agent-team/lib/types/context-tools.d.ts +32 -7
- package/packages/tool-agent-team/lib/types/context-tools.d.ts.map +1 -1
- package/packages/tool-agent-team/lib/types/index.d.ts.map +1 -1
- package/packages/agent-team/lib/pressure-policy.js +0 -230
- package/packages/agent-team/lib/types/pressure-policy.d.ts +0 -130
- package/packages/agent-team/lib/types/pressure-policy.d.ts.map +0 -1
- package/packages/client-agent-team/lib/types/client/HumanSettingsSection.d.ts +0 -15
- package/packages/client-agent-team/lib/types/client/HumanSettingsSection.d.ts.map +0 -1
- 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)
|
|
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,
|
|
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)
|
|
21
|
+
* (which resolves on its own).
|
|
22
22
|
*/
|
|
23
23
|
export function restartOffered(status) {
|
|
24
|
-
|
|
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
|
-
/**
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
|
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.
|
|
13
|
-
*
|
|
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
|
-
|
|
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
|
+
}
|