@wowyuarm/dsh-agent-team 0.1.15 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/README.md +8 -4
  2. package/README.zh.md +8 -4
  3. package/cordis.patch.yml +26 -12
  4. package/package.json +52 -47
  5. package/packages/agent-team/lib/attachments.js +63 -4
  6. package/packages/agent-team/lib/compaction-engine.js +18 -0
  7. package/packages/agent-team/lib/context-continuity-host.js +198 -4
  8. package/packages/agent-team/lib/context-judge.js +150 -0
  9. package/packages/agent-team/lib/context-source.js +11 -6
  10. package/packages/agent-team/lib/environment-check.js +102 -0
  11. package/packages/agent-team/lib/human-profile.js +23 -37
  12. package/packages/agent-team/lib/index.js +510 -99
  13. package/packages/agent-team/lib/installed-manifest.js +32 -0
  14. package/packages/agent-team/lib/invariant.js +36 -12
  15. package/packages/agent-team/lib/ledger.js +37 -1
  16. package/packages/agent-team/lib/member-context.js +2 -4
  17. package/packages/agent-team/lib/member-runtime.js +2 -1
  18. package/packages/agent-team/lib/member-time-context.js +2 -4
  19. package/packages/agent-team/lib/recovery.js +53 -4
  20. package/packages/agent-team/lib/typert.host.js +225 -54
  21. package/packages/agent-team/lib/typert.remote-client.d.ts +5 -1
  22. package/packages/agent-team/lib/typert.remote-client.d.ts.map +1 -1
  23. package/packages/agent-team/lib/typert.remote-client.js +127 -39
  24. package/packages/agent-team/lib/types/attachments.d.ts +32 -2
  25. package/packages/agent-team/lib/types/attachments.d.ts.map +1 -1
  26. package/packages/agent-team/lib/types/compaction-engine.d.ts +19 -0
  27. package/packages/agent-team/lib/types/compaction-engine.d.ts.map +1 -0
  28. package/packages/agent-team/lib/types/context-continuity-host.d.ts +131 -4
  29. package/packages/agent-team/lib/types/context-continuity-host.d.ts.map +1 -1
  30. package/packages/agent-team/lib/types/context-judge.d.ts +82 -0
  31. package/packages/agent-team/lib/types/context-judge.d.ts.map +1 -0
  32. package/packages/agent-team/lib/types/context-source.d.ts +7 -4
  33. package/packages/agent-team/lib/types/context-source.d.ts.map +1 -1
  34. package/packages/agent-team/lib/types/environment-check.d.ts +66 -0
  35. package/packages/agent-team/lib/types/environment-check.d.ts.map +1 -0
  36. package/packages/agent-team/lib/types/human-profile.d.ts +21 -27
  37. package/packages/agent-team/lib/types/human-profile.d.ts.map +1 -1
  38. package/packages/agent-team/lib/types/index.d.ts +281 -18
  39. package/packages/agent-team/lib/types/index.d.ts.map +1 -1
  40. package/packages/agent-team/lib/types/installed-manifest.d.ts +16 -0
  41. package/packages/agent-team/lib/types/installed-manifest.d.ts.map +1 -0
  42. package/packages/agent-team/lib/types/invariant.d.ts +27 -5
  43. package/packages/agent-team/lib/types/invariant.d.ts.map +1 -1
  44. package/packages/agent-team/lib/types/ledger.d.ts +12 -0
  45. package/packages/agent-team/lib/types/ledger.d.ts.map +1 -1
  46. package/packages/agent-team/lib/types/member-context.d.ts.map +1 -1
  47. package/packages/agent-team/lib/types/member-runtime.d.ts +1 -1
  48. package/packages/agent-team/lib/types/member-runtime.d.ts.map +1 -1
  49. package/packages/agent-team/lib/types/member-time-context.d.ts.map +1 -1
  50. package/packages/agent-team/lib/types/recovery.d.ts +19 -6
  51. package/packages/agent-team/lib/types/recovery.d.ts.map +1 -1
  52. package/packages/agent-team/lib/types/types/entities.d.ts +1 -8
  53. package/packages/agent-team/lib/types/types/entities.d.ts.map +1 -1
  54. package/packages/agent-team/lib/types/types/requests-results.d.ts +94 -2
  55. package/packages/agent-team/lib/types/types/requests-results.d.ts.map +1 -1
  56. package/packages/agent-team/lib/types/types.d.ts +9 -0
  57. package/packages/agent-team/lib/types/types.d.ts.map +1 -1
  58. package/packages/client-agent-team/lib/client.js +1264 -383
  59. package/packages/client-agent-team/lib/client.js.map +1 -1
  60. package/packages/client-agent-team/lib/types/client/EnvironmentCheck.d.ts +18 -0
  61. package/packages/client-agent-team/lib/types/client/EnvironmentCheck.d.ts.map +1 -0
  62. package/packages/client-agent-team/lib/types/client/EnvironmentCheck.js +58 -0
  63. package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.js +0 -5
  64. package/packages/client-agent-team/lib/types/client/TeamPresenceDot.d.ts +1 -1
  65. package/packages/client-agent-team/lib/types/client/TeamPresenceDot.d.ts.map +1 -1
  66. package/packages/client-agent-team/lib/types/client/TeamPresenceDot.js +2 -7
  67. package/packages/client-agent-team/lib/types/client/TeamSettingsSection.d.ts +26 -0
  68. package/packages/client-agent-team/lib/types/client/TeamSettingsSection.d.ts.map +1 -0
  69. package/packages/client-agent-team/lib/types/client/TeamSettingsSection.js +195 -0
  70. package/packages/client-agent-team/lib/types/client/attachment-preview.d.ts +7 -11
  71. package/packages/client-agent-team/lib/types/client/attachment-preview.d.ts.map +1 -1
  72. package/packages/client-agent-team/lib/types/client/attachment-preview.js +13 -3
  73. package/packages/client-agent-team/lib/types/client/context-judge.d.ts +64 -0
  74. package/packages/client-agent-team/lib/types/client/context-judge.d.ts.map +1 -0
  75. package/packages/client-agent-team/lib/types/client/context-judge.js +78 -0
  76. package/packages/client-agent-team/lib/types/client/environment-check.d.ts +65 -0
  77. package/packages/client-agent-team/lib/types/client/environment-check.d.ts.map +1 -0
  78. package/packages/client-agent-team/lib/types/client/environment-check.js +78 -0
  79. package/packages/client-agent-team/lib/types/client/index.d.ts.map +1 -1
  80. package/packages/client-agent-team/lib/types/client/index.js +84 -11
  81. package/packages/client-agent-team/lib/types/client/judge-form.d.ts +99 -0
  82. package/packages/client-agent-team/lib/types/client/judge-form.d.ts.map +1 -0
  83. package/packages/client-agent-team/lib/types/client/judge-form.js +208 -0
  84. package/packages/client-agent-team/lib/types/client/locales.d.ts +70 -14
  85. package/packages/client-agent-team/lib/types/client/locales.d.ts.map +1 -1
  86. package/packages/client-agent-team/lib/types/client/locales.js +76 -14
  87. package/packages/tool-agent-team/README.md +6 -3
  88. package/packages/tool-agent-team/README.zh.md +6 -3
  89. package/packages/tool-agent-team/lib/context-tools.js +148 -164
  90. package/packages/tool-agent-team/lib/index.js +17 -2
  91. package/packages/tool-agent-team/lib/types/context-tools.d.ts +32 -7
  92. package/packages/tool-agent-team/lib/types/context-tools.d.ts.map +1 -1
  93. package/packages/tool-agent-team/lib/types/index.d.ts.map +1 -1
  94. package/packages/agent-team/lib/pressure-policy.js +0 -230
  95. package/packages/agent-team/lib/types/pressure-policy.d.ts +0 -130
  96. package/packages/agent-team/lib/types/pressure-policy.d.ts.map +0 -1
  97. package/packages/client-agent-team/lib/types/client/HumanSettingsSection.d.ts +0 -15
  98. package/packages/client-agent-team/lib/types/client/HumanSettingsSection.d.ts.map +0 -1
  99. package/packages/client-agent-team/lib/types/client/HumanSettingsSection.js +0 -115
@@ -0,0 +1,65 @@
1
+ import type { RemoteResult } from '@deepseek-ai/dsh-typert-protocol';
2
+ import type { AgentTeamEnvironmentResult } from '@wowyuarm/dsh-agent-team/types';
3
+ /**
4
+ * The Client's one projection of the local environment check.
5
+ *
6
+ * Deliberately separate from the Human identity projection: this is a fact
7
+ * about the installation, not about the Human, it is read once and never
8
+ * written back, and the settings page is the only surface that renders it.
9
+ * Folding it into the identity store would make every seat that names the
10
+ * Human depend on a version comparison it has no use for.
11
+ *
12
+ * Reads are demand-driven off the first subscriber, so an ordinary conversation
13
+ * never calls the Remote. There is no retry: `undetermined` is a settled
14
+ * verdict about facts the Host could not establish, not a failed request, so a
15
+ * reader has nothing to retry.
16
+ */
17
+ /** One read of the environment check, replaced wholesale on every change. */
18
+ export interface TeamEnvironmentSnapshot {
19
+ /** `loading` until the first read settles, `ready` once it has. */
20
+ readonly status: 'loading' | 'ready';
21
+ /** The report to render; undefined until the first read settles. */
22
+ readonly report?: AgentTeamEnvironmentResult | undefined;
23
+ }
24
+ /** Read-side face the settings page binds. */
25
+ export interface TeamEnvironmentSource {
26
+ getSnapshot(): TeamEnvironmentSnapshot;
27
+ subscribe(listener: () => void): () => void;
28
+ }
29
+ /** Host call the store reads through; one loader per Client context. */
30
+ export interface TeamEnvironmentLoader {
31
+ loadEnvironment: () => Promise<RemoteResult<AgentTeamEnvironmentResult>>;
32
+ }
33
+ export declare class TeamEnvironmentCheck implements TeamEnvironmentSource {
34
+ private snapshot;
35
+ private readonly listeners;
36
+ private reading;
37
+ private readonly loader;
38
+ constructor(loader: TeamEnvironmentLoader);
39
+ readonly getSnapshot: () => TeamEnvironmentSnapshot;
40
+ /**
41
+ * Observe the check, starting the first read when nobody has read yet.
42
+ * @param listener - invoked after every snapshot replacement.
43
+ * @returns the disposer removing this listener.
44
+ */
45
+ readonly subscribe: (listener: () => void) => (() => void);
46
+ /**
47
+ * Read the Host projection. Concurrent callers share one round trip, and a
48
+ * failed read keeps the last accepted report — the block never blanks out
49
+ * over a background read.
50
+ * @returns settlement of this read (or of the read already in flight).
51
+ */
52
+ refresh(): Promise<void>;
53
+ dispose(): void;
54
+ private read;
55
+ /**
56
+ * Settle a read that produced no report, so a reader stops waiting on a
57
+ * `loading` that will never finish. A report that already stands is left
58
+ * untouched: a failed background read never blanks the block.
59
+ */
60
+ private fail;
61
+ private commit;
62
+ }
63
+ /** Subscribe one rendered block to the environment check. */
64
+ export declare function useEnvironmentCheck(environment: TeamEnvironmentSource): TeamEnvironmentSnapshot;
65
+ //# sourceMappingURL=environment-check.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"environment-check.d.ts","sourceRoot":"","sources":["../../../src/client/environment-check.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kCAAkC,CAAA;AACpE,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,gCAAgC,CAAA;AAEhF;;;;;;;;;;;;;GAaG;AAEH,6EAA6E;AAC7E,MAAM,WAAW,uBAAuB;IACtC,mEAAmE;IACnE,QAAQ,CAAC,MAAM,EAAE,SAAS,GAAG,OAAO,CAAA;IACpC,oEAAoE;IACpE,QAAQ,CAAC,MAAM,CAAC,EAAE,0BAA0B,GAAG,SAAS,CAAA;CACzD;AAED,8CAA8C;AAC9C,MAAM,WAAW,qBAAqB;IACpC,WAAW,IAAI,uBAAuB,CAAA;IACtC,SAAS,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAAA;CAC5C;AAED,wEAAwE;AACxE,MAAM,WAAW,qBAAqB;IACpC,eAAe,EAAE,MAAM,OAAO,CAAC,YAAY,CAAC,0BAA0B,CAAC,CAAC,CAAA;CACzE;AAID,qBAAa,oBAAqB,YAAW,qBAAqB;IAChE,OAAO,CAAC,QAAQ,CAAmC;IACnD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAwB;IAClD,OAAO,CAAC,OAAO,CAA2B;IAC1C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAuB;gBAElC,MAAM,EAAE,qBAAqB;IAIzC,QAAQ,CAAC,WAAW,QAAO,uBAAuB,CAAiB;IAEnE;;;;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,6DAA6D;AAC7D,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,qBAAqB,GAAG,uBAAuB,CAE/F"}
@@ -0,0 +1,78 @@
1
+ import { useSyncExternalStore } from 'react';
2
+ const INITIAL = { status: 'loading' };
3
+ export class TeamEnvironmentCheck {
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 check, 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 projection. Concurrent callers share one round trip, and a
25
+ * failed read keeps the last accepted report — the block never blanks out
26
+ * over a 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.loadEnvironment();
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 environment check. */
76
+ export function useEnvironmentCheck(environment) {
77
+ return useSyncExternalStore(environment.subscribe, environment.getSnapshot, environment.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;AA6CnE,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;AAIhD,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;AAsQD,wBAAsB,KAAK,CAAC,GAAG,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAI7D"}
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,6 +1,9 @@
1
1
  import agentTeamRemote from '@wowyuarm/dsh-agent-team/remote';
2
- import { HumanSettingsSection } from "./HumanSettingsSection.js";
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";
6
+ import { TeamEnvironmentCheck } from "./environment-check.js";
4
7
  import { bytesToBase64 } from "./attachment-preview.js";
5
8
  import { TeamNavigation } from "./navigation.js";
6
9
  import { TeamChangeStream, TeamReadStream } from "./team-changes.js";
@@ -12,8 +15,15 @@ import { TeamWorkspaceBrowser } from "./TeamWorkspaceBrowser.js";
12
15
  import { en, zh } from "./locales.js";
13
16
  export { TeamNavigation } from "./navigation.js";
14
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';
15
25
  export const inject = [
16
- 'slots', 'workspaces', 'locale', 'remote', 'remote.session', 'sessions', 'connection', 'conversation', 'uiWorkspace',
26
+ 'slots', 'workspaces', 'layout', 'locale', 'remote', 'remote.session', 'sessions', 'connection', 'conversation', 'uiWorkspace',
17
27
  ];
18
28
  /**
19
29
  * 0.1.7 moved the conversation selection into the workspace service: the
@@ -160,10 +170,31 @@ function applyUi(ctx) {
160
170
  return `data:${result.value.mediaType};base64,${result.value.bytesBase64}`;
161
171
  },
162
172
  });
173
+ // The local environment check: a fact about this installation rather than
174
+ // about the Human, so it is its own projection and never a field of the
175
+ // identity above. Read once, on the first subscriber, and never written back.
176
+ const environment = new TeamEnvironmentCheck({
177
+ loadEnvironment: () => ctx.remote.agentTeam.environment({}),
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();
163
191
  ctx.effect(() => () => {
164
192
  navigation.dispose();
165
193
  drafts.dispose();
166
194
  humanIdentity.dispose();
195
+ environment.dispose();
196
+ contextJudge.dispose();
197
+ judgeForm.dispose();
167
198
  void disposeNavigation();
168
199
  void disposeDrafts();
169
200
  }, 'agent-team: navigation service');
@@ -195,6 +226,25 @@ function applyUi(ctx) {
195
226
  restore();
196
227
  };
197
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');
198
248
  const changes = new TeamChangeStream(ctx.remote);
199
249
  ctx.effect(() => () => changes.dispose(), 'agent-team: change subscriptions');
200
250
  // The Harness resumes a live generation across reconnects, but a stream that
@@ -236,20 +286,43 @@ function applyUi(ctx) {
236
286
  registerModeShadow(ctx, navigation, changes, reads, drafts, humanIdentity, 'sidebar.workspaces', TeamWorkspaceBrowser);
237
287
  registerModeShadow(ctx, navigation, changes, reads, drafts, humanIdentity, 'main', TeamConversation, undefined, 'conversation');
238
288
  registerModeShadow(ctx, navigation, changes, reads, drafts, humanIdentity, 'sidebar.settings', TeamMembersAction, () => ({ loadMemberGroups }));
239
- // The Human profile page: one settings section, ordered between General (0)
240
- // and Models (10) so identity sits near the top. Writes go through the Team
241
- // Remote, which answers with the Host's own rejection reason (a name
242
- // collides, an empty one), and the shared identity re-reads afterwards, so a
243
- // rename lands in the timeline and the member refs at the same moment the
244
- // 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
+ });
245
310
  ctx.slots.inject('settings.section', () => ctx.slots.register({
246
311
  name: 'settings.section',
247
- id: 'team-human',
312
+ id: 'team-settings',
248
313
  order: 5,
249
- label: () => ctx.locale.bind(NS)('humanSettingsNav'),
314
+ label: () => ctx.locale.bind(NS)('teamSettingsNav'),
250
315
  locale: NS,
251
316
  inject: () => ({
252
317
  identity: humanIdentity,
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,
253
326
  saveName: async (name) => {
254
327
  const saved = await ctx.remote.agentTeam.setHumanProfile({ name });
255
328
  if (!saved.ok)
@@ -285,7 +358,7 @@ function applyUi(ctx) {
285
358
  return undefined;
286
359
  },
287
360
  }),
288
- }, HumanSettingsSection));
361
+ }, TeamSettingsSection));
289
362
  }
290
363
  export async function apply(ctx) {
291
364
  const disposeRemote = await ctx.remote.$mount(agentTeamRemote);
@@ -0,0 +1,99 @@
1
+ import { type SettingsFieldState, type SettingsFormActions, type SettingsFormScope, type SettingsFormShell } from '@deepseek-ai/dsh-client-ui-primitives';
2
+ import type { TeamContextJudgeCheck } from './context-judge.ts';
3
+ /** Where each control's value lives inside the Team row's settings section. */
4
+ declare const FIELD_PATHS: {
5
+ readonly apiBase: readonly ["jev", "apiBase"];
6
+ readonly model: readonly ["jev", "model"];
7
+ readonly apiKey: readonly ["jev", "apiKey"];
8
+ };
9
+ /** One control this page owns. */
10
+ export type TeamJudgeField = keyof typeof FIELD_PATHS;
11
+ /** The group this form edits, as it sits inside the Team row's section. */
12
+ export interface TeamJudgeSection {
13
+ /** The judge's endpoint configuration. */
14
+ readonly jev?: unknown;
15
+ }
16
+ /** Everything the page renders about the form, in one replaceable value. */
17
+ export interface TeamJudgeFormSnapshot {
18
+ /** Availability, writability, and what a save would do. */
19
+ readonly shell: SettingsFormShell;
20
+ /** One state per control this page owns. */
21
+ readonly fields: Readonly<Record<TeamJudgeField, SettingsFieldState>>;
22
+ }
23
+ export declare class TeamJudgeForm {
24
+ private readonly section;
25
+ private readonly judge;
26
+ private readonly model;
27
+ private readonly scope;
28
+ private readonly store;
29
+ /**
30
+ * @param section - the Team row's configuration form, as the settings provider serves it.
31
+ * @param judge - the status projection a landed write re-reads.
32
+ */
33
+ constructor(section: SettingsFormScope<TeamJudgeSection>, judge: TeamContextJudgeCheck);
34
+ /** The form state the page renders. */
35
+ readonly getSnapshot: () => TeamJudgeFormSnapshot;
36
+ /** Observe the form state. */
37
+ readonly subscribe: (listener: () => void) => (() => void);
38
+ /** Stage a draft, reset a field, save every staged edit, or discard them. */
39
+ actions(): SettingsFormActions;
40
+ /**
41
+ * Remove the stored key literal: the judge unmounts and the status re-reads.
42
+ *
43
+ * Deliberately its own write rather than a staged edit — the shared form
44
+ * model has no clear for a write-only control, and an empty draft must keep
45
+ * the stored literal instead of clearing it. The page only offers this while
46
+ * no draft is staged, because a write moves the document revision the staged
47
+ * save is fenced to.
48
+ * @returns whether the Host accepted the clear.
49
+ */
50
+ clearKey(): Promise<boolean>;
51
+ dispose(): void;
52
+ /** Build the page's state from the model's reads and drafts. */
53
+ private project;
54
+ /** Write every staged edit, then re-read the status the Host now serves. */
55
+ save(): Promise<void>;
56
+ }
57
+ /** Read-side face the settings page binds for the endpoint form. */
58
+ export interface TeamJudgeFormSource {
59
+ getSnapshot(): TeamJudgeForm | undefined;
60
+ subscribe(listener: () => void): () => void;
61
+ }
62
+ /**
63
+ * The form's seat on the page, as an observable rather than a value.
64
+ *
65
+ * The form exists only while this deployment serves a settings document, and
66
+ * that service may arrive after the page's first render. A slot entry's
67
+ * injected props are computed once and cached for the entry's lifetime, so a
68
+ * value handed over at that moment would freeze the answer: the group would
69
+ * never appear until a reload. The page therefore observes this seat, which
70
+ * the service's arrival and departure both publish through.
71
+ */
72
+ export declare class TeamJudgeFormSeat implements TeamJudgeFormSource {
73
+ private form;
74
+ private readonly listeners;
75
+ readonly getSnapshot: () => TeamJudgeForm | undefined;
76
+ /**
77
+ * Observe the form, without reading anything on subscribe: the seat carries
78
+ * no remote state of its own, so a subscriber renders whatever is held now.
79
+ * @param listener - invoked after every hold or release.
80
+ * @returns the disposer removing this listener.
81
+ */
82
+ readonly subscribe: (listener: () => void) => (() => void);
83
+ /**
84
+ * Publish the form the settings service answers with.
85
+ * @param form - the form built over that service's section scope.
86
+ */
87
+ hold(form: TeamJudgeForm): void;
88
+ /**
89
+ * Withdraw the form when its service goes away. Only the holder of the form
90
+ * in place can withdraw it, so a replacement is never cleared by the
91
+ * departure of the one it replaced.
92
+ * @param form - the form this caller holds.
93
+ */
94
+ release(form: TeamJudgeForm): void;
95
+ dispose(): void;
96
+ private publish;
97
+ }
98
+ export {};
99
+ //# sourceMappingURL=judge-form.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"judge-form.d.ts","sourceRoot":"","sources":["../../../src/client/judge-form.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,kBAAkB,EAAE,KAAK,mBAAmB,EAA2B,KAAK,iBAAiB,EAClE,KAAK,iBAAiB,EACvD,MAAM,uCAAuC,CAAA;AAC9C,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAA;AAqB/D,+EAA+E;AAC/E,QAAA,MAAM,WAAW;;;;CAIP,CAAA;AAEV,kCAAkC;AAClC,MAAM,MAAM,cAAc,GAAG,MAAM,OAAO,WAAW,CAAA;AAErD,2EAA2E;AAC3E,MAAM,WAAW,gBAAgB;IAC/B,0CAA0C;IAC1C,QAAQ,CAAC,GAAG,CAAC,EAAE,OAAO,CAAA;CACvB;AA4DD,4EAA4E;AAC5E,MAAM,WAAW,qBAAqB;IACpC,2DAA2D;IAC3D,QAAQ,CAAC,KAAK,EAAE,iBAAiB,CAAA;IACjC,4CAA4C;IAC5C,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,cAAc,EAAE,kBAAkB,CAAC,CAAC,CAAA;CACtE;AAQD,qBAAa,aAAa;IAUtB,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,KAAK;IAVxB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAA4C;IAClE,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAgB;IACtC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAgB;IAEtC;;;OAGG;gBAEgB,OAAO,EAAE,iBAAiB,CAAC,gBAAgB,CAAC,EAC5C,KAAK,EAAE,qBAAqB;IAiB/C,uCAAuC;IACvC,QAAQ,CAAC,WAAW,QAAO,qBAAqB,CAA4B;IAE5E,8BAA8B;IAC9B,QAAQ,CAAC,SAAS,GAAI,UAAU,MAAM,IAAI,KAAG,CAAC,MAAM,IAAI,CAAC,CAAkC;IAE3F,6EAA6E;IAC7E,OAAO,IAAI,mBAAmB;IAI9B;;;;;;;;;OASG;IACG,QAAQ,IAAI,OAAO,CAAC,OAAO,CAAC;IAYlC,OAAO,IAAI,IAAI;IAIf,gEAAgE;IAChE,OAAO,CAAC,OAAO;IAMf,4EAA4E;IACtE,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;CAI5B;AAED,oEAAoE;AACpE,MAAM,WAAW,mBAAmB;IAClC,WAAW,IAAI,aAAa,GAAG,SAAS,CAAA;IACxC,SAAS,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAAA;CAC5C;AAED;;;;;;;;;GASG;AACH,qBAAa,iBAAkB,YAAW,mBAAmB;IAC3D,OAAO,CAAC,IAAI,CAA2B;IACvC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAwB;IAElD,QAAQ,CAAC,WAAW,QAAO,aAAa,GAAG,SAAS,CAAa;IAEjE;;;;;OAKG;IACH,QAAQ,CAAC,SAAS,GAAI,UAAU,MAAM,IAAI,KAAG,CAAC,MAAM,IAAI,CAAC,CAGxD;IAED;;;OAGG;IACH,IAAI,CAAC,IAAI,EAAE,aAAa,GAAG,IAAI;IAK/B;;;;;OAKG;IACH,OAAO,CAAC,IAAI,EAAE,aAAa,GAAG,IAAI;IAMlC,OAAO,IAAI,IAAI;IAIf,OAAO,CAAC,OAAO;CAGhB"}
@@ -0,0 +1,208 @@
1
+ import { SettingsFormModel, settingsTextField, } from '@deepseek-ai/dsh-client-ui-primitives';
2
+ /**
3
+ * The Team settings page's staged form over the judge's endpoint.
4
+ *
5
+ * The page edits one group of the Team row's settings section: the `jev`
6
+ * endpoint the relatedness judge is mounted from. The identity fields that
7
+ * share that section belong to the Human profile rows and are never rendered
8
+ * here, so this form reaches its values through a projection — the shared form
9
+ * model addresses one flat section, and the adapter below maps each control to
10
+ * the path it really lives at.
11
+ *
12
+ * The key literal is the one control that is not read back: the Host redacts
13
+ * it, so its draft starts blank on every load, a blank draft writes nothing,
14
+ * and removing a stored literal is an explicit clear rather than an empty
15
+ * string.
16
+ */
17
+ /** The group inside the Team row's settings section that this form edits. */
18
+ const JUDGE_GROUP = 'jev';
19
+ /** Where each control's value lives inside the Team row's settings section. */
20
+ const FIELD_PATHS = {
21
+ apiBase: [JUDGE_GROUP, 'apiBase'],
22
+ model: [JUDGE_GROUP, 'model'],
23
+ apiKey: [JUDGE_GROUP, 'apiKey'],
24
+ };
25
+ /** Read the judge group of a section layer as a plain record. */
26
+ function groupOf(layer) {
27
+ if (layer === null || typeof layer !== 'object')
28
+ return undefined;
29
+ const value = layer[JUDGE_GROUP];
30
+ return value !== null && typeof value === 'object' ? value : undefined;
31
+ }
32
+ /**
33
+ * Project one section layer onto the controls this page renders. A control the
34
+ * layer does not carry stays absent rather than becoming an empty string, which
35
+ * is what the model reads to decide whether a field is overridden and what a
36
+ * reset reverts to.
37
+ * @param layer - a section, or one of its composition layers.
38
+ * @returns the flat control values, or undefined when the layer carries none.
39
+ */
40
+ function flatten(layer) {
41
+ if (layer === null || typeof layer !== 'object')
42
+ return undefined;
43
+ const group = groupOf(layer);
44
+ const flat = {};
45
+ for (const [field, path] of Object.entries(FIELD_PATHS)) {
46
+ if (group !== undefined && Object.hasOwn(group, path[1]))
47
+ flat[field] = group[path[1]];
48
+ }
49
+ return flat;
50
+ }
51
+ /** Address one control's edit at the path it really lives at. */
52
+ function locate(op) {
53
+ const path = FIELD_PATHS[op.path[0]];
54
+ return op.op === 'set'
55
+ ? { op: 'set', path: [...path], value: op.value }
56
+ : { op: 'unset', path: [...path] };
57
+ }
58
+ /** The shared form model's view of this page's controls, over the real section. */
59
+ class TeamJudgeScope {
60
+ section;
61
+ constructor(section) {
62
+ this.section = section;
63
+ }
64
+ getSnapshot() {
65
+ const snapshot = this.section.getSnapshot();
66
+ return {
67
+ status: snapshot.status,
68
+ value: flatten(snapshot.value),
69
+ base: flatten(snapshot.base),
70
+ user: flatten(snapshot.user),
71
+ writable: snapshot.writable,
72
+ revision: snapshot.revision,
73
+ };
74
+ }
75
+ subscribe(listener) {
76
+ return this.section.subscribe(listener);
77
+ }
78
+ mutate(ops, expectedRevision) {
79
+ return this.section.mutate(ops.map(op => locate(op)), expectedRevision);
80
+ }
81
+ }
82
+ export class TeamJudgeForm {
83
+ section;
84
+ judge;
85
+ model;
86
+ scope;
87
+ store;
88
+ /**
89
+ * @param section - the Team row's configuration form, as the settings provider serves it.
90
+ * @param judge - the status projection a landed write re-reads.
91
+ */
92
+ constructor(section, judge) {
93
+ this.section = section;
94
+ this.judge = judge;
95
+ this.scope = new TeamJudgeScope(section);
96
+ this.model = new SettingsFormModel(this.scope, [
97
+ settingsTextField('apiBase'),
98
+ settingsTextField('model'),
99
+ ], [{
100
+ field: 'apiKey',
101
+ // The literal never rides a response, so a draft is only ever what the
102
+ // reader typed; the write addresses the real path directly.
103
+ write: text => section.mutate([{ op: 'set', path: [...FIELD_PATHS.apiKey], value: text }]),
104
+ }]);
105
+ // The model publishes a projection on every scope change or staged edit;
106
+ // binding one is how this page observes it.
107
+ this.store = this.model.bind(() => this.project());
108
+ }
109
+ /** The form state the page renders. */
110
+ getSnapshot = () => this.store.getSnapshot();
111
+ /** Observe the form state. */
112
+ subscribe = (listener) => this.store.subscribe(listener);
113
+ /** Stage a draft, reset a field, save every staged edit, or discard them. */
114
+ actions() {
115
+ return { ...this.model.actions(), save: () => { void this.save(); } };
116
+ }
117
+ /**
118
+ * Remove the stored key literal: the judge unmounts and the status re-reads.
119
+ *
120
+ * Deliberately its own write rather than a staged edit — the shared form
121
+ * model has no clear for a write-only control, and an empty draft must keep
122
+ * the stored literal instead of clearing it. The page only offers this while
123
+ * no draft is staged, because a write moves the document revision the staged
124
+ * save is fenced to.
125
+ * @returns whether the Host accepted the clear.
126
+ */
127
+ async clearKey() {
128
+ let landed;
129
+ try {
130
+ landed = await this.section.mutate([{ op: 'unset', path: [...FIELD_PATHS.apiKey] }]);
131
+ }
132
+ catch {
133
+ return false;
134
+ }
135
+ if (!landed)
136
+ return false;
137
+ await this.judge.refresh();
138
+ return true;
139
+ }
140
+ dispose() {
141
+ this.model.dispose();
142
+ }
143
+ /** Build the page's state from the model's reads and drafts. */
144
+ project() {
145
+ const fields = {};
146
+ for (const field of Object.keys(FIELD_PATHS))
147
+ fields[field] = this.model.field(field);
148
+ return { shell: this.model.shell(), fields };
149
+ }
150
+ /** Write every staged edit, then re-read the status the Host now serves. */
151
+ async save() {
152
+ await this.model.save();
153
+ if (!this.model.shell().failed)
154
+ await this.judge.refresh();
155
+ }
156
+ }
157
+ /**
158
+ * The form's seat on the page, as an observable rather than a value.
159
+ *
160
+ * The form exists only while this deployment serves a settings document, and
161
+ * that service may arrive after the page's first render. A slot entry's
162
+ * injected props are computed once and cached for the entry's lifetime, so a
163
+ * value handed over at that moment would freeze the answer: the group would
164
+ * never appear until a reload. The page therefore observes this seat, which
165
+ * the service's arrival and departure both publish through.
166
+ */
167
+ export class TeamJudgeFormSeat {
168
+ form;
169
+ listeners = new Set();
170
+ getSnapshot = () => this.form;
171
+ /**
172
+ * Observe the form, without reading anything on subscribe: the seat carries
173
+ * no remote state of its own, so a subscriber renders whatever is held now.
174
+ * @param listener - invoked after every hold or release.
175
+ * @returns the disposer removing this listener.
176
+ */
177
+ subscribe = (listener) => {
178
+ this.listeners.add(listener);
179
+ return () => { this.listeners.delete(listener); };
180
+ };
181
+ /**
182
+ * Publish the form the settings service answers with.
183
+ * @param form - the form built over that service's section scope.
184
+ */
185
+ hold(form) {
186
+ this.form = form;
187
+ this.publish();
188
+ }
189
+ /**
190
+ * Withdraw the form when its service goes away. Only the holder of the form
191
+ * in place can withdraw it, so a replacement is never cleared by the
192
+ * departure of the one it replaced.
193
+ * @param form - the form this caller holds.
194
+ */
195
+ release(form) {
196
+ if (this.form !== form)
197
+ return;
198
+ this.form = undefined;
199
+ this.publish();
200
+ }
201
+ dispose() {
202
+ this.listeners.clear();
203
+ }
204
+ publish() {
205
+ for (const listener of this.listeners)
206
+ listener();
207
+ }
208
+ }