@wowyuarm/dsh-agent-team 0.1.14 → 0.1.15

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 (71) hide show
  1. package/README.md +2 -2
  2. package/README.zh.md +2 -2
  3. package/cordis.patch.yml +187 -2
  4. package/package.json +49 -50
  5. package/packages/agent-team/README.md +1 -1
  6. package/packages/agent-team/README.zh.md +1 -1
  7. package/packages/agent-team/lib/context-continuity-host.js +2 -3
  8. package/packages/agent-team/lib/context-projection.js +3 -3
  9. package/packages/agent-team/lib/context-source.js +34 -13
  10. package/packages/agent-team/lib/human-profile.js +123 -12
  11. package/packages/agent-team/lib/index.js +165 -99
  12. package/packages/agent-team/lib/member-context.js +5 -3
  13. package/packages/agent-team/lib/member-time-context.js +5 -2
  14. package/packages/agent-team/lib/pressure-policy.js +5 -3
  15. package/packages/agent-team/lib/stored-session-reader.js +1 -2
  16. package/packages/agent-team/lib/typert.host.js +394 -252
  17. package/packages/agent-team/lib/typert.remote-client.d.ts +6 -3
  18. package/packages/agent-team/lib/typert.remote-client.d.ts.map +1 -1
  19. package/packages/agent-team/lib/typert.remote-client.js +322 -223
  20. package/packages/agent-team/lib/types/context-continuity-host.d.ts.map +1 -1
  21. package/packages/agent-team/lib/types/context-source.d.ts +46 -13
  22. package/packages/agent-team/lib/types/context-source.d.ts.map +1 -1
  23. package/packages/agent-team/lib/types/human-profile.d.ts +83 -11
  24. package/packages/agent-team/lib/types/human-profile.d.ts.map +1 -1
  25. package/packages/agent-team/lib/types/index.d.ts +64 -26
  26. package/packages/agent-team/lib/types/index.d.ts.map +1 -1
  27. package/packages/agent-team/lib/types/member-context.d.ts +14 -0
  28. package/packages/agent-team/lib/types/member-context.d.ts.map +1 -1
  29. package/packages/agent-team/lib/types/member-time-context.d.ts +15 -0
  30. package/packages/agent-team/lib/types/member-time-context.d.ts.map +1 -1
  31. package/packages/agent-team/lib/types/pressure-policy.d.ts.map +1 -1
  32. package/packages/agent-team/lib/types/stored-session-reader.d.ts +1 -2
  33. package/packages/agent-team/lib/types/stored-session-reader.d.ts.map +1 -1
  34. package/packages/agent-team/lib/types/types/entities.d.ts +3 -3
  35. package/packages/agent-team/lib/types/types/requests-results.d.ts +18 -0
  36. package/packages/agent-team/lib/types/types/requests-results.d.ts.map +1 -1
  37. package/packages/client-agent-team/README.md +1 -1
  38. package/packages/client-agent-team/README.zh.md +1 -1
  39. package/packages/client-agent-team/lib/client.js +330 -239
  40. package/packages/client-agent-team/lib/client.js.map +1 -1
  41. package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.js +6 -6
  42. package/packages/client-agent-team/lib/types/client/TeamChannelPage.js +3 -3
  43. package/packages/client-agent-team/lib/types/client/TeamChannelsPanel.js +4 -4
  44. package/packages/client-agent-team/lib/types/client/TeamComposer.js +3 -3
  45. package/packages/client-agent-team/lib/types/client/TeamFooterAction.js +2 -2
  46. package/packages/client-agent-team/lib/types/client/TeamMemberEditor.js +3 -3
  47. package/packages/client-agent-team/lib/types/client/TeamMembersAction.js +2 -2
  48. package/packages/client-agent-team/lib/types/client/TeamMessage.d.ts.map +1 -1
  49. package/packages/client-agent-team/lib/types/client/TeamMessage.js +1 -1
  50. package/packages/client-agent-team/lib/types/client/TeamRowMenu.d.ts.map +1 -1
  51. package/packages/client-agent-team/lib/types/client/TeamRowMenu.js +2 -2
  52. package/packages/client-agent-team/lib/types/client/TeamSidebarSection.js +2 -2
  53. package/packages/client-agent-team/lib/types/client/TeamThreadPage.js +3 -3
  54. package/packages/client-agent-team/lib/types/client/TeamWorkspaceBrowser.js +3 -3
  55. package/packages/client-agent-team/lib/types/client/TeamWorkspaceSelector.js +2 -2
  56. package/packages/client-agent-team/lib/types/client/human-identity.d.ts +4 -8
  57. package/packages/client-agent-team/lib/types/client/human-identity.d.ts.map +1 -1
  58. package/packages/client-agent-team/lib/types/client/human-identity.js +0 -21
  59. package/packages/client-agent-team/lib/types/client/index.d.ts.map +1 -1
  60. package/packages/client-agent-team/lib/types/client/index.js +50 -49
  61. package/packages/client-agent-team/lib/types/client/multi-menu-field.js +2 -2
  62. package/packages/client-agent-team/lib/types/client/slots.d.ts +1 -1
  63. package/packages/tool-agent-team/README.md +1 -1
  64. package/packages/tool-agent-team/README.zh.md +1 -1
  65. package/packages/agent-team/lib/preset-roster.js +0 -15
  66. package/packages/agent-team/lib/session-remediation.js +0 -493
  67. package/packages/agent-team/lib/types/preset-roster.d.ts +0 -5
  68. package/packages/agent-team/lib/types/preset-roster.d.ts.map +0 -1
  69. package/packages/agent-team/lib/types/session-remediation.d.ts +0 -172
  70. package/packages/agent-team/lib/types/session-remediation.d.ts.map +0 -1
  71. package/packages/agent-team/preset/team-member/agent.cordis.yml +0 -166
@@ -1,33 +1,79 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { dirname, resolve } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
1
4
  import z from '@deepseek-ai/schemastery';
5
+ import { parse } from 'yaml';
2
6
  /**
3
7
  * Human identity profile: the one configurable display name plus the avatar
4
8
  * reference. `member:human` stays the durable identity everywhere; only the
5
9
  * handle shown in team_view, @ matching, and UI follows this profile.
6
10
  *
7
11
  * Storage split (see spec.md v1):
8
- * - name + avatarRef live in the Host settings namespace `agent-team-human`
9
- * (settings.yaml user layer, live through `ctx.settings`).
10
- * - avatar bytes live under a persistent directory below; the settings
11
- * document holds only the reference, never a data URL. The composer
12
- * attachment cache is TTL-bound and must not hold avatar bytes.
12
+ * - name + avatarRef are the Team Host row's own Config, so they live in the
13
+ * active profile's patch document as that row's `config` and the settings
14
+ * service derives their form from this schema. rc.1 derives every form from
15
+ * a plugin's Config, so the retired `installSection` namespace — a section of
16
+ * its own — has no counterpart; the row id below is the settings namespace.
17
+ * - avatar bytes live under a persistent directory below; the profile holds
18
+ * only the reference, never a data URL. The composer attachment cache is
19
+ * TTL-bound and must not hold avatar bytes.
13
20
  */
14
- export const HUMAN_PROFILE_SETTINGS_NAMESPACE = 'agent-team-human';
21
+ /**
22
+ * Settings namespace of the Human profile: the Team Host row's id in
23
+ * `cordis.patch.yml`, which is what the settings service addresses a form and a
24
+ * write by AND the id the profile-document write patches. It is addressed by
25
+ * this constant, never by the running Host's `ctx.fiber.entry`: a Remote call
26
+ * runs under its caller's context, so that lookup names the RPC gateway's row.
27
+ * `shipping.spec.ts` pins the constant to the row the composition declares.
28
+ */
29
+ export const HUMAN_PROFILE_SETTINGS_NAMESPACE = 'wowyuarm-agent-team-host';
15
30
  /** Fallback display name before any user override is stored. */
16
31
  export const HUMAN_PROFILE_DEFAULT_NAME = 'human';
17
32
  /** Repository home for the version footnote link. */
18
33
  export const HUMAN_PROFILE_REPO_URL = 'https://github.com/wowyuarm/dsh-agent-team';
19
34
  /**
20
- * Bundle version shown in the settings footnote. Kept in sync with the root
21
- * package.json by hand until the Host build injects it; the footnote is
35
+ * Bundle version shown in the settings footnote, and the current side of the
36
+ * update check: the version of the package THIS Host runs from — read from the
37
+ * installed manifest, so a `link:` checkout under the development profile and a
38
+ * registry tarball under stable each state their own truth. It is not a
39
+ * hand-maintained string: the 0.1.14 bundle shipped with `0.1.13` written in
40
+ * it, which made the footnote name the previous release and the update check
41
+ * offer the release the user already had.
42
+ *
43
+ * Resolved once at load, three levels above this module — `packages/agent-team/{src,lib}`
44
+ * sits that deep in both layouts, the same relative positioning
45
+ * `member-runtime.ts` uses to find `core-skills`. An unreadable or malformed
46
+ * manifest degrades to `'unknown'`: the Remote's `version: string` contract
47
+ * holds and the update comparison simply compares nothing. The footnote is
22
48
  * informational only and never gates behavior.
23
49
  */
24
- export const HUMAN_PROFILE_VERSION = '0.1.13';
25
- /** Schemastery schema resolving the `agent-team-human` namespace value. */
50
+ export const HUMAN_PROFILE_VERSION = readInstalledBundleVersion();
51
+ /** Version of the manifest this package installed from, or `'unknown'`. */
52
+ function readInstalledBundleVersion() {
53
+ try {
54
+ const manifestPath = resolve(dirname(fileURLToPath(import.meta.url)), '../../../package.json');
55
+ const manifest = JSON.parse(readFileSync(manifestPath, 'utf8'));
56
+ if (typeof manifest.version === 'string' && manifest.version !== '')
57
+ return manifest.version;
58
+ }
59
+ catch {
60
+ // Reading our own manifest must never fail the Host boot: a host that
61
+ // cannot find its own package.json is a broken install, and the footnote
62
+ // reporting 'unknown' says so more honestly than a stale number.
63
+ }
64
+ return 'unknown';
65
+ }
66
+ /**
67
+ * Schemastery schema of the Human profile: the Team Host row's Config. Both
68
+ * fields are volatile, which is what lets an edit reach the running Host
69
+ * without remounting it — and what makes the settings service derive a form
70
+ * from this schema at all.
71
+ */
26
72
  export const HUMAN_PROFILE_SETTINGS_SCHEMA = z.object({
27
- name: z.string().default(HUMAN_PROFILE_DEFAULT_NAME),
73
+ name: z.string().default(HUMAN_PROFILE_DEFAULT_NAME).volatile(),
28
74
  // Schemastery fields are optional unless `.required()`: a missing avatarRef
29
75
  // simply resolves absent, which the profile reads as "no custom avatar".
30
- avatarRef: z.string(),
76
+ avatarRef: z.string().volatile(),
31
77
  });
32
78
  /** Normalize one candidate display name the way Member handles normalize. */
33
79
  export function normalizeHumanName(raw) {
@@ -44,3 +90,68 @@ export function assertValidHumanName(raw) {
44
90
  throw new Error('human name must not be empty');
45
91
  return name;
46
92
  }
93
+ /**
94
+ * The retired settings section this profile's facts lived in before rc.1.
95
+ * rc.1 derives every settings form from a plugin's Config, and its legacy
96
+ * `settings.yaml` importer maps a section to a composition entry id through a
97
+ * closed built-in list — this third-party section matches nothing there, so
98
+ * the import rejects it and the values survive only in the renamed document.
99
+ * This Host owns the section, so adopting what is left belongs here.
100
+ */
101
+ export const LEGACY_HUMAN_PROFILE_SECTION = 'agent-team-human';
102
+ /**
103
+ * Read the legacy section out of one legacy settings document. An
104
+ * unparsable document or a missing section means "nothing to adopt" rather
105
+ * than an error — the file is retired input, not an authority — and each
106
+ * field is validated independently so one bad field never costs the other.
107
+ */
108
+ export function parseLegacyHumanProfile(yamlText) {
109
+ let document;
110
+ try {
111
+ document = parse(yamlText);
112
+ }
113
+ catch {
114
+ return undefined;
115
+ }
116
+ if (typeof document !== 'object' || document === null)
117
+ return undefined;
118
+ const section = document[LEGACY_HUMAN_PROFILE_SECTION];
119
+ if (typeof section !== 'object' || section === null)
120
+ return undefined;
121
+ const fields = section;
122
+ let name;
123
+ if (typeof fields.name === 'string') {
124
+ try {
125
+ name = assertValidHumanName(fields.name);
126
+ }
127
+ catch {
128
+ name = undefined;
129
+ }
130
+ }
131
+ const avatarRef = typeof fields.avatarRef === 'string' && fields.avatarRef.trim() !== ''
132
+ ? fields.avatarRef.trim()
133
+ : undefined;
134
+ if (name === undefined && avatarRef === undefined)
135
+ return undefined;
136
+ return { ...(name === undefined ? {} : { name }), ...(avatarRef === undefined ? {} : { avatarRef }) };
137
+ }
138
+ /**
139
+ * Decide what adoption may write: nothing unless the stored profile is still
140
+ * the pristine default, so a value the Human re-entered after the upgrade
141
+ * always wins, and never the legacy default name itself. The current profile
142
+ * carries explicit `undefined` on `avatarRef` — the Host getter spreads a
143
+ * settings read — and so is not `HumanProfile` under
144
+ * exactOptionalPropertyTypes; taking that shape directly keeps the Host call
145
+ * cast-free. The returned fields are exactly the ops the profile page would
146
+ * have written; byte existence for `avatarRef` is the caller's I/O and must
147
+ * already hold.
148
+ */
149
+ export function planLegacyAdoption(current, legacy) {
150
+ if (current.name !== HUMAN_PROFILE_DEFAULT_NAME || current.avatarRef !== undefined)
151
+ return undefined;
152
+ const name = legacy.name !== undefined && legacy.name !== HUMAN_PROFILE_DEFAULT_NAME ? legacy.name : undefined;
153
+ const avatarRef = legacy.avatarRef;
154
+ if (name === undefined && avatarRef === undefined)
155
+ return undefined;
156
+ return { ...(name === undefined ? {} : { name }), ...(avatarRef === undefined ? {} : { avatarRef }) };
157
+ }
@@ -40,6 +40,8 @@ var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn,
40
40
  done = true;
41
41
  };
42
42
  import { randomUUID } from 'node:crypto';
43
+ import { readFileSync } from 'node:fs';
44
+ import { join } from 'node:path';
43
45
  import { isDeepStrictEqual } from 'node:util';
44
46
  import { Service } from '@deepseek-ai/cordis';
45
47
  import { installModelSelection } from '@deepseek-ai/dsh-agent';
@@ -50,18 +52,17 @@ import { Session, SessionId, SessionLogOffset } from '@deepseek-ai/dsh-session';
50
52
  import { setSandboxMode } from '@deepseek-ai/dsh-sandbox-policy';
51
53
  import { Remote, TypertRemoteService } from '@deepseek-ai/dsh-typert-protocol';
52
54
  import { ATTACHMENT_MAX_BYTES, attachmentPayloadPath, attachmentsRoot, copyPathAttachment, newAttachmentId, readAttachment, sanitizeMediaType, sweepAttachmentCache, validatePathAttachment, writeAttachment } from "./attachments.js";
53
- import { HUMAN_PROFILE_DEFAULT_NAME, HUMAN_PROFILE_REPO_URL, HUMAN_PROFILE_SETTINGS_NAMESPACE, HUMAN_PROFILE_SETTINGS_SCHEMA, HUMAN_PROFILE_VERSION, assertValidHumanName, normalizeHumanName } from "./human-profile.js";
55
+ import { HUMAN_PROFILE_DEFAULT_NAME, HUMAN_PROFILE_REPO_URL, HUMAN_PROFILE_SETTINGS_NAMESPACE, HUMAN_PROFILE_SETTINGS_SCHEMA, HUMAN_PROFILE_VERSION, assertValidHumanName, normalizeHumanName, parseLegacyHumanProfile, planLegacyAdoption } from "./human-profile.js";
54
56
  import { humanAvatarsRoot, readHumanAvatar, removeHumanAvatar, writeHumanAvatar } from "./human-avatar.js";
55
57
  import { createHumanUpdateChecker } from "./human-update-check.js";
56
58
  import { PressurePolicyCoordinator } from "./pressure-policy.js";
57
59
  import { CONTEXT_CONTINUITY_PROJECTION_KEY, readContextTimeline } from '@wowyuarm/dsh-context-continuity';
58
60
  import { createTeamContextManagement, TEAM_CONTEXT_CODEC } from "./context-continuity-host.js";
59
- import { AGENT_TEAM_PLUGIN_ID } from "./context-source.js";
61
+ import { AGENT_TEAM_PLUGIN_ID, isAgentTeamSource } from "./context-source.js";
60
62
  import { boundaryByRef, carriedInputOf, checkpointByRef, checkpointRefFor, createTeamContextProjectionConfig, createTeamContextProjectionDefinition, foldTeamContextProjection, retainedTopicsThrough, TeamContextProjectionHost } from "./context-projection.js";
61
63
  import { AGENT_TEAM_HUMAN_MEMBER_ID, AgentTeamLedger, agentTeamHumanActor } from "./ledger.js";
62
64
  import { AGENT_TEAM_TOOL_NAMES, deepCopyCapabilities, memberMemoryDirectoryName, MemberRuntime } from "./member-runtime.js";
63
65
  import { classifyRecoverableError, RecoveryCoordinator, RECOVERY_MAX_CONSECUTIVE_ERRORS } from "./recovery.js";
64
- import { SessionRemediation, handoffAlreadyInLog } from "./session-remediation.js";
65
66
  import { StoredSessionReadError, StoredSessionReader, sessionFailureOf } from "./stored-session-reader.js";
66
67
  import { agentTeamDomainSpec } from "./spec.js";
67
68
  import { formatTeamTimestamp } from "./time-format.js";
@@ -98,7 +99,7 @@ const MAX_TIMELINE_LIMIT = 24;
98
99
  * disagree about where history ends.
99
100
  */
100
101
  const MAX_TIMELINE_ANCESTORS = 8;
101
- /** Product pressure budget constants (see docs/team-collaboration.md). */
102
+ /** Product pressure budget constants (see docs/team-collaboration/README.md). */
102
103
  const CONTEXT_HARD_LIMIT_CAP = 256_000;
103
104
  const CONTEXT_HANDOFF_AT_CAP = 200_000;
104
105
  const CONTEXT_HANDOFF_RESERVE = 8_000;
@@ -198,6 +199,7 @@ let AgentTeam = (() => {
198
199
  let _putHumanAvatar_decorators;
199
200
  let _getHumanAvatar_decorators;
200
201
  let _removeHumanAvatar_decorators;
202
+ let _setHumanProfile_decorators;
201
203
  let _reply_decorators;
202
204
  let _changeAttention_decorators;
203
205
  let _inbox_decorators;
@@ -233,6 +235,7 @@ let AgentTeam = (() => {
233
235
  _putHumanAvatar_decorators = [Remote('putHumanAvatar')];
234
236
  _getHumanAvatar_decorators = [Remote('getHumanAvatar')];
235
237
  _removeHumanAvatar_decorators = [Remote('removeHumanAvatar')];
238
+ _setHumanProfile_decorators = [Remote('setHumanProfile')];
236
239
  _reply_decorators = [Remote('reply')];
237
240
  _changeAttention_decorators = [Remote('changeAttention')];
238
241
  _inbox_decorators = [Remote('inbox')];
@@ -265,6 +268,7 @@ let AgentTeam = (() => {
265
268
  __esDecorate(this, null, _putHumanAvatar_decorators, { kind: "method", name: "putHumanAvatar", static: false, private: false, access: { has: obj => "putHumanAvatar" in obj, get: obj => obj.putHumanAvatar }, metadata: _metadata }, null, _instanceExtraInitializers);
266
269
  __esDecorate(this, null, _getHumanAvatar_decorators, { kind: "method", name: "getHumanAvatar", static: false, private: false, access: { has: obj => "getHumanAvatar" in obj, get: obj => obj.getHumanAvatar }, metadata: _metadata }, null, _instanceExtraInitializers);
267
270
  __esDecorate(this, null, _removeHumanAvatar_decorators, { kind: "method", name: "removeHumanAvatar", static: false, private: false, access: { has: obj => "removeHumanAvatar" in obj, get: obj => obj.removeHumanAvatar }, metadata: _metadata }, null, _instanceExtraInitializers);
271
+ __esDecorate(this, null, _setHumanProfile_decorators, { kind: "method", name: "setHumanProfile", static: false, private: false, access: { has: obj => "setHumanProfile" in obj, get: obj => obj.setHumanProfile }, metadata: _metadata }, null, _instanceExtraInitializers);
268
272
  __esDecorate(this, null, _reply_decorators, { kind: "method", name: "reply", static: false, private: false, access: { has: obj => "reply" in obj, get: obj => obj.reply }, metadata: _metadata }, null, _instanceExtraInitializers);
269
273
  __esDecorate(this, null, _changeAttention_decorators, { kind: "method", name: "changeAttention", static: false, private: false, access: { has: obj => "changeAttention" in obj, get: obj => obj.changeAttention }, metadata: _metadata }, null, _instanceExtraInitializers);
270
274
  __esDecorate(this, null, _inbox_decorators, { kind: "method", name: "inbox", static: false, private: false, access: { has: obj => "inbox" in obj, get: obj => obj.inbox }, metadata: _metadata }, null, _instanceExtraInitializers);
@@ -274,6 +278,8 @@ let AgentTeam = (() => {
274
278
  __esDecorate(this, null, _view_decorators, { kind: "method", name: "view", static: false, private: false, access: { has: obj => "view" in obj, get: obj => obj.view }, metadata: _metadata }, null, _instanceExtraInitializers);
275
279
  if (_metadata) Object.defineProperty(this, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
276
280
  }
281
+ config = __runInitializers(this, _instanceExtraInitializers);
282
+ static Config = HUMAN_PROFILE_SETTINGS_SCHEMA;
277
283
  static inject = [
278
284
  'storageDomain',
279
285
  'workspaceRegistry',
@@ -284,7 +290,7 @@ let AgentTeam = (() => {
284
290
  'sessionPersistence',
285
291
  'sessionProjections',
286
292
  ];
287
- domain = __runInitializers(this, _instanceExtraInitializers);
293
+ domain;
288
294
  ledger;
289
295
  handles = new Map();
290
296
  /** Live Member per session id; drives the root session/event listener. */
@@ -324,12 +330,6 @@ let AgentTeam = (() => {
324
330
  pressurePolicy;
325
331
  notifiedInbox = new Map();
326
332
  attachmentGcTimer;
327
- /**
328
- * Current human profile source, wired to the `agent-team-human` settings
329
- * namespace when a settings service is present. Falls back to the historic
330
- * default so team_view and @ matching keep working without settings.
331
- */
332
- humanProfileSource = () => ({ name: HUMAN_PROFILE_DEFAULT_NAME });
333
333
  /**
334
334
  * New-release check behind the settings footnote. Memory-only and
335
335
  * background-refreshed, so the profile read path never waits on the
@@ -361,7 +361,7 @@ let AgentTeam = (() => {
361
361
  * successful `context_rollover` tool result into its next private context
362
362
  * generation. The ledger owns the binding audit, the engine's projection
363
363
  * unit owns intent, and this coordinator owns only reconstructible process
364
- * state. See docs/architecture.md and docs/team-collaboration.md.
364
+ * state. See docs/architecture/README.md and docs/team-collaboration/README.md.
365
365
  */
366
366
  contextManagement = createTeamContextManagement({
367
367
  agentForMember: memberId => this.handles.get(memberId)?.agent,
@@ -381,6 +381,8 @@ let AgentTeam = (() => {
381
381
  });
382
382
  lifecycleTail = Promise.resolve();
383
383
  accepting = true;
384
+ /** One adoption attempt per boot: the two readiness edges fire once each, and this keeps their attempt single. */
385
+ legacyAdoptionStarted = false;
384
386
  /**
385
387
  * Presence wake epoch: Agent running/idle/failure is runtime state with no
386
388
  * durable fact behind it, so the presence scope counts those edge wakes in
@@ -388,14 +390,9 @@ let AgentTeam = (() => {
388
390
  */
389
391
  presenceEpoch = 0;
390
392
  changeWaiters = new Set();
391
- /**
392
- * The startup-opened remediation instance, held for the restart heal: the
393
- * completion-cache domain may only be opened once per plugin lifecycle, so
394
- * the restart path reuses this instance instead of opening its own.
395
- */
396
- remediation;
397
- constructor(ctx) {
393
+ constructor(ctx, config) {
398
394
  super(ctx, 'agentTeam');
395
+ this.config = config;
399
396
  this.pressurePolicy = new PressurePolicyCoordinator({
400
397
  agentForMember: memberId => this.handles.get(memberId)?.agent,
401
398
  memberForAgent: agent => {
@@ -412,37 +409,30 @@ let AgentTeam = (() => {
412
409
  },
413
410
  log: message => { this.ctx.logger.warn(`agent-team: ${message}`); },
414
411
  });
415
- // Human profile settings: the composition entry is the historic default;
416
- // the settings provider overlays the user document section when present.
417
- // Reads stay live through the source closure, so team_view and @ matching
418
- // follow a rename without a Host restart.
412
+ // Human profile: this Host row's own Config (name + avatarRef, both
413
+ // volatile), so the settings service derives its form from the schema and
414
+ // an edit lands in the running plugin without a remount. Team's own page
415
+ // owns that surface, so the row declares itself presentation-owned rather
416
+ // than schema-page owned. Reads go straight to the live Config reference;
417
+ // this subscription is the change signal the retired section hook used to
418
+ // be — the ledger's runtime @ handle follows every edit.
419
419
  this.ctx.inject(['settings'], settingsCtx => {
420
- settingsCtx.settings.installSection(this.ctx, HUMAN_PROFILE_SETTINGS_NAMESPACE, HUMAN_PROFILE_SETTINGS_SCHEMA, { name: HUMAN_PROFILE_DEFAULT_NAME }, {
421
- setSource: (current) => {
422
- this.humanProfileSource = current;
423
- this.syncHumanHandle();
424
- },
425
- validate: (value) => {
426
- this.validateHumanProfile(value);
427
- },
428
- onChange: () => {
429
- this.syncHumanHandle();
430
- },
431
- });
420
+ settingsCtx.effect(() => settingsCtx.settings.configure({ auto: false }, this.ctx.fiber));
421
+ this.adoptLegacyHumanProfile();
432
422
  });
423
+ this.ctx.on('loader/volatile-update', () => { this.syncHumanHandle(); });
433
424
  }
434
425
  /** Current human display name; the single source for team_view and @ matching. */
435
426
  humanHandle() {
436
- const name = normalizeHumanName(this.humanProfileSource().name);
427
+ const name = normalizeHumanName(this.config.name.get());
437
428
  return name === '' ? HUMAN_PROFILE_DEFAULT_NAME : name;
438
429
  }
439
- /** Current human profile reference held in settings (name + avatarRef). */
430
+ /** Current human profile reference held in this Host row's Config (name + avatarRef). */
440
431
  humanProfile() {
441
- const current = this.humanProfileSource();
442
- const name = this.humanHandle();
443
- return Object.freeze({ name, ...(current.avatarRef === undefined ? {} : { avatarRef: current.avatarRef }) });
432
+ const avatarRef = this.config.avatarRef.get();
433
+ return Object.freeze({ name: this.humanHandle(), ...(avatarRef === undefined ? {} : { avatarRef }) });
444
434
  }
445
- /** Push the current settings name into the ledger's runtime @ handle. */
435
+ /** Push the current Config name into the ledger's runtime @ handle. */
446
436
  syncHumanHandle() {
447
437
  try {
448
438
  this.ledger?.setHumanDisplayHandle(this.humanHandle());
@@ -452,9 +442,10 @@ let AgentTeam = (() => {
452
442
  }
453
443
  }
454
444
  /**
455
- * Settings-level validation for the human profile: same non-empty floor as
456
- * Member handles plus global uniqueness against live Members. Runs inside
457
- * the settings write path, so a colliding rename rejects before persisting.
445
+ * The two judgements the Config schema cannot make about a Human name: the
446
+ * same non-empty floor as Member handles, plus global uniqueness against live
447
+ * Members. `setHumanProfile` runs it before the write, so a colliding rename
448
+ * rejects instead of persisting.
458
449
  */
459
450
  validateHumanProfile(value) {
460
451
  const name = assertValidHumanName(value.name);
@@ -470,6 +461,73 @@ let AgentTeam = (() => {
470
461
  }
471
462
  }
472
463
  }
464
+ /**
465
+ * One-time adoption of the profile facts the retired `agent-team-human`
466
+ * settings section held (see `LEGACY_HUMAN_PROFILE_SECTION` for why the
467
+ * upstream importer cannot carry them over). The values land in this Host
468
+ * row's own Config through the same Remote the profile page writes, so the
469
+ * page, the ledger's @ handle, and every avatar seat follow as after any
470
+ * edit. Readiness has two one-way edges — the settings service arrives, the
471
+ * ledger opens — and whichever fires second starts the attempt; only a
472
+ * pristine profile is adopted, and one attempt per boot is its whole
473
+ * lifetime, so adoption never loops and never re-writes.
474
+ */
475
+ adoptLegacyHumanProfile() {
476
+ if (this.legacyAdoptionStarted)
477
+ return;
478
+ if (this.ctx.get('settings') === undefined || this.ledger === undefined)
479
+ return;
480
+ this.legacyAdoptionStarted = true;
481
+ void this.adoptLegacyHumanProfileNow().catch((error) => {
482
+ this.ctx.logger.warn('agent-team: legacy human profile adoption failed: %s', String(error));
483
+ });
484
+ }
485
+ /** Carry the first legacy document that still has the section into a pristine profile. */
486
+ async adoptLegacyHumanProfileNow() {
487
+ // A profile-launched Host always carries the profile context; a Host booted
488
+ // without one still adopts from the DSH home, which is where a pre-rc.1
489
+ // install kept the document. Read through `get`: cordis refuses a direct
490
+ // `ctx.profileContext` for a service this plugin does not declare, and
491
+ // adoption may not gate the Host on app-boot.
492
+ const profileHome = this.ctx.get('profileContext')?.home;
493
+ const homes = profileHome === undefined ? [dshHomePath()] : [dshHomePath(), profileHome];
494
+ const candidates = new Set(homes.flatMap(home => [join(home, 'settings.yaml'), join(home, 'settings.yaml.imported')]));
495
+ let legacy;
496
+ let source;
497
+ for (const candidate of candidates) {
498
+ let text;
499
+ try {
500
+ text = readFileSync(candidate, 'utf8');
501
+ }
502
+ catch {
503
+ // Absent candidates are the steady state once adoption settles; the
504
+ // live document is one only because the first boot after the upgrade
505
+ // can race the importer that renames it.
506
+ continue;
507
+ }
508
+ const parsed = parseLegacyHumanProfile(text);
509
+ if (parsed !== undefined) {
510
+ legacy = parsed;
511
+ source = candidate;
512
+ break;
513
+ }
514
+ }
515
+ if (legacy === undefined || source === undefined)
516
+ return;
517
+ // The reference is carried only while its bytes still draw: the avatar
518
+ // store is the authority for whether this avatar exists at all.
519
+ let avatarRef = legacy.avatarRef;
520
+ if (avatarRef !== undefined && await readHumanAvatar(humanAvatarsRoot(), avatarRef) === undefined)
521
+ avatarRef = undefined;
522
+ const plan = planLegacyAdoption(this.humanProfile(), {
523
+ ...(legacy.name === undefined ? {} : { name: legacy.name }),
524
+ ...(avatarRef === undefined ? {} : { avatarRef }),
525
+ });
526
+ if (plan === undefined)
527
+ return;
528
+ await this.setHumanProfile(plan);
529
+ this.ctx.logger.info('agent-team: adopted the legacy human profile from %s', source);
530
+ }
473
531
  /** Open the durable ledger and restore every enabled Member independently. */
474
532
  async [Service.init]() {
475
533
  // The continuity projection registers once per Host: the framework keeps
@@ -545,7 +603,6 @@ let AgentTeam = (() => {
545
603
  const domain = await this.ctx.storageDomain.open(agentTeamDomainSpec);
546
604
  this.ctx.effect(() => async () => {
547
605
  this.accepting = false;
548
- this.remediation = undefined;
549
606
  this.recovery.dispose();
550
607
  this.contextManagement.dispose();
551
608
  this.pressurePolicy.dispose();
@@ -564,25 +621,15 @@ let AgentTeam = (() => {
564
621
  this.domain = domain;
565
622
  const ledger = new AgentTeamLedger(domain.table('operations'));
566
623
  this.ledger = ledger;
567
- // The settings wiring in the constructor may have fired before the ledger
568
- // existed; sync once here so @ matching starts from the stored name.
624
+ // A live Config edit in the constructor's window may have arrived before
625
+ // the ledger existed; sync once here so @ matching starts from the stored
626
+ // name.
569
627
  this.syncHumanHandle();
628
+ this.adoptLegacyHumanProfile();
570
629
  const initialization = await ledger.initialize();
571
630
  if (initialization.committed)
572
631
  this.emitCommitted(initialization.value);
573
632
  this.startAttachmentGc(ledger);
574
- // Legacy-artifact remediation runs before any Member activation: no write
575
- // lease exists yet, so publishing sibling generations for refused Session
576
- // logs cannot race a live writer. Remediation failure never blocks
577
- // startup — the next start retries exactly what the cache does not cover.
578
- try {
579
- const remediation = new SessionRemediation(this.ctx, this.ctx.sessionPersistence, await SessionRemediation.open(this.ctx));
580
- this.remediation = remediation;
581
- await remediation.remediateEnabledMembers(ledger.listMembers());
582
- }
583
- catch (error) {
584
- this.ctx.logger.warn(`agent-team: legacy Session remediation did not run to completion (it will retry on the next start): ${error instanceof Error ? error.message : String(error)}`);
585
- }
586
633
  // One metadata listing serves every Member restore; per-member list calls
587
634
  // would repeat the same I/O linearly during startup.
588
635
  const persistedSessions = new Set((await this.persistedSessionHeaders()).map(snapshot => snapshot.header.id));
@@ -836,23 +883,10 @@ let AgentTeam = (() => {
836
883
  if (member.state !== 'enabled')
837
884
  throw new Error(`Agent Member '${member.handle}' is ${member.state}; only enabled Members can be restarted`);
838
885
  this.ctx.logger.info(`agent-team: restarting member '${member.handle}' after a failed activation`);
839
- // A deterministic session refusal may be repairable in place: run the
840
- // same bounded startup remediation for this one Member before retrying
841
- // activation, so the restart heals instead of replaying the failure.
842
- const activation = this.memberFailures.get(request.memberId)?.activation;
843
- if (activation !== undefined && activation.class === 'session-refused' && this.remediation !== undefined) {
844
- const outcome = await this.remediation.remediateMember(member);
845
- if (outcome.repaired > 0) {
846
- this.ctx.logger.info(`agent-team: repaired ${outcome.repaired} refused Session artifact(s) for member '${member.handle}'; retrying activation`);
847
- }
848
- else if (outcome.completed) {
849
- // The walk finished and nothing was provably this plugin's to fix:
850
- // a retry would fail identically. Mark the refusal non-remediable
851
- // so the surface stops offering restart and says why.
852
- this.markRefusalNonRemediable(request.memberId);
853
- return Object.freeze({ status: this.memberStatus(member) });
854
- }
855
- }
886
+ // There is no write-side repair pass anymore: dsh 0.1.7 converts the
887
+ // released V3 history at read time, and this bundle no longer authors
888
+ // the old wrapper shape, so a refusal stays a deterministic failure the
889
+ // retry reports again rather than something the restart heals.
856
890
  await this.reactivateMember(request.memberId);
857
891
  return Object.freeze({ status: this.memberStatus(member) });
858
892
  }
@@ -1068,7 +1102,7 @@ let AgentTeam = (() => {
1068
1102
  const body = notifications.length === 0 ? text : `${text}\n\n${this.notificationText(notifications, member.memberId)}`;
1069
1103
  const hint = createUserMessage({
1070
1104
  content: [{ type: 'text', text: body }],
1071
- source: { kind: 'plugin', plugin: AGENT_TEAM_PLUGIN_ID, form: 'notice', summary: RECOVERY_NOTICE_SUMMARY },
1105
+ source: { kind: AGENT_TEAM_PLUGIN_ID, form: 'notice', summary: RECOVERY_NOTICE_SUMMARY },
1072
1106
  });
1073
1107
  for (const pending of [...handle.agent.inbox.nextStep, ...handle.agent.inbox.nextTurn]) {
1074
1108
  if (this.isInboxNotice(pending))
@@ -1276,9 +1310,9 @@ let AgentTeam = (() => {
1276
1310
  }
1277
1311
  /**
1278
1312
  * Human profile read for the settings page and footnote: name + avatar
1279
- * reference from settings plus version facts. Human-scoped (the Web
1280
- * Client calls it); agent tools never receive avatar bytes, only the name
1281
- * through team_view. The new-release check stays best-effort and cached —
1313
+ * reference from this Host row's live Config plus version facts. Human-scoped
1314
+ * (the Web Client calls it); agent tools never receive avatar bytes, only the
1315
+ * name through team_view. The new-release check stays best-effort and cached —
1282
1316
  * `updateAvailable` is false until a background refresh actually observes a
1283
1317
  * newer published release.
1284
1318
  */
@@ -1318,6 +1352,47 @@ let AgentTeam = (() => {
1318
1352
  await removeHumanAvatar(humanAvatarsRoot(), request.avatarRef);
1319
1353
  return Object.freeze({ removed: true });
1320
1354
  }
1355
+ /**
1356
+ * Overwrite the Human profile fields the caller supplies. The Host owns this
1357
+ * write because the profile is the Host row's own Config: the schema supplies
1358
+ * the shape, this method supplies the two judgements the schema cannot make
1359
+ * (a non-empty name, a name no live Member already answers to), and the
1360
+ * settings service persists the result into the active profile's patch
1361
+ * document and applies it to the running plugin live.
1362
+ *
1363
+ * No expected revision accompanies the write: the profile is two scalar
1364
+ * fields written from the Human's own pages, where the last write wins. This
1365
+ * is not a hard boundary around a user-editable document — the settings
1366
+ * service's own document opener (and a text editor) can change the stored
1367
+ * name without passing here, exactly as the retired section validator could
1368
+ * not stop it.
1369
+ */
1370
+ async setHumanProfile(request) {
1371
+ this.requireAccepting();
1372
+ const settings = this.ctx.get('settings');
1373
+ if (settings === undefined)
1374
+ throw new Error('the settings service is unavailable');
1375
+ const name = request.name === undefined ? undefined : assertValidHumanName(request.name);
1376
+ if (name !== undefined)
1377
+ this.validateHumanProfile({ name });
1378
+ const ops = [];
1379
+ if (name !== undefined)
1380
+ ops.push({ op: 'set', path: ['name'], value: name });
1381
+ if (request.avatarRef === null)
1382
+ ops.push({ op: 'unset', path: ['avatarRef'] });
1383
+ else if (request.avatarRef !== undefined)
1384
+ ops.push({ op: 'set', path: ['avatarRef'], value: request.avatarRef });
1385
+ if (ops.length !== 0) {
1386
+ // The settings namespace IS this Host row's id in the composition, and it
1387
+ // must be addressed by that name rather than by `ctx.fiber.entry` here: a
1388
+ // Remote call runs under its caller's context, whose fiber entry is the
1389
+ // RPC gateway's row (`typert-gateway`), which the settings service then
1390
+ // looks up as an unrelated plugin. shipping.spec.ts pins the constant to
1391
+ // the row `cordis.patch.yml` declares.
1392
+ await settings.mutate(HUMAN_PROFILE_SETTINGS_NAMESPACE, ops);
1393
+ }
1394
+ return Object.freeze({ ...this.humanProfile() });
1395
+ }
1321
1396
  /** Human existing-Thread reply; unread and revision conflicts are business outcomes. */
1322
1397
  async reply(request) {
1323
1398
  return this.replyAs(this.humanCall(request.workspaceId), request);
@@ -1483,7 +1558,7 @@ let AgentTeam = (() => {
1483
1558
  try {
1484
1559
  const message = createUserMessage({
1485
1560
  content: [{ type: 'text', text: this.dmRelayText(agent, recipient, request.body.trim(), result.value.receipt.occurredAt, result.value.receipt.operationId, request.workspaceId) }],
1486
- source: { kind: 'plugin', plugin: AGENT_TEAM_PLUGIN_ID, form: 'relay' },
1561
+ source: { kind: AGENT_TEAM_PLUGIN_ID, form: 'relay' },
1487
1562
  });
1488
1563
  // An idle recipient gets one ordinary turn; a busy one is steered into
1489
1564
  // its current turn — the same wake split subagent continuations use.
@@ -2430,12 +2505,11 @@ let AgentTeam = (() => {
2430
2505
  // delivered activates the new Session with no handoff in its own
2431
2506
  // log — never treat that as an ordinary blank Member Session. The
2432
2507
  // operation's recorded previous Session (the lineage parent) still
2433
- // holds the intent; rebuild the handoff from it. "No handoff in its
2434
- // own log" is judged over both shapes: a generation rescued from the
2435
- // retired custom kinds carries its handoff as a source the projection
2436
- // does not classify, and rebuilding on top of it would inject the same
2437
- // handoff twice.
2438
- if (!handoffAlreadyInLog(state.boundaries, created.agent.session.ownEvents())) {
2508
+ // holds the intent; rebuild the handoff from it. Presence is judged
2509
+ // by the projection boundary alone: every handoff this Host ever
2510
+ // published classifies under the context source's recognizer,
2511
+ // including the history the read-time conversion renamed.
2512
+ if (!state.boundaries.some(boundary => boundary.kind === 'handoff')) {
2439
2513
  await this.reconstructMissingHandoff(member, created.agent);
2440
2514
  }
2441
2515
  // Carried input redelivery binds to the committed transition target —
@@ -2609,14 +2683,6 @@ let AgentTeam = (() => {
2609
2683
  }
2610
2684
  return { class: 'activation', detail: error instanceof Error ? error.message : String(error) };
2611
2685
  }
2612
- /** Mark a session-refused activation diagnostic as proven non-remediable. */
2613
- markRefusalNonRemediable(memberId) {
2614
- const failures = this.memberFailures.get(memberId);
2615
- const activation = failures?.activation;
2616
- if (activation === undefined || activation.class !== 'session-refused')
2617
- return;
2618
- failures.activation = Object.freeze({ ...activation, remediable: false });
2619
- }
2620
2686
  clearMemberFailure(memberId, slot) {
2621
2687
  const failures = this.memberFailures.get(memberId);
2622
2688
  if (failures === undefined || failures[slot] === undefined)
@@ -2657,7 +2723,7 @@ let AgentTeam = (() => {
2657
2723
  const path = this.ctx.workspaceRegistry.get(operation.data.workspaceId)?.path;
2658
2724
  const text = `Team participation changed: you have ${operation.kind === 'team/member-workspace-joined' ? 'joined' : 'left'} Workspace ${operation.data.workspaceId}${path === undefined ? ' (path unavailable)' : ` (${JSON.stringify(path)})`}.\nCurrent Workspace ids: ${ledger.workspacesOf(operation.data.memberId).join(', ')}. This replaces earlier participation information. Your Session and cwd have not moved.`;
2659
2725
  const notice = createUserMessage({ content: [{ type: 'text', text }],
2660
- source: { kind: 'plugin', plugin: AGENT_TEAM_PLUGIN_ID, form: 'notice', summary: 'Team Workspace participation changed' } });
2726
+ source: { kind: AGENT_TEAM_PLUGIN_ID, form: 'notice', summary: 'Team Workspace participation changed' } });
2661
2727
  try {
2662
2728
  if (agent.status === 'idle' || agent.inbox.nextTurn.some(message => message.source.kind === 'user'))
2663
2729
  agent.followup(notice);
@@ -2754,7 +2820,7 @@ let AgentTeam = (() => {
2754
2820
  agent.inbox.remove(existingInboxHint.id);
2755
2821
  const hint = createUserMessage({
2756
2822
  content: [{ type: 'text', text: this.notificationText(notifications, member.memberId) }],
2757
- source: { kind: 'plugin', plugin: AGENT_TEAM_PLUGIN_ID, form: 'notice', summary: INBOX_NOTICE_SUMMARY },
2823
+ source: { kind: AGENT_TEAM_PLUGIN_ID, form: 'notice', summary: INBOX_NOTICE_SUMMARY },
2758
2824
  });
2759
2825
  this.notifiedInbox.set(member.memberId, signature);
2760
2826
  try {
@@ -2775,12 +2841,12 @@ let AgentTeam = (() => {
2775
2841
  }
2776
2842
  isInboxNotice(message) {
2777
2843
  const source = message.source;
2778
- return source.kind === 'plugin' && source.plugin === AGENT_TEAM_PLUGIN_ID
2844
+ return isAgentTeamSource(source)
2779
2845
  && source.form === 'notice' && source.summary === INBOX_NOTICE_SUMMARY;
2780
2846
  }
2781
2847
  isRecoveryNotice(message) {
2782
2848
  const source = message.source;
2783
- return source.kind === 'plugin' && source.plugin === AGENT_TEAM_PLUGIN_ID
2849
+ return isAgentTeamSource(source)
2784
2850
  && source.form === 'notice' && source.summary === RECOVERY_NOTICE_SUMMARY;
2785
2851
  }
2786
2852
  notificationText(notifications, readerId) {