@wowyuarm/dsh-agent-team 0.2.0 → 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 +5 -5
- package/README.zh.md +5 -5
- package/cordis.patch.yml +26 -12
- package/package.json +50 -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/index.js +482 -97
- package/packages/agent-team/lib/invariant.js +36 -12
- package/packages/agent-team/lib/ledger.js +2 -1
- package/packages/agent-team/lib/member-runtime.js +2 -1
- package/packages/agent-team/lib/recovery.js +53 -4
- package/packages/agent-team/lib/typert.host.js +138 -44
- package/packages/agent-team/lib/typert.remote-client.d.ts +3 -1
- package/packages/agent-team/lib/typert.remote-client.d.ts.map +1 -1
- package/packages/agent-team/lib/typert.remote-client.js +75 -35
- 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/index.d.ts +259 -17
- package/packages/agent-team/lib/types/index.d.ts.map +1 -1
- 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.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/recovery.d.ts +19 -6
- package/packages/agent-team/lib/types/recovery.d.ts.map +1 -1
- package/packages/agent-team/lib/types/types/requests-results.d.ts +28 -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 +1006 -374
- package/packages/client-agent-team/lib/client.js.map +1 -1
- package/packages/client-agent-team/lib/types/client/{HumanSettingsSection.d.ts → TeamSettingsSection.d.ts} +12 -4
- 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/index.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/index.js +75 -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 +52 -6
- package/packages/client-agent-team/lib/types/client/locales.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/locales.js +55 -6
- 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 +84 -112
- package/packages/tool-agent-team/lib/types/context-tools.d.ts +29 -13
- package/packages/tool-agent-team/lib/types/context-tools.d.ts.map +1 -1
- package/packages/agent-team/lib/pressure-policy.js +0 -235
- package/packages/agent-team/lib/types/pressure-policy.d.ts +0 -134
- package/packages/agent-team/lib/types/pressure-policy.d.ts.map +0 -1
- 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 -118
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import type { PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots';
|
|
2
2
|
import { type TeamHumanIdentityFace } from './human-identity.ts';
|
|
3
3
|
import type { TeamEnvironmentSource } from './environment-check.ts';
|
|
4
|
-
|
|
4
|
+
import { type TeamContextJudgeSource } from './context-judge.ts';
|
|
5
|
+
import type { TeamJudgeFormSource } from './judge-form.ts';
|
|
6
|
+
export interface TeamSettingsInjected {
|
|
5
7
|
/** The shared Human identity projection: profile facts plus post-write refresh. */
|
|
6
8
|
identity: TeamHumanIdentityFace;
|
|
7
9
|
/** Persist one renamed display name; the failure message when the Host refuses it. */
|
|
@@ -12,7 +14,13 @@ export interface HumanSettingsSectionInjected {
|
|
|
12
14
|
removeAvatar: () => Promise<string | undefined>;
|
|
13
15
|
/** The local environment check, a read-only projection of the installation. */
|
|
14
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;
|
|
15
23
|
}
|
|
16
|
-
export type
|
|
17
|
-
export declare function
|
|
18
|
-
//# sourceMappingURL=
|
|
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
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/client/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,qBAAqB,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/client/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,qBAAqB,CAAA;AAgDnE,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAEhD,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAK5C,OAAO,EAAU,KAAK,OAAO,EAAE,MAAM,cAAc,CAAA;AAEnD,YAAY,EAAE,QAAQ,EAAE,qBAAqB,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAA;AAC9F,YAAY,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AAC3C,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAYhD,eAAO,MAAM,MAAM,UAElB,CAAA;AAuBD,OAAO,QAAQ,kCAAkC,CAAC;IAChD,UAAU,kBAAkB;QAC1B,IAAI,EAAE,OAAO,CAAA;KACd;CACF;AAED,OAAO,QAAQ,qBAAqB,CAAC;IACnC,UAAU,OAAO;QACf,cAAc,EAAE,cAAc,CAAA;QAC9B,UAAU,EAAE,cAAc,CAAA;KAC3B;CACF;AAoUD,wBAAsB,KAAK,CAAC,GAAG,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAI7D"}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import agentTeamRemote from '@wowyuarm/dsh-agent-team/remote';
|
|
2
|
-
import {
|
|
2
|
+
import { TeamSettingsSection } from "./TeamSettingsSection.js";
|
|
3
|
+
import { TeamContextJudgeCheck } from "./context-judge.js";
|
|
4
|
+
import { TeamJudgeForm, TeamJudgeFormSeat } from "./judge-form.js";
|
|
3
5
|
import { TeamHumanIdentity } from "./human-identity.js";
|
|
4
6
|
import { TeamEnvironmentCheck } from "./environment-check.js";
|
|
5
7
|
import { bytesToBase64 } from "./attachment-preview.js";
|
|
@@ -13,8 +15,15 @@ import { TeamWorkspaceBrowser } from "./TeamWorkspaceBrowser.js";
|
|
|
13
15
|
import { en, zh } from "./locales.js";
|
|
14
16
|
export { TeamNavigation } from "./navigation.js";
|
|
15
17
|
const NS = 'team';
|
|
18
|
+
/**
|
|
19
|
+
* The Team Host row's settings namespace. Spelled here rather than imported,
|
|
20
|
+
* because a client package must not depend on a Host package — the profile, the
|
|
21
|
+
* judge's endpoint, and the gate's thresholds all live in this one row section,
|
|
22
|
+
* and each settings page renders only the group it owns.
|
|
23
|
+
*/
|
|
24
|
+
const TEAM_SETTINGS_NS = 'wowyuarm-agent-team-host';
|
|
16
25
|
export const inject = [
|
|
17
|
-
'slots', 'workspaces', 'locale', 'remote', 'remote.session', 'sessions', 'connection', 'conversation', 'uiWorkspace',
|
|
26
|
+
'slots', 'workspaces', 'layout', 'locale', 'remote', 'remote.session', 'sessions', 'connection', 'conversation', 'uiWorkspace',
|
|
18
27
|
];
|
|
19
28
|
/**
|
|
20
29
|
* 0.1.7 moved the conversation selection into the workspace service: the
|
|
@@ -167,11 +176,25 @@ function applyUi(ctx) {
|
|
|
167
176
|
const environment = new TeamEnvironmentCheck({
|
|
168
177
|
loadEnvironment: () => ctx.remote.agentTeam.environment({}),
|
|
169
178
|
});
|
|
179
|
+
// The long-gap gate's judge: what the Host answers about reachability, and
|
|
180
|
+
// the thresholds in force. Its own projection rather than a field of the
|
|
181
|
+
// environment check, because it re-reads after every write that can mount or
|
|
182
|
+
// unmount the judge while the environment verdict is settled once.
|
|
183
|
+
const contextJudge = new TeamContextJudgeCheck({
|
|
184
|
+
loadContextJudge: () => ctx.remote.agentTeam.contextJudge({}),
|
|
185
|
+
});
|
|
186
|
+
// The settings page's endpoint form. It is held in a seat rather than handed
|
|
187
|
+
// over as a value, because the service it is built over may arrive after the
|
|
188
|
+
// page's injected props were computed — and a slot entry's props are computed
|
|
189
|
+
// once and cached, so a value would freeze the answer.
|
|
190
|
+
const judgeForm = new TeamJudgeFormSeat();
|
|
170
191
|
ctx.effect(() => () => {
|
|
171
192
|
navigation.dispose();
|
|
172
193
|
drafts.dispose();
|
|
173
194
|
humanIdentity.dispose();
|
|
174
195
|
environment.dispose();
|
|
196
|
+
contextJudge.dispose();
|
|
197
|
+
judgeForm.dispose();
|
|
175
198
|
void disposeNavigation();
|
|
176
199
|
void disposeDrafts();
|
|
177
200
|
}, 'agent-team: navigation service');
|
|
@@ -203,6 +226,25 @@ function applyUi(ctx) {
|
|
|
203
226
|
restore();
|
|
204
227
|
};
|
|
205
228
|
}, 'agent-team: member session restore');
|
|
229
|
+
// The shell renders one keyed main panel at a time — `activePanelId ?? 'conversation'`
|
|
230
|
+
// — so a shipped global panel (the plugin manager) stays on screen after Team mode is
|
|
231
|
+
// entered, and every Team destination then opens behind it: the sidebar moves while the
|
|
232
|
+
// column keeps rendering the foreign page. Team mode owns the conversation seat, so
|
|
233
|
+
// entering it, or navigating inside it, hands the column back to that seat. A null
|
|
234
|
+
// selection — and every mode but Team, where panel choice belongs to the shipped
|
|
235
|
+
// sidebar — is left untouched, so this never fights the shell's own selection.
|
|
236
|
+
ctx.effect(() => {
|
|
237
|
+
const releaseMainPanel = () => {
|
|
238
|
+
if (navigation.getSnapshot().mode !== 'team')
|
|
239
|
+
return;
|
|
240
|
+
if (ctx.layout.panelInfo.getSnapshot().activePanelId === null)
|
|
241
|
+
return;
|
|
242
|
+
ctx.layout.selectPanel(null);
|
|
243
|
+
};
|
|
244
|
+
const unsubscribe = navigation.subscribe(releaseMainPanel);
|
|
245
|
+
releaseMainPanel();
|
|
246
|
+
return unsubscribe;
|
|
247
|
+
}, 'agent-team: main panel ownership');
|
|
206
248
|
const changes = new TeamChangeStream(ctx.remote);
|
|
207
249
|
ctx.effect(() => () => changes.dispose(), 'agent-team: change subscriptions');
|
|
208
250
|
// The Harness resumes a live generation across reconnects, but a stream that
|
|
@@ -244,21 +286,43 @@ function applyUi(ctx) {
|
|
|
244
286
|
registerModeShadow(ctx, navigation, changes, reads, drafts, humanIdentity, 'sidebar.workspaces', TeamWorkspaceBrowser);
|
|
245
287
|
registerModeShadow(ctx, navigation, changes, reads, drafts, humanIdentity, 'main', TeamConversation, undefined, 'conversation');
|
|
246
288
|
registerModeShadow(ctx, navigation, changes, reads, drafts, humanIdentity, 'sidebar.settings', TeamMembersAction, () => ({ loadMemberGroups }));
|
|
247
|
-
// The
|
|
248
|
-
// and Models (10) so
|
|
249
|
-
//
|
|
250
|
-
//
|
|
251
|
-
//
|
|
252
|
-
// page shows it.
|
|
289
|
+
// The Team's settings page: one settings section, ordered between General (0)
|
|
290
|
+
// and Models (10) so it sits near the top. It holds two groups that write
|
|
291
|
+
// through two different documents: identity goes through the Team Remote,
|
|
292
|
+
// which answers with the Host's own rejection reason (a name collides, an
|
|
293
|
+
// empty one), and the shared identity re-reads afterwards, so a rename lands
|
|
294
|
+
// in the timeline and the member refs at the same moment the page shows it.
|
|
295
|
+
//
|
|
296
|
+
// The judge's endpoint is one group of the Team row's settings section, so it
|
|
297
|
+
// is offered only while this deployment serves a settings document: the page
|
|
298
|
+
// is registered here regardless, because identity is reachable without one,
|
|
299
|
+
// and the seat above stays empty when there is none. The service is read
|
|
300
|
+
// through `ctx.get` rather than a hard `inject` for the same reason — a
|
|
301
|
+
// deployment without the settings UI keeps the rest of the Team.
|
|
302
|
+
ctx.inject(['configForms'], (scope) => {
|
|
303
|
+
const form = new TeamJudgeForm(scope.configForms.get(TEAM_SETTINGS_NS), contextJudge);
|
|
304
|
+
judgeForm.hold(form);
|
|
305
|
+
scope.effect(() => () => {
|
|
306
|
+
judgeForm.release(form);
|
|
307
|
+
form.dispose();
|
|
308
|
+
}, 'agent-team: judge settings form');
|
|
309
|
+
});
|
|
253
310
|
ctx.slots.inject('settings.section', () => ctx.slots.register({
|
|
254
311
|
name: 'settings.section',
|
|
255
|
-
id: 'team-
|
|
312
|
+
id: 'team-settings',
|
|
256
313
|
order: 5,
|
|
257
|
-
label: () => ctx.locale.bind(NS)('
|
|
314
|
+
label: () => ctx.locale.bind(NS)('teamSettingsNav'),
|
|
258
315
|
locale: NS,
|
|
259
316
|
inject: () => ({
|
|
260
317
|
identity: humanIdentity,
|
|
261
318
|
environment,
|
|
319
|
+
judge: contextJudge,
|
|
320
|
+
judgeForm,
|
|
321
|
+
// The key's literal never rides a response; the describe mirror is what
|
|
322
|
+
// says whether one is stored.
|
|
323
|
+
keyConfigured: () => ctx.get('configForms')?.describe().getSnapshot().view?.namespaces
|
|
324
|
+
.find(row => row.ns === TEAM_SETTINGS_NS)?.secrets
|
|
325
|
+
.some(secret => secret.path.join('.') === 'jev.apiKey' && secret.set) ?? false,
|
|
262
326
|
saveName: async (name) => {
|
|
263
327
|
const saved = await ctx.remote.agentTeam.setHumanProfile({ name });
|
|
264
328
|
if (!saved.ok)
|
|
@@ -294,7 +358,7 @@ function applyUi(ctx) {
|
|
|
294
358
|
return undefined;
|
|
295
359
|
},
|
|
296
360
|
}),
|
|
297
|
-
},
|
|
361
|
+
}, TeamSettingsSection));
|
|
298
362
|
}
|
|
299
363
|
export async function apply(ctx) {
|
|
300
364
|
const disposeRemote = await ctx.remote.$mount(agentTeamRemote);
|