@wowyuarm/dsh-agent-team 0.1.14-alpha.2 → 0.1.14

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 (127) hide show
  1. package/README.md +5 -3
  2. package/README.zh.md +5 -3
  3. package/cordis.patch.yml +2 -185
  4. package/package.json +52 -46
  5. package/packages/agent-team/README.md +1 -1
  6. package/packages/agent-team/README.zh.md +1 -1
  7. package/packages/agent-team/core-skills/member-memory-manager/SKILL.md +30 -0
  8. package/packages/agent-team/lib/context-continuity-host.js +91 -0
  9. package/packages/agent-team/lib/context-projection.js +266 -489
  10. package/packages/agent-team/lib/context-source.js +17 -106
  11. package/packages/agent-team/lib/human-avatar.js +61 -0
  12. package/packages/agent-team/lib/human-profile.js +46 -0
  13. package/packages/agent-team/lib/human-update-check.js +119 -0
  14. package/packages/agent-team/lib/index.js +408 -264
  15. package/packages/agent-team/lib/ledger.js +120 -15
  16. package/packages/agent-team/lib/member-context.js +9 -8
  17. package/packages/agent-team/lib/member-runtime.js +1 -1
  18. package/packages/agent-team/lib/member-time-context.js +2 -5
  19. package/packages/agent-team/lib/preset-roster.js +15 -0
  20. package/packages/agent-team/lib/pressure-policy.js +9 -6
  21. package/packages/agent-team/lib/session-remediation.js +493 -0
  22. package/packages/agent-team/lib/stored-session-reader.js +2 -1
  23. package/packages/agent-team/lib/typert.host.js +605 -291
  24. package/packages/agent-team/lib/typert.remote-client.d.ts +15 -6
  25. package/packages/agent-team/lib/typert.remote-client.d.ts.map +1 -1
  26. package/packages/agent-team/lib/typert.remote-client.js +367 -244
  27. package/packages/agent-team/lib/types/context-continuity-host.d.ts +84 -0
  28. package/packages/agent-team/lib/types/context-continuity-host.d.ts.map +1 -0
  29. package/packages/agent-team/lib/types/context-projection.d.ts +130 -185
  30. package/packages/agent-team/lib/types/context-projection.d.ts.map +1 -1
  31. package/packages/agent-team/lib/types/context-source.d.ts +17 -81
  32. package/packages/agent-team/lib/types/context-source.d.ts.map +1 -1
  33. package/packages/agent-team/lib/types/human-avatar.d.ts +29 -0
  34. package/packages/agent-team/lib/types/human-avatar.d.ts.map +1 -0
  35. package/packages/agent-team/lib/types/human-profile.d.ts +44 -0
  36. package/packages/agent-team/lib/types/human-profile.d.ts.map +1 -0
  37. package/packages/agent-team/lib/types/human-update-check.d.ts +73 -0
  38. package/packages/agent-team/lib/types/human-update-check.d.ts.map +1 -0
  39. package/packages/agent-team/lib/types/index.d.ts +112 -38
  40. package/packages/agent-team/lib/types/index.d.ts.map +1 -1
  41. package/packages/agent-team/lib/types/ledger.d.ts +31 -2
  42. package/packages/agent-team/lib/types/ledger.d.ts.map +1 -1
  43. package/packages/agent-team/lib/types/member-context.d.ts +0 -14
  44. package/packages/agent-team/lib/types/member-context.d.ts.map +1 -1
  45. package/packages/agent-team/lib/types/member-time-context.d.ts +0 -15
  46. package/packages/agent-team/lib/types/member-time-context.d.ts.map +1 -1
  47. package/packages/agent-team/lib/types/preset-roster.d.ts +5 -0
  48. package/packages/agent-team/lib/types/preset-roster.d.ts.map +1 -0
  49. package/packages/agent-team/lib/types/pressure-policy.d.ts +6 -0
  50. package/packages/agent-team/lib/types/pressure-policy.d.ts.map +1 -1
  51. package/packages/agent-team/lib/types/session-remediation.d.ts +172 -0
  52. package/packages/agent-team/lib/types/session-remediation.d.ts.map +1 -0
  53. package/packages/agent-team/lib/types/stored-session-reader.d.ts +2 -1
  54. package/packages/agent-team/lib/types/stored-session-reader.d.ts.map +1 -1
  55. package/packages/agent-team/lib/types/types/entities.d.ts +20 -3
  56. package/packages/agent-team/lib/types/types/entities.d.ts.map +1 -1
  57. package/packages/agent-team/lib/types/types/requests-results.d.ts +63 -0
  58. package/packages/agent-team/lib/types/types/requests-results.d.ts.map +1 -1
  59. package/packages/agent-team/preset/team-member/agent.cordis.yml +166 -0
  60. package/packages/client-agent-team/lib/client.js +1667 -516
  61. package/packages/client-agent-team/lib/client.js.map +1 -1
  62. package/packages/client-agent-team/lib/types/client/HumanSettingsSection.d.ts +15 -0
  63. package/packages/client-agent-team/lib/types/client/HumanSettingsSection.d.ts.map +1 -0
  64. package/packages/client-agent-team/lib/types/client/HumanSettingsSection.js +115 -0
  65. package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.d.ts.map +1 -1
  66. package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.js +12 -8
  67. package/packages/client-agent-team/lib/types/client/TeamAvatarStack.d.ts +22 -1
  68. package/packages/client-agent-team/lib/types/client/TeamAvatarStack.d.ts.map +1 -1
  69. package/packages/client-agent-team/lib/types/client/TeamAvatarStack.js +25 -2
  70. package/packages/client-agent-team/lib/types/client/TeamChannelPage.d.ts +6 -1
  71. package/packages/client-agent-team/lib/types/client/TeamChannelPage.d.ts.map +1 -1
  72. package/packages/client-agent-team/lib/types/client/TeamChannelPage.js +69 -31
  73. package/packages/client-agent-team/lib/types/client/TeamChannelsPanel.js +4 -4
  74. package/packages/client-agent-team/lib/types/client/TeamComposer.js +3 -3
  75. package/packages/client-agent-team/lib/types/client/TeamConversation.d.ts +1 -1
  76. package/packages/client-agent-team/lib/types/client/TeamConversation.d.ts.map +1 -1
  77. package/packages/client-agent-team/lib/types/client/TeamConversation.js +12 -5
  78. package/packages/client-agent-team/lib/types/client/TeamFooterAction.js +2 -2
  79. package/packages/client-agent-team/lib/types/client/TeamInboxPage.d.ts +4 -1
  80. package/packages/client-agent-team/lib/types/client/TeamInboxPage.d.ts.map +1 -1
  81. package/packages/client-agent-team/lib/types/client/TeamInboxPage.js +22 -7
  82. package/packages/client-agent-team/lib/types/client/TeamMemberEditor.d.ts +11 -1
  83. package/packages/client-agent-team/lib/types/client/TeamMemberEditor.d.ts.map +1 -1
  84. package/packages/client-agent-team/lib/types/client/TeamMemberEditor.js +69 -8
  85. package/packages/client-agent-team/lib/types/client/TeamMembersAction.js +2 -2
  86. package/packages/client-agent-team/lib/types/client/TeamMessage.d.ts +18 -3
  87. package/packages/client-agent-team/lib/types/client/TeamMessage.d.ts.map +1 -1
  88. package/packages/client-agent-team/lib/types/client/TeamMessage.js +213 -67
  89. package/packages/client-agent-team/lib/types/client/TeamRowMenu.d.ts.map +1 -1
  90. package/packages/client-agent-team/lib/types/client/TeamRowMenu.js +2 -2
  91. package/packages/client-agent-team/lib/types/client/TeamSidebarSection.js +2 -2
  92. package/packages/client-agent-team/lib/types/client/TeamThreadPage.d.ts +5 -0
  93. package/packages/client-agent-team/lib/types/client/TeamThreadPage.d.ts.map +1 -1
  94. package/packages/client-agent-team/lib/types/client/TeamThreadPage.js +55 -29
  95. package/packages/client-agent-team/lib/types/client/TeamWorkspaceBrowser.js +3 -3
  96. package/packages/client-agent-team/lib/types/client/TeamWorkspaceSelector.js +2 -2
  97. package/packages/client-agent-team/lib/types/client/avatar-image.d.ts +21 -0
  98. package/packages/client-agent-team/lib/types/client/avatar-image.d.ts.map +1 -0
  99. package/packages/client-agent-team/lib/types/client/avatar-image.js +9 -0
  100. package/packages/client-agent-team/lib/types/client/human-identity.d.ts +94 -0
  101. package/packages/client-agent-team/lib/types/client/human-identity.d.ts.map +1 -0
  102. package/packages/client-agent-team/lib/types/client/human-identity.js +117 -0
  103. package/packages/client-agent-team/lib/types/client/index.d.ts.map +1 -1
  104. package/packages/client-agent-team/lib/types/client/index.js +112 -41
  105. package/packages/client-agent-team/lib/types/client/locales.d.ts +72 -2
  106. package/packages/client-agent-team/lib/types/client/locales.d.ts.map +1 -1
  107. package/packages/client-agent-team/lib/types/client/locales.js +78 -2
  108. package/packages/client-agent-team/lib/types/client/multi-menu-field.js +2 -2
  109. package/packages/client-agent-team/lib/types/client/refs.d.ts +71 -0
  110. package/packages/client-agent-team/lib/types/client/refs.d.ts.map +1 -0
  111. package/packages/client-agent-team/lib/types/client/refs.js +181 -0
  112. package/packages/client-agent-team/lib/types/client/slots.d.ts +8 -2
  113. package/packages/client-agent-team/lib/types/client/slots.d.ts.map +1 -1
  114. package/packages/client-agent-team/lib/types/client/team-formatters.d.ts +57 -9
  115. package/packages/client-agent-team/lib/types/client/team-formatters.d.ts.map +1 -1
  116. package/packages/client-agent-team/lib/types/client/team-formatters.js +85 -17
  117. package/packages/tool-agent-team/README.md +1 -1
  118. package/packages/tool-agent-team/README.zh.md +1 -1
  119. package/packages/tool-agent-team/lib/context-tools.js +20 -6
  120. package/packages/tool-agent-team/lib/index.js +2 -2
  121. package/packages/tool-agent-team/lib/types/context-tools.d.ts.map +1 -1
  122. package/packages/agent-team/lib/context-management.js +0 -371
  123. package/packages/agent-team/lib/types/context-management.d.ts +0 -154
  124. package/packages/agent-team/lib/types/context-management.d.ts.map +0 -1
  125. package/packages/client-agent-team/lib/types/client/task-refs.d.ts +0 -31
  126. package/packages/client-agent-team/lib/types/client/task-refs.d.ts.map +0 -1
  127. package/packages/client-agent-team/lib/types/client/task-refs.js +0 -99
@@ -1,10 +1,14 @@
1
1
  /**
2
- * Producer-attributed message sources for Agent Team context management.
2
+ * Plugin-attributed message sources for Agent Team context management.
3
3
  *
4
- * Both sources ride ordinary `UserMessage`s under this plugin's own
5
- * producer kind with the `snapshot` context form — Session format V4 admits
6
- * exactly that shape and refuses the retired `{ kind: 'plugin', plugin: … }`
7
- * wrapper at write time. See `docs/dsh-release-compatibility.md`
4
+ * Both sources ride ordinary `UserMessage`s under the shipped `plugin` kind
5
+ * with the `snapshot` context form — the same shape the Harness's own
6
+ * system-prompt producer writes. This module declares no `MessageSourceMap`
7
+ * member of its own, and carries no bespoke source members: Session format
8
+ * migration validates a `plugin` source against a closed member list
9
+ * (`kind`, `plugin`, `form`, `sections`, `summary`), and refuses every logged
10
+ * Session that carries anything else. A plugin-declared kind is type-legal yet
11
+ * refused the same way. See `docs/dsh-release-compatibility.md`
8
12
  * § "Session message sources".
9
13
  *
10
14
  * Everything the Host needs to read back therefore rides the admitted payload
@@ -15,82 +19,18 @@
15
19
  * snapshot-aware surface.
16
20
  *
17
21
  * The validators below are the single place that recognizes these messages, so
18
- * callers never match on localized body text.
22
+ * callers never match on localized body text. Writing them belongs to the
23
+ * context-continuity engine's codec, which Team constructs with its own plugin
24
+ * identity and prose (`context-continuity-host.ts`): one writer, so the
25
+ * envelope Team reads back can never drift from the one it wrote.
19
26
  * @module @wowyuarm/dsh-agent-team/context-source
20
27
  */
21
- import { createUserMessage } from '@deepseek-ai/dsh-llm';
22
- /** The producer kind attributing every Agent Team message source. */
28
+ /** Plugin identity attributing every Agent Team message source. */
23
29
  export const AGENT_TEAM_PLUGIN_ID = '@wowyuarm/dsh-agent-team';
24
- /**
25
- * The kind the Harness's V4 read-time conversion renames this plugin's
26
- * released V3 `plugin` sources into: `plugin:` + the producer id, with the
27
- * `plugin` key dropped and every payload field (`form`, `sections`,
28
- * `summary`) preserved. History read through the new line carries exactly
29
- * this shape, so the read side recognizes both identities below.
30
- */
31
- export const AGENT_TEAM_V3_RENAMED_KIND = `plugin:${AGENT_TEAM_PLUGIN_ID}`;
32
- /** The kind the read-time conversion renames one producer's released V3 sources into. */
33
- export const v3RenamedSourceKind = (producer) => `plugin:${producer}`;
34
- /**
35
- * Whether one message source carries this plugin's own attribution. Both
36
- * identities are matched by exact kind equality — never by a `plugin:`
37
- * prefix test, which would claim third-party producers' rows as Team facts.
38
- */
39
- export function isAgentTeamSource(source) {
40
- return source.kind === AGENT_TEAM_PLUGIN_ID || source.kind === AGENT_TEAM_V3_RENAMED_KIND;
41
- }
42
- /**
43
- * Whether one source kind carries this plugin's attribution, for call sites
44
- * whose sources arrive untyped. Exact identities only, never a prefix test.
45
- */
46
- export function isAgentTeamSourceKind(kind) {
47
- return kind === AGENT_TEAM_PLUGIN_ID || kind === AGENT_TEAM_V3_RENAMED_KIND;
48
- }
49
30
  /** Handoff snapshot section name carrying the model-authored prose. */
50
31
  export const HANDOFF_SECTION_NAME = 'HANDOFF';
51
32
  /** Stable section name marking a checkpoint continuation and carrying its ref. */
52
33
  export const CHECKPOINT_SECTION_NAME = 'Checkpoint';
53
- /** Fixed text of the quiet checkpoint continuation delivered on the next turn. */
54
- export const CHECKPOINT_CONTINUATION_TEXT = 'A context checkpoint was recorded at the end of the previous turn. Continue the work you were doing.';
55
- /**
56
- * Build the first model-facing context of one rollover generation. The Host
57
- * owns only the verifiable envelope; the prose is the Member's own handoff.
58
- *
59
- * The envelope sections are additive structure, not the only carrier of these
60
- * facts: {@link handoffBody} already states them in the model-facing text, so a
61
- * surface that renders only the body loses nothing.
62
- */
63
- export function createHandoffMessage(input) {
64
- return createUserMessage({
65
- content: [{ type: 'text', text: handoffBody(input) }],
66
- source: {
67
- kind: AGENT_TEAM_PLUGIN_ID,
68
- form: 'snapshot',
69
- sections: handoffSections(input),
70
- },
71
- });
72
- }
73
- /**
74
- * Build the quiet follow-up that continues work after an explicit checkpoint
75
- * concluded its turn. Delivery is scheduled only after the checkpoint's
76
- * successful tool result is durable, so a result-render failure can never
77
- * leave a ghost continuation behind.
78
- *
79
- * This is a `snapshot` rather than a `notice` for one reason: the projection
80
- * must read the checkpoint ref back out of the durable log to record delivery,
81
- * and `sections` is the only admitted payload slot that carries structure. A
82
- * notice would have forced the ref into its human-readable one-line summary.
83
- */
84
- export function createCheckpointContinuationMessage(checkpointRef) {
85
- return createUserMessage({
86
- content: [{ type: 'text', text: CHECKPOINT_CONTINUATION_TEXT }],
87
- source: {
88
- kind: AGENT_TEAM_PLUGIN_ID,
89
- form: 'snapshot',
90
- sections: [{ name: CHECKPOINT_SECTION_NAME, text: checkpointRef }],
91
- },
92
- });
93
- }
94
34
  /**
95
35
  * Read one message's snapshot sections when it is this plugin's own snapshot.
96
36
  * @param message - candidate user message.
@@ -98,7 +38,7 @@ export function createCheckpointContinuationMessage(checkpointRef) {
98
38
  */
99
39
  function ownSections(message) {
100
40
  const source = message.source;
101
- if (!isAgentTeamSource(source))
41
+ if (source.kind !== 'plugin' || source.plugin !== AGENT_TEAM_PLUGIN_ID)
102
42
  return undefined;
103
43
  if (source.form !== 'snapshot')
104
44
  return undefined;
@@ -192,40 +132,11 @@ export function isAgentTeamContextSource(message) {
192
132
  return isHandoffMessage(message) || isCheckpointContinuationMessage(message);
193
133
  }
194
134
  /** Envelope section names; stable, because they are read back from the log.
195
- * One shared vocabulary for writer and reader — no drift between the shape
196
- * published at rollover and the shape the projection folds back. */
135
+ * Exported for the legacy-artifact remediation, which rewrites pre-0.1.10
136
+ * envelopes into exactly these names — one shared vocabulary, no drift. */
197
137
  export const HANDOFF_PREVIOUS_SESSION = 'Previous session';
198
138
  export const HANDOFF_NEW_SESSION = 'New session';
199
139
  export const HANDOFF_TRIGGER = 'Trigger';
200
140
  export const HANDOFF_EVENT_SEQ = 'Handoff event seq';
201
141
  export const HANDOFF_CHECKPOINT = 'Continued from checkpoint';
202
142
  export const HANDOFF_RELATED_FILES = 'Related files';
203
- /** The envelope contributions of one handoff, prose first. */
204
- function handoffSections(input) {
205
- return [
206
- { name: HANDOFF_SECTION_NAME, text: input.handoff },
207
- { name: HANDOFF_PREVIOUS_SESSION, text: input.previousSessionId },
208
- { name: HANDOFF_NEW_SESSION, text: input.newSessionId },
209
- { name: HANDOFF_TRIGGER, text: input.trigger },
210
- { name: HANDOFF_EVENT_SEQ, text: String(input.handoffEventSeq) },
211
- ...(input.checkpointRef === undefined ? [] : [{ name: HANDOFF_CHECKPOINT, text: input.checkpointRef }]),
212
- ...(input.relatedFiles === undefined || input.relatedFiles.length === 0
213
- ? []
214
- : [{ name: HANDOFF_RELATED_FILES, text: JSON.stringify(input.relatedFiles.map(file => file.path)) }]),
215
- ];
216
- }
217
- function handoffBody(input) {
218
- const lines = [
219
- 'Context handoff: you are continuing as the same Team Member in a fresh private context.',
220
- `Previous session: ${input.previousSessionId}`,
221
- `New session: ${input.newSessionId}`,
222
- `Trigger: ${input.trigger}`,
223
- ...(input.checkpointRef === undefined ? [] : [`Continued from checkpoint: ${input.checkpointRef}`]),
224
- ...(input.relatedFiles === undefined || input.relatedFiles.length === 0 ? [] : [`Related files: ${input.relatedFiles.map(file => file.path).join(', ')}`]),
225
- '',
226
- 'Your handoff from the previous context follows. Verify external state before relying on it; a context change never rolls back files, processes, Team facts, or remote side effects.',
227
- '',
228
- input.handoff,
229
- ];
230
- return lines.join('\n');
231
- }
@@ -0,0 +1,61 @@
1
+ import { mkdir, readFile, readdir, rm, writeFile } from 'node:fs/promises';
2
+ import { join } from 'node:path';
3
+ import { randomUUID } from 'node:crypto';
4
+ import { dshHomePath } from '@deepseek-ai/dsh-home-paths';
5
+ import { ATTACHMENT_MAX_BYTES, sanitizeFileName, sanitizeMediaType } from "./attachments.js";
6
+ /**
7
+ * Persistent human avatar store. Layout mirrors the composer attachment
8
+ * cache (`<avatarRef>/` payload plus `meta.json`) but lives under its own
9
+ * root with no TTL sweep: avatar bytes must survive longer than the 72h
10
+ * referenced / 24h orphan windows that bound message attachments.
11
+ */
12
+ export function humanAvatarsRoot() {
13
+ return dshHomePath('agent-team', 'human', 'v1');
14
+ }
15
+ function avatarDir(root, avatarRef) {
16
+ return join(root, avatarRef);
17
+ }
18
+ /** Accept only image payloads; the settings page sends `image/*` exclusively. */
19
+ export function assertAvatarMediaType(mediaType) {
20
+ const sanitized = sanitizeMediaType(mediaType);
21
+ if (!sanitized.startsWith('image/'))
22
+ throw new Error(`human avatar must be an image (got '${mediaType}')`);
23
+ return sanitized;
24
+ }
25
+ /** Write one avatar as an immutable entry; the caller stores the ref in settings. */
26
+ export async function writeHumanAvatar(root, rawName, mediaType, bytes) {
27
+ if (bytes.byteLength === 0)
28
+ throw new Error('human avatar must not be empty');
29
+ if (bytes.byteLength > ATTACHMENT_MAX_BYTES)
30
+ throw new Error(`human avatar exceeds the ${ATTACHMENT_MAX_BYTES} byte limit`);
31
+ const storedMediaType = assertAvatarMediaType(mediaType);
32
+ const name = sanitizeFileName(rawName);
33
+ const avatarRef = randomUUID();
34
+ const dir = avatarDir(root, avatarRef);
35
+ await mkdir(dir, { recursive: true });
36
+ await writeFile(join(dir, name), bytes);
37
+ await writeFile(join(dir, 'meta.json'), JSON.stringify({ name, mediaType: storedMediaType, uploadedAt: new Date().toISOString() }), 'utf8');
38
+ return { avatarRef, path: join(dir, name), name, byteSize: bytes.byteLength, mediaType: storedMediaType };
39
+ }
40
+ /** Read one avatar back; `undefined` when removed or never written. */
41
+ export async function readHumanAvatar(root, avatarRef) {
42
+ const dir = avatarDir(root, avatarRef);
43
+ let metaRaw;
44
+ try {
45
+ metaRaw = await readFile(join(dir, 'meta.json'));
46
+ }
47
+ catch {
48
+ return undefined;
49
+ }
50
+ const meta = JSON.parse(metaRaw.toString('utf8'));
51
+ const entries = await readdir(dir);
52
+ const payload = entries.filter(entry => entry !== 'meta.json')[0];
53
+ if (payload === undefined)
54
+ return undefined;
55
+ const bytes = await readFile(join(dir, payload));
56
+ return { name: meta.name, mediaType: meta.mediaType, byteSize: bytes.byteLength, uploadedAt: meta.uploadedAt, bytes };
57
+ }
58
+ /** Remove one avatar entry; missing entries already satisfy the removal. */
59
+ export async function removeHumanAvatar(root, avatarRef) {
60
+ await rm(avatarDir(root, avatarRef), { recursive: true, force: true });
61
+ }
@@ -0,0 +1,46 @@
1
+ import z from '@deepseek-ai/schemastery';
2
+ /**
3
+ * Human identity profile: the one configurable display name plus the avatar
4
+ * reference. `member:human` stays the durable identity everywhere; only the
5
+ * handle shown in team_view, @ matching, and UI follows this profile.
6
+ *
7
+ * 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.
13
+ */
14
+ export const HUMAN_PROFILE_SETTINGS_NAMESPACE = 'agent-team-human';
15
+ /** Fallback display name before any user override is stored. */
16
+ export const HUMAN_PROFILE_DEFAULT_NAME = 'human';
17
+ /** Repository home for the version footnote link. */
18
+ export const HUMAN_PROFILE_REPO_URL = 'https://github.com/wowyuarm/dsh-agent-team';
19
+ /**
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
22
+ * informational only and never gates behavior.
23
+ */
24
+ export const HUMAN_PROFILE_VERSION = '0.1.13';
25
+ /** Schemastery schema resolving the `agent-team-human` namespace value. */
26
+ export const HUMAN_PROFILE_SETTINGS_SCHEMA = z.object({
27
+ name: z.string().default(HUMAN_PROFILE_DEFAULT_NAME),
28
+ // Schemastery fields are optional unless `.required()`: a missing avatarRef
29
+ // simply resolves absent, which the profile reads as "no custom avatar".
30
+ avatarRef: z.string(),
31
+ });
32
+ /** Normalize one candidate display name the way Member handles normalize. */
33
+ export function normalizeHumanName(raw) {
34
+ return raw.normalize('NFKC').trim();
35
+ }
36
+ /**
37
+ * Validate one candidate display name with the same floor as Member handles:
38
+ * non-empty after trim. Uniqueness against live Members is checked by the
39
+ * Host (which owns the ledger), not here, so this stays a pure function.
40
+ */
41
+ export function assertValidHumanName(raw) {
42
+ const name = normalizeHumanName(raw);
43
+ if (name === '')
44
+ throw new Error('human name must not be empty');
45
+ return name;
46
+ }
@@ -0,0 +1,119 @@
1
+ /**
2
+ * Best-effort Host-side check for a newer published bundle release.
3
+ *
4
+ * The settings footnote needs `updateAvailable`/`latestVersion`, but the
5
+ * profile read path must never wait on the network: the checker serves a
6
+ * cached snapshot synchronously and refreshes it in the background. Every
7
+ * failure mode — disabled by the operator, no fetch implementation, timeout,
8
+ * non-OK status, malformed payload, unparsable version — settles as "no
9
+ * update known", so the footnote degrades to version + link exactly as before.
10
+ *
11
+ * No durable state: the cache lives only in memory. A Host restart simply
12
+ * starts unknown again, which is the safe default for an informational tip.
13
+ */
14
+ /** Public npm metadata document for the bundle's `latest` dist-tag. */
15
+ export const HUMAN_UPDATE_CHECK_REGISTRY_URL = 'https://registry.npmjs.org/@wowyuarm%2fdsh-agent-team/latest';
16
+ /** How long one settled check stays authoritative before a re-check. */
17
+ export const HUMAN_UPDATE_CHECK_TTL_MS = 12 * 60 * 60 * 1000;
18
+ /** Upper bound for one registry round trip; the read path never waits on it. */
19
+ export const HUMAN_UPDATE_CHECK_TIMEOUT_MS = 5000;
20
+ /** Setting this env var to `0`/`false`/`off` disables the outbound check. */
21
+ export const HUMAN_UPDATE_CHECK_ENV = 'DSH_AGENT_TEAM_UPDATE_CHECK';
22
+ /** Operator kill-switch; anything but an explicit off value keeps the default on. */
23
+ export function isUpdateCheckEnabled(env = process.env) {
24
+ const raw = env[HUMAN_UPDATE_CHECK_ENV]?.trim().toLowerCase();
25
+ return raw !== '0' && raw !== 'false' && raw !== 'off';
26
+ }
27
+ /**
28
+ * Numeric core of a `major.minor.patch…` version. Pre-release/build suffixes
29
+ * are ignored for ordering; anything else unparsable resolves absent so the
30
+ * caller treats it as "no update known" instead of guessing.
31
+ */
32
+ export function parseVersionCore(value) {
33
+ const core = value.split('+', 1)[0]?.split('-', 1)[0];
34
+ if (core === undefined || core === '')
35
+ return undefined;
36
+ const parts = core.split('.');
37
+ const numbers = [];
38
+ for (const part of parts) {
39
+ if (part === '' || !/^[0-9]+$/.test(part))
40
+ return undefined;
41
+ numbers.push(Number(part));
42
+ }
43
+ return numbers.length === 0 ? undefined : numbers;
44
+ }
45
+ /** True when `latest` orders strictly after `current` on the numeric core. */
46
+ export function isNewerVersion(current, latest) {
47
+ const from = parseVersionCore(current);
48
+ const to = parseVersionCore(latest);
49
+ if (from === undefined || to === undefined)
50
+ return false;
51
+ const width = Math.max(from.length, to.length);
52
+ for (let index = 0; index < width; index += 1) {
53
+ const left = from[index] ?? 0;
54
+ const right = to[index] ?? 0;
55
+ if (right !== left)
56
+ return right > left;
57
+ }
58
+ return false;
59
+ }
60
+ /**
61
+ * One registry round trip resolving the published `latest` version string.
62
+ * Never throws: anything unexpected resolves absent.
63
+ */
64
+ export async function fetchLatestVersion(fetchImpl, url = HUMAN_UPDATE_CHECK_REGISTRY_URL, timeoutMs = HUMAN_UPDATE_CHECK_TIMEOUT_MS) {
65
+ try {
66
+ const response = await fetchImpl(url, { signal: AbortSignal.timeout(timeoutMs) });
67
+ if (!response.ok)
68
+ return undefined;
69
+ const payload = (await response.json());
70
+ return typeof payload?.version === 'string' ? payload.version : undefined;
71
+ }
72
+ catch {
73
+ return undefined;
74
+ }
75
+ }
76
+ /**
77
+ * Synchronous snapshot over a background-refreshed latest-version cache.
78
+ * `snapshot()` never blocks: a stale or empty cache serves the last known
79
+ * value (initially "no update") and kicks off at most one shared refresh.
80
+ * Concurrent and repeated calls while a refresh is in flight share it, and a
81
+ * refresh that settles only stamps the cache — failures simply leave the
82
+ * previous value standing.
83
+ */
84
+ export function createHumanUpdateChecker(options) {
85
+ const { currentVersion, ttlMs = HUMAN_UPDATE_CHECK_TTL_MS, timeoutMs = HUMAN_UPDATE_CHECK_TIMEOUT_MS, now = Date.now, env = process.env, } = options;
86
+ const fetchImpl = options.fetchImpl ?? ((url, init) => globalThis.fetch(url, init));
87
+ let latestVersion;
88
+ let checkedAt = Number.NEGATIVE_INFINITY;
89
+ let inFlight;
90
+ const refresh = () => {
91
+ if (inFlight !== undefined)
92
+ return;
93
+ if (!isUpdateCheckEnabled(env))
94
+ return;
95
+ const settled = fetchLatestVersion(fetchImpl, HUMAN_UPDATE_CHECK_REGISTRY_URL, timeoutMs).then(version => {
96
+ checkedAt = now();
97
+ if (version !== undefined && isNewerVersion(currentVersion, version))
98
+ latestVersion = version;
99
+ });
100
+ const tracked = settled.finally(() => {
101
+ if (inFlight === tracked)
102
+ inFlight = undefined;
103
+ });
104
+ inFlight = tracked;
105
+ // Rejections are already absorbed inside fetchLatestVersion, but the
106
+ // finally chain still needs a settlement handler so a Host without an
107
+ // unhandled-rejection policy never sees one from this floating refresh.
108
+ void tracked.catch(() => { });
109
+ };
110
+ return {
111
+ snapshot: () => {
112
+ if (checkedAt + ttlMs <= now())
113
+ refresh();
114
+ if (latestVersion === undefined)
115
+ return { updateAvailable: false };
116
+ return { updateAvailable: true, latestVersion };
117
+ },
118
+ };
119
+ }