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

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 (67) hide show
  1. package/README.md +1 -1
  2. package/README.zh.md +1 -1
  3. package/cordis.patch.yml +185 -2
  4. package/package.json +45 -49
  5. package/packages/agent-team/lib/context-management.js +2 -3
  6. package/packages/agent-team/lib/context-projection.js +7 -11
  7. package/packages/agent-team/lib/context-source.js +36 -17
  8. package/packages/agent-team/lib/index.js +26 -64
  9. package/packages/agent-team/lib/member-context.js +5 -3
  10. package/packages/agent-team/lib/member-time-context.js +5 -2
  11. package/packages/agent-team/lib/pressure-policy.js +5 -3
  12. package/packages/agent-team/lib/stored-session-reader.js +1 -2
  13. package/packages/agent-team/lib/typert.host.js +289 -211
  14. package/packages/agent-team/lib/typert.remote-client.d.ts +3 -2
  15. package/packages/agent-team/lib/typert.remote-client.d.ts.map +1 -1
  16. package/packages/agent-team/lib/typert.remote-client.js +243 -189
  17. package/packages/agent-team/lib/types/context-management.d.ts.map +1 -1
  18. package/packages/agent-team/lib/types/context-projection.d.ts.map +1 -1
  19. package/packages/agent-team/lib/types/context-source.d.ts +46 -13
  20. package/packages/agent-team/lib/types/context-source.d.ts.map +1 -1
  21. package/packages/agent-team/lib/types/index.d.ts +0 -8
  22. package/packages/agent-team/lib/types/index.d.ts.map +1 -1
  23. package/packages/agent-team/lib/types/member-context.d.ts +14 -0
  24. package/packages/agent-team/lib/types/member-context.d.ts.map +1 -1
  25. package/packages/agent-team/lib/types/member-time-context.d.ts +15 -0
  26. package/packages/agent-team/lib/types/member-time-context.d.ts.map +1 -1
  27. package/packages/agent-team/lib/types/pressure-policy.d.ts.map +1 -1
  28. package/packages/agent-team/lib/types/stored-session-reader.d.ts +1 -2
  29. package/packages/agent-team/lib/types/stored-session-reader.d.ts.map +1 -1
  30. package/packages/agent-team/lib/types/types/entities.d.ts +3 -3
  31. package/packages/client-agent-team/lib/client.js +467 -341
  32. package/packages/client-agent-team/lib/client.js.map +1 -1
  33. package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.js +6 -6
  34. package/packages/client-agent-team/lib/types/client/TeamChannelPage.js +3 -3
  35. package/packages/client-agent-team/lib/types/client/TeamChannelsPanel.js +4 -4
  36. package/packages/client-agent-team/lib/types/client/TeamComposer.js +3 -3
  37. package/packages/client-agent-team/lib/types/client/TeamFooterAction.js +2 -2
  38. package/packages/client-agent-team/lib/types/client/TeamMemberEditor.js +3 -3
  39. package/packages/client-agent-team/lib/types/client/TeamMembersAction.js +2 -2
  40. package/packages/client-agent-team/lib/types/client/TeamRowMenu.d.ts.map +1 -1
  41. package/packages/client-agent-team/lib/types/client/TeamRowMenu.js +2 -2
  42. package/packages/client-agent-team/lib/types/client/TeamSidebarSection.js +2 -2
  43. package/packages/client-agent-team/lib/types/client/TeamThreadPage.js +3 -3
  44. package/packages/client-agent-team/lib/types/client/TeamWorkspaceBrowser.d.ts.map +1 -1
  45. package/packages/client-agent-team/lib/types/client/TeamWorkspaceBrowser.js +5 -8
  46. package/packages/client-agent-team/lib/types/client/TeamWorkspaceSelector.d.ts +37 -0
  47. package/packages/client-agent-team/lib/types/client/TeamWorkspaceSelector.d.ts.map +1 -0
  48. package/packages/client-agent-team/lib/types/client/TeamWorkspaceSelector.js +58 -0
  49. package/packages/client-agent-team/lib/types/client/index.d.ts.map +1 -1
  50. package/packages/client-agent-team/lib/types/client/index.js +37 -18
  51. package/packages/client-agent-team/lib/types/client/locales.d.ts +2 -0
  52. package/packages/client-agent-team/lib/types/client/locales.d.ts.map +1 -1
  53. package/packages/client-agent-team/lib/types/client/locales.js +2 -0
  54. package/packages/client-agent-team/lib/types/client/multi-menu-field.js +2 -2
  55. package/packages/client-agent-team/lib/types/client/sidebar-sections.d.ts +1 -1
  56. package/packages/client-agent-team/lib/types/client/sidebar-sections.d.ts.map +1 -1
  57. package/packages/client-agent-team/lib/types/client/sidebar-sections.js +4 -3
  58. package/packages/agent-team/lib/preset-roster.js +0 -15
  59. package/packages/agent-team/lib/session-remediation.js +0 -493
  60. package/packages/agent-team/lib/types/preset-roster.d.ts +0 -5
  61. package/packages/agent-team/lib/types/preset-roster.d.ts.map +0 -1
  62. package/packages/agent-team/lib/types/session-remediation.d.ts +0 -172
  63. package/packages/agent-team/lib/types/session-remediation.d.ts.map +0 -1
  64. package/packages/agent-team/preset/team-member/agent.cordis.yml +0 -164
  65. package/packages/client-agent-team/lib/types/client/TeamWorkspaceRow.d.ts +0 -11
  66. package/packages/client-agent-team/lib/types/client/TeamWorkspaceRow.d.ts.map +0 -1
  67. package/packages/client-agent-team/lib/types/client/TeamWorkspaceRow.js +0 -6
@@ -2,6 +2,7 @@ export const zh = {
2
2
  team: '团队',
3
3
  backToConversations: '对话',
4
4
  workspaces: '工作区',
5
+ workspaceSelectorWithValue: '工作区,{title}',
5
6
  channels: '频道',
6
7
  agents: 'Agents',
7
8
  members: '成员',
@@ -178,6 +179,7 @@ export const en = {
178
179
  team: 'Team',
179
180
  backToConversations: 'Conversations',
180
181
  workspaces: 'Workspaces',
182
+ workspaceSelectorWithValue: 'Workspace, {title}',
181
183
  channels: 'Channels',
182
184
  agents: 'Agents',
183
185
  members: 'Members',
@@ -4,7 +4,7 @@ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-run
4
4
  // without closing the list so several entries can be picked in one pass; the
5
5
  // capped card scrolls internally through long catalogs.
6
6
  import { useState } from 'react';
7
- import { IconChevronDownOutline14, Menu } from '@deepseek-ai/dsh-client-ui-primitives';
7
+ import { IconChevronDownOutlineRegular, Menu } from '@deepseek-ai/dsh-client-ui-primitives';
8
8
  import createCss from './create.module.css';
9
9
  import css from './sidebar.module.css';
10
10
  /**
@@ -26,5 +26,5 @@ export function MultiMenuField({ label, options, selected, onToggle, disabled =
26
26
  ...(option.disabled === true ? { disabled: true } : {}),
27
27
  ...(option.icon === undefined ? {} : { icon: option.icon }),
28
28
  }));
29
- return (_jsxs("div", { className: createCss.field, children: [_jsx("span", { children: label }), options.length === 0 && emptyText !== undefined ? _jsx("small", { className: css.editHint, children: emptyText }) : (_jsx(Menu, { open: open, portal: true, className: createCss.menuCap, items: items, selectedIds: selected, onSelect: id => { onToggle(id); }, onClose: () => { setOpen(false); }, anchor: _jsxs("button", { type: "button", className: createCss.selectTrigger, "aria-label": label, "aria-haspopup": "listbox", "aria-expanded": open, disabled: disabled, onClick: () => { setOpen(value => !value); }, children: [_jsx("span", { className: createCss.selectValue, children: selected.length === 0 ? triggerEmptyLabel : formatCount(selected.length) }), _jsx("span", { className: `${createCss.chevron} ${open ? createCss.chevronOpen : ''}`, "aria-hidden": true, children: _jsx(IconChevronDownOutline14, {}) })] }) }))] }));
29
+ return (_jsxs("div", { className: createCss.field, children: [_jsx("span", { children: label }), options.length === 0 && emptyText !== undefined ? _jsx("small", { className: css.editHint, children: emptyText }) : (_jsx(Menu, { open: open, portal: true, className: createCss.menuCap, items: items, selectedIds: selected, onSelect: id => { onToggle(id); }, onClose: () => { setOpen(false); }, anchor: _jsxs("button", { type: "button", className: createCss.selectTrigger, "aria-label": label, "aria-haspopup": "menu", "aria-expanded": open, disabled: disabled, onClick: () => { setOpen(value => !value); }, children: [_jsx("span", { className: createCss.selectValue, children: selected.length === 0 ? triggerEmptyLabel : formatCount(selected.length) }), _jsx("span", { className: `${createCss.chevron} ${open ? createCss.chevronOpen : ''}`, "aria-hidden": true, children: _jsx(IconChevronDownOutlineRegular, {}) })] }) }))] }));
30
30
  }
@@ -1,6 +1,6 @@
1
1
  import type { WorkspaceId } from '@deepseek-ai/dsh-api-workspace-controller/client';
2
2
  /** Which sidebar section a collapse state belongs to. */
3
- export type TeamSidebarSectionKind = 'workspaces' | 'channels' | 'agents';
3
+ export type TeamSidebarSectionKind = 'channels' | 'agents';
4
4
  /**
5
5
  * Effective expanded state for one sidebar section: `false` only when this
6
6
  * browser explicitly collapsed it. Booleans are primitives, so the snapshot
@@ -1 +1 @@
1
- {"version":3,"file":"sidebar-sections.d.ts","sourceRoot":"","sources":["../../../src/client/sidebar-sections.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kDAAkD,CAAA;AAEnF,yDAAyD;AACzD,MAAM,MAAM,sBAAsB,GAAG,YAAY,GAAG,UAAU,GAAG,QAAQ,CAAA;AA8DzE;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,WAAW,EAAE,WAAW,GAAG,SAAS,EAAE,IAAI,EAAE,sBAAsB,GAAG,OAAO,CASjH;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,WAAW,EAAE,WAAW,GAAG,SAAS,EAAE,IAAI,EAAE,sBAAsB,EAAE,IAAI,EAAE,OAAO,GAAG,IAAI,CAQ7H"}
1
+ {"version":3,"file":"sidebar-sections.d.ts","sourceRoot":"","sources":["../../../src/client/sidebar-sections.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kDAAkD,CAAA;AAEnF,yDAAyD;AACzD,MAAM,MAAM,sBAAsB,GAAG,UAAU,GAAG,QAAQ,CAAA;AA+D1D;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,WAAW,EAAE,WAAW,GAAG,SAAS,EAAE,IAAI,EAAE,sBAAsB,GAAG,OAAO,CASjH;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,WAAW,EAAE,WAAW,GAAG,SAAS,EAAE,IAAI,EAAE,sBAAsB,EAAE,IAAI,EAAE,OAAO,GAAG,IAAI,CAQ7H"}
@@ -10,9 +10,10 @@
10
10
  import { useCallback, useSyncExternalStore } from 'react';
11
11
  const STORAGE_KEY = 'dsh.agent-team.sidebar-sections';
12
12
  function sectionKey(workspaceId, kind) {
13
- // The workspaces section is workspace-independent; the two panels are
14
- // per-workspace because the Team shell remounts them on workspace switch.
15
- return workspaceId === undefined ? (kind === 'workspaces' ? kind : undefined) : `${workspaceId}|${kind}`;
13
+ // Both panels are per-workspace: the Team shell remounts them on workspace
14
+ // switch, and the Workspace selector above them is a field rather than a
15
+ // collapsible section, so it keeps no preference of its own.
16
+ return workspaceId === undefined ? undefined : `${workspaceId}|${kind}`;
16
17
  }
17
18
  const MEMORY_ONLY = Symbol('sidebar-sections.memory');
18
19
  /** Read-through parse cache over the raw persisted payload; see `sidebar-order.ts`. */
@@ -1,15 +0,0 @@
1
- import { dirname, resolve } from 'node:path';
2
- import { fileURLToPath } from 'node:url';
3
- import AgentPresets from '@deepseek-ai/dsh-agent-presets';
4
- export const inject = ['loader'];
5
- /** Bundle-private Agent preset roster. The composition must isolate `agentPresets` around this provider and AgentTeam. */
6
- export function apply(ctx) {
7
- const here = dirname(fileURLToPath(import.meta.url));
8
- const root = resolve(here, '../preset');
9
- ctx.plugin(AgentPresets, {
10
- default: 'team-member',
11
- roots: [{ path: root, trust: 'system' }],
12
- includeShippedRoot: false,
13
- includeUserRoot: false,
14
- });
15
- }
@@ -1,493 +0,0 @@
1
- /**
2
- * Startup remediation of legacy Member Session artifacts.
3
- *
4
- * Released dsh-agent-team 0.1.9 wrote its rollover handoff and checkpoint
5
- * continuation into Member Session logs under bespoke message source kinds.
6
- * dsh 0.1.5's session-format migration chain admits a closed source-kind
7
- * vocabulary and refuses those artifacts fail-closed — the bytes are intact,
8
- * but no reader (activation, session search, lineage timeline) can open them,
9
- * which surfaces as Members going `unavailable`.
10
- *
11
- * This module restores readability without touching a byte of the refused
12
- * artifact: it decodes the stored v0 log physically (frame-walking the
13
- * concatenated zstd container), rewrites only this plugin's legacy source
14
- * kinds into the admitted `plugin` shape the current writer already uses,
15
- * re-validates the whole stream through the format catalog's own migration
16
- * chain in memory, and publishes the migrated log as a sibling current-format
17
- * artifact (`session.v3.jsonl.zstd`) — the storage layer's designated slot for
18
- * a published current generation, atomically created and never overwritten.
19
- * The v0 original remains the rollback path: delete the sibling.
20
- *
21
- * Scope is deliberately narrow: artifacts refused for any other reason
22
- * (structural turn defects, foreign legacy fields) are left untouched with a
23
- * diagnostic log — rewriting history a plugin did not author is out of
24
- * bounds. Member availability for those rests on the activation recovery
25
- * path's bounded fail-open, not on this migration.
26
- *
27
- * The walk runs once per plugin start, before any Member activation (no
28
- * write lease exists yet) and records completion per Member in a separate
29
- * durable domain — never in the `agent_team` ledger domain, whose version is
30
- * the operation schema's compatibility contract, not a cache stamp.
31
- * @module @wowyuarm/dsh-agent-team/session-remediation
32
- */
33
- import { randomUUID } from 'node:crypto';
34
- import { link, readFile, rm, stat, writeFile } from 'node:fs/promises';
35
- import { basename, dirname, join } from 'node:path';
36
- import { constants as zstdConstants, zstdCompressSync, zstdDecompressSync } from 'node:zlib';
37
- import { z } from 'zod';
38
- import { defineDomain, domainTable } from '@deepseek-ai/dsh-storage-domain';
39
- import { sessionFormatCatalog } from '@deepseek-ai/dsh-session-format-catalog';
40
- import { SessionFormatUnsupportedError } from '@deepseek-ai/dsh-session-persistence';
41
- import { AGENT_TEAM_PLUGIN_ID, CHECKPOINT_SECTION_NAME, HANDOFF_CHECKPOINT, HANDOFF_EVENT_SEQ, HANDOFF_NEW_SESSION, HANDOFF_PREVIOUS_SESSION, HANDOFF_RELATED_FILES, HANDOFF_TRIGGER, } from "./context-source.js";
42
- /** The rollover-handoff kind released 0.1.9 wrote; it is also handoff evidence in a log. */
43
- export const LEGACY_HANDOFF_SOURCE_KIND = 'agent-team-context-handoff';
44
- /** The two bespoke source kinds released 0.1.9 wrote; the migration's whole scope. */
45
- const LEGACY_SOURCE_KINDS = new Set([LEGACY_HANDOFF_SOURCE_KIND, 'agent-team-context-continuation']);
46
- /** The handoff kind alone, for handoff-presence questions. */
47
- const LEGACY_HANDOFF_SOURCE_KINDS = new Set([LEGACY_HANDOFF_SOURCE_KIND]);
48
- /** Sibling filename the storage layer reads in preference when present. */
49
- const CURRENT_GENERATION_FILENAME = 'session.v3.jsonl.zstd';
50
- /** Lineage walks stop at this depth; a chain this long is a defect, not history. */
51
- const MAX_LINEAGE_DEPTH = 64;
52
- /**
53
- * Durable completion cache. A separate domain on purpose: the `agent_team`
54
- * domain's version gates the ledger schema, and stamping migration bookkeeping
55
- * there would turn a cache refresh into a ledger-format break. Records are
56
- * disposable derived state — losing them only re-runs the (idempotent) walk.
57
- */
58
- const sessionRemediationDomainSpec = defineDomain({
59
- name: 'agent_team_remediation',
60
- version: 1,
61
- tables: {
62
- lineages: domainTable(z.object({
63
- memberId: z.string().min(1),
64
- formatVersion: z.number().int().nonnegative(),
65
- currentSessionId: z.string().min(1),
66
- verifiedAt: z.number().int().nonnegative(),
67
- }).strict()),
68
- },
69
- });
70
- /**
71
- * Rewrite one legacy source onto the admitted shape the current writer uses.
72
- * The original `sections` array is preserved verbatim (it carries the
73
- * model-authored handoff prose); envelope fields ride additional named
74
- * sections exactly as `handoffSections` composes them, added only when the
75
- * field is present and no section of that name exists.
76
- */
77
- function admitLegacySource(source) {
78
- if (source.kind === 'agent-team-context-handoff') {
79
- const sections = Array.isArray(source.sections)
80
- ? source.sections.filter((section) => section !== null && typeof section === 'object')
81
- : [];
82
- const envelope = [
83
- [HANDOFF_PREVIOUS_SESSION, source.previousSessionId],
84
- [HANDOFF_NEW_SESSION, source.newSessionId],
85
- [HANDOFF_TRIGGER, source.trigger],
86
- [HANDOFF_EVENT_SEQ, source.handoffEventSeq],
87
- [HANDOFF_CHECKPOINT, source.checkpointRef],
88
- [HANDOFF_RELATED_FILES, Array.isArray(source.relatedFiles)
89
- ? source.relatedFiles.filter((file) => typeof file === 'string').join(', ')
90
- : undefined],
91
- ];
92
- for (const [name, value] of envelope) {
93
- if (value === undefined || value === null || value === '')
94
- continue;
95
- if (sections.some(section => section.name === name))
96
- continue;
97
- sections.push({ name, text: String(value) });
98
- }
99
- return { kind: 'plugin', plugin: AGENT_TEAM_PLUGIN_ID, form: 'snapshot', sections };
100
- }
101
- // A continuation carried its checkpoint ref at the top level; the admitted
102
- // shape reads it back from a single-name section, same as the writer.
103
- return {
104
- kind: 'plugin',
105
- plugin: AGENT_TEAM_PLUGIN_ID,
106
- form: 'snapshot',
107
- sections: [{ name: CHECKPOINT_SECTION_NAME, text: String(source.checkpointRef ?? '') }],
108
- };
109
- }
110
- /**
111
- * Deep-rewrite every legacy Team source in one physical row tree. The kinds
112
- * are globally unique to this plugin, so a structural match anywhere in the
113
- * row (message sources ride several payload positions) is exactly the set to
114
- * rewrite; everything else passes through untouched.
115
- */
116
- export function admitLegacyRow(value) {
117
- if (value === null || typeof value !== 'object')
118
- return value;
119
- if (Array.isArray(value))
120
- return value.map(admitLegacyRow);
121
- const out = {};
122
- for (const [key, item] of Object.entries(value))
123
- out[key] = admitLegacyRow(item);
124
- if (typeof out.kind === 'string' && LEGACY_SOURCE_KINDS.has(out.kind))
125
- return admitLegacySource(out);
126
- return out;
127
- }
128
- /** Whether one value tree still contains a legacy Team source kind. */
129
- export function containsLegacySource(value) {
130
- return containsLegacySourceKind(value, LEGACY_SOURCE_KINDS);
131
- }
132
- /**
133
- * Whether one value tree carries the retired rollover-handoff kind. A
134
- * continuation is deliberately excluded: it never carried a handoff, so it is
135
- * not evidence that one already arrived.
136
- */
137
- export function carriesLegacyHandoffSource(value) {
138
- return containsLegacySourceKind(value, LEGACY_HANDOFF_SOURCE_KINDS);
139
- }
140
- /**
141
- * Whether a generation's log already holds a rollover handoff, in either
142
- * shape. The admitted shape surfaces as a projection boundary; a generation
143
- * rescued from the retired kinds carries the very same handoff as a source the
144
- * projection does not classify, so recovery that keys on the boundary alone
145
- * would deliver that handoff a second time.
146
- */
147
- export function handoffAlreadyInLog(boundaries, ownEvents) {
148
- if (boundaries.some(boundary => boundary.source === 'handoff'))
149
- return true;
150
- return ownEvents.some(carriesLegacyHandoffSource);
151
- }
152
- function containsLegacySourceKind(value, kinds) {
153
- if (value === null || typeof value !== 'object')
154
- return false;
155
- if (Array.isArray(value))
156
- return value.some(item => containsLegacySourceKind(item, kinds));
157
- const kind = value.kind;
158
- if (typeof kind === 'string' && kinds.has(kind))
159
- return true;
160
- return Object.values(value).some(item => containsLegacySourceKind(item, kinds));
161
- }
162
- const ZSTD_MAGIC = 0xfd2fb528;
163
- const SKIPPABLE_FROM = 0x184d2a50;
164
- const SKIPPABLE_TO = 0x184d2a5f;
165
- /**
166
- * Byte ranges of every complete zstd frame in a concatenated container, plus
167
- * the offset of a torn final frame. Node's zstd bindings decode only the
168
- * first frame of their input, so multi-frame containers must be walked
169
- * structurally; scanning for the magic alone is unsound because the magic
170
- * sequence can occur inside compressed payloads.
171
- */
172
- export function scanZstdFrames(buffer) {
173
- const frames = [];
174
- let at = 0;
175
- let tornStart;
176
- while (at < buffer.length) {
177
- if (at + 4 > buffer.length) {
178
- tornStart = at;
179
- break;
180
- }
181
- const magic = buffer.readUInt32LE(at);
182
- if (magic >= SKIPPABLE_FROM && magic <= SKIPPABLE_TO) {
183
- if (at + 8 > buffer.length) {
184
- tornStart = at;
185
- break;
186
- }
187
- const size = buffer.readUInt32LE(at + 4);
188
- if (at + 8 + size > buffer.length) {
189
- tornStart = at;
190
- break;
191
- }
192
- frames.push({ start: at, end: at + 8 + size, skippable: true });
193
- at += 8 + size;
194
- continue;
195
- }
196
- if (magic !== ZSTD_MAGIC)
197
- throw new Error(`not a zstd frame at byte ${at}`);
198
- if (at + 5 > buffer.length) {
199
- tornStart = at;
200
- break;
201
- }
202
- const descriptor = buffer.readUInt8(at + 4);
203
- const contentSizeFlag = descriptor >> 6;
204
- const singleSegment = (descriptor >> 5) & 1;
205
- const hasChecksum = (descriptor >> 2) & 1;
206
- const dictionaryFlag = descriptor & 3;
207
- let cursor = at + 5;
208
- if (singleSegment === 0)
209
- cursor += 1;
210
- const dictionaryLengths = [0, 1, 2, 4];
211
- const contentSizeLengths = [0, 2, 4, 8];
212
- cursor += dictionaryLengths[dictionaryFlag] ?? 0;
213
- cursor += contentSizeFlag === 0 ? (singleSegment === 1 ? 1 : 0) : (contentSizeLengths[contentSizeFlag] ?? 0);
214
- for (;;) {
215
- if (cursor + 3 > buffer.length)
216
- throw new Error(`torn zstd block header at byte ${cursor}`);
217
- const header = buffer.readUInt8(cursor) | (buffer.readUInt8(cursor + 1) << 8) | (buffer.readUInt8(cursor + 2) << 16);
218
- const last = header & 1;
219
- const blockType = (header >> 1) & 3;
220
- const size = header >> 3;
221
- if (blockType === 3)
222
- throw new Error(`reserved zstd block type at byte ${cursor}`);
223
- cursor += 3 + (blockType === 1 ? 1 : size);
224
- if (last === 1)
225
- break;
226
- }
227
- if (hasChecksum === 1)
228
- cursor += 4;
229
- if (cursor > buffer.length)
230
- throw new Error(`torn zstd frame at byte ${at}`);
231
- frames.push({ start: at, end: cursor, skippable: false });
232
- at = cursor;
233
- }
234
- return { frames, tornStart };
235
- }
236
- /** Decode one stored zstd container to its physical JSONL rows. */
237
- export function readPhysicalRows(buffer) {
238
- const { frames } = scanZstdFrames(buffer);
239
- const chunks = frames.map(frame => frame.skippable ? Buffer.alloc(0) : zstdDecompressSync(buffer.subarray(frame.start, frame.end)));
240
- const lines = Buffer.concat(chunks).toString('utf8').split('\n').filter(line => line.length > 0);
241
- return lines.map(line => JSON.parse(line));
242
- }
243
- /** Encode the migrated artifact as one checksummed zstd frame per JSONL row. */
244
- function encodeCurrentArtifact(artifact) {
245
- const checksum = { params: { [zstdConstants.ZSTD_c_checksumFlag]: 1 } };
246
- const lines = [
247
- JSON.stringify(sessionFormatCatalog.encodeCurrentHeader(artifact.header, artifact.inheritedEventCount)),
248
- ...artifact.events.map(event => JSON.stringify(sessionFormatCatalog.encodeCurrentEvent(event))),
249
- ];
250
- return Buffer.concat(lines.map(line => zstdCompressSync(Buffer.from(line + '\n'), checksum)));
251
- }
252
- /** Run the format catalog's own migration chain over transformed rows; throws on any residual refusal. */
253
- function migrateForProof(rows) {
254
- const [header, ...events] = rows;
255
- const handle = sessionFormatCatalog.createRestore(header, { recovery: 'recoverable', validation: 'current' });
256
- for (const row of events)
257
- handle.decodeRow(row);
258
- return handle.finish();
259
- }
260
- /** The JSONL artifact path a format refusal reports, when it reports one. */
261
- function artifactPath(error) {
262
- return error.location?.kind === 'jsonl' ? error.location.path : undefined;
263
- }
264
- /** The recorded parent Session of one physical header row. */
265
- function physicalParentSession(rows) {
266
- const parent = rows[0]?.parentSession;
267
- return typeof parent === 'string' ? parent : undefined;
268
- }
269
- export class SessionRemediation {
270
- ctx;
271
- persistence;
272
- table;
273
- constructor(ctx, persistence, domain) {
274
- this.ctx = ctx;
275
- this.persistence = persistence;
276
- this.table = domain?.table('lineages');
277
- }
278
- /** Open the completion-cache domain; a failure only costs re-walking. */
279
- static async open(ctx) {
280
- try {
281
- return await ctx.storageDomain.open(sessionRemediationDomainSpec);
282
- }
283
- catch (error) {
284
- ctx.logger.warn(`agent-team: session remediation cache unavailable (walks will re-run): ${error instanceof Error ? error.message : String(error)}`);
285
- return undefined;
286
- }
287
- }
288
- /**
289
- * Remediate every enabled Member's lineage once, before activation. A
290
- * Member is skipped when its cache record still matches the current format
291
- * version and Session binding — a rollover or a dsh format change is what
292
- * re-opens the walk.
293
- */
294
- async remediateEnabledMembers(members) {
295
- let repaired = 0;
296
- let untouched = 0;
297
- let walked = 0;
298
- for (const member of members) {
299
- if (member.state !== 'enabled')
300
- continue;
301
- const outcome = await this.remediateMember(member);
302
- if (outcome.cacheHit)
303
- continue;
304
- walked += 1;
305
- repaired += outcome.repaired;
306
- untouched += outcome.untouched;
307
- }
308
- if (walked > 0) {
309
- this.ctx.logger.info(`agent-team: legacy Session remediation walked ${walked} member lineage(s): ${repaired} artifact(s) repaired, ${untouched} left untouched`);
310
- }
311
- }
312
- /**
313
- * Remediate one Member's lineage; the bounded in-place heal a restart
314
- * performs after an activation refused on a session. `completed` with zero
315
- * repairs is the deterministic nothing-to-do answer (a finished walk found
316
- * nothing provably this plugin's, or the cache already covered the Member);
317
- * `completed: false` means the walk itself failed and a later attempt
318
- * should retry. Never throws.
319
- */
320
- async remediateMember(member) {
321
- if (member.state !== 'enabled' || this.cacheStillValid(member))
322
- return { repaired: 0, untouched: 0, completed: true, cacheHit: true };
323
- try {
324
- const summary = await this.remediateLineage(member);
325
- // A walk that failed a repair is not evidence of a clean lineage: it is
326
- // left uncached so the next start retries exactly this Member, and it
327
- // reports `completed: false` so no caller reads it as the deterministic
328
- // nothing-to-do answer.
329
- if (summary.failed === 0)
330
- await this.recordCompletion(member);
331
- return { repaired: summary.repaired, untouched: summary.untouched, completed: summary.failed === 0, cacheHit: false };
332
- }
333
- catch (error) {
334
- this.ctx.logger.warn(`agent-team: legacy Session remediation for member '${member.handle}' did not complete: ${error instanceof Error ? error.message : String(error)}`);
335
- return { repaired: 0, untouched: 0, completed: false, cacheHit: false };
336
- }
337
- }
338
- /** Whether the cached walk still covers this Member under the current format. */
339
- cacheStillValid(member) {
340
- const record = this.table?.get(member.memberId);
341
- return record !== undefined
342
- && record.formatVersion === sessionFormatCatalog.currentVersion
343
- && record.currentSessionId === member.sessionId;
344
- }
345
- async recordCompletion(member) {
346
- if (this.table === undefined)
347
- return;
348
- try {
349
- await this.table.put(member.memberId, {
350
- memberId: member.memberId,
351
- formatVersion: sessionFormatCatalog.currentVersion,
352
- currentSessionId: member.sessionId,
353
- verifiedAt: Date.now(),
354
- });
355
- }
356
- catch (error) {
357
- this.ctx.logger.warn(`agent-team: could not record session remediation completion for member '${member.handle}' (the walk will re-run): ${error instanceof Error ? error.message : String(error)}`);
358
- }
359
- }
360
- /**
361
- * Walk one Member's lineage from its bound Session up through recorded
362
- * parent Sessions, remediating refused artifacts in place. Readable
363
- * artifacts still carrying legacy kinds inside a current-format generation
364
- * (an artifact of the one-off 2026-09-10 manual rescue) are counted and
365
- * logged but not rewritten: they are readable, and rewriting a generation
366
- * the storage layer may still append to is a write-path risk this pass
367
- * does not take.
368
- */
369
- async remediateLineage(member) {
370
- let repaired = 0;
371
- let untouched = 0;
372
- let failed = 0;
373
- const seen = new Set();
374
- let sessionId = member.sessionId;
375
- for (let depth = 0; sessionId !== undefined && !seen.has(sessionId) && depth < MAX_LINEAGE_DEPTH; depth += 1) {
376
- seen.add(sessionId);
377
- const outcome = await this.remediateOne(sessionId, member);
378
- if (outcome.status === 'repaired')
379
- repaired += 1;
380
- else if (outcome.status === 'left-untouched')
381
- untouched += 1;
382
- else if (outcome.status === 'repair-failed')
383
- failed += 1;
384
- sessionId = outcome.parentSession === undefined ? undefined : outcome.parentSession;
385
- }
386
- return { repaired, untouched, failed };
387
- }
388
- /** Remediate one lineage Session: verify readability, and repair when the refusal is this plugin's legacy source shape. */
389
- async remediateOne(sessionId, member) {
390
- try {
391
- const handle = await this.persistence.open(sessionId, 'read');
392
- try {
393
- const { events } = await handle.read();
394
- if (events.some(event => containsLegacySource(event))) {
395
- this.ctx.logger.info(`agent-team: Session '${sessionId}' of member '${member.handle}' reads fine but still carries legacy envelope kinds; its history metadata stays degraded until the next rollover`);
396
- }
397
- return { status: 'already-readable', parentSession: handle.header.parentSession };
398
- }
399
- finally {
400
- await handle.close();
401
- }
402
- }
403
- catch (error) {
404
- if (!(error instanceof SessionFormatUnsupportedError)) {
405
- this.ctx.logger.warn(`agent-team: Session '${sessionId}' of member '${member.handle}' is unreadable for a non-format reason: ${error instanceof Error ? error.message : String(error)}`);
406
- return { status: 'left-untouched', parentSession: undefined, reason: 'unreadable for a non-format reason' };
407
- }
408
- const path = artifactPath(error);
409
- if (path === undefined) {
410
- this.ctx.logger.warn(`agent-team: refused Session '${sessionId}' of member '${member.handle}' did not report a JSONL artifact path: ${error.message}`);
411
- return { status: 'left-untouched', parentSession: undefined, reason: 'no artifact path reported' };
412
- }
413
- return await this.repairArtifact(sessionId, path, member, error);
414
- }
415
- }
416
- /** Transform, prove, and publish the sibling current-format artifact for one refused Session. */
417
- async repairArtifact(sessionId, path, member, refusal) {
418
- let rows;
419
- try {
420
- rows = readPhysicalRows(await readFile(path));
421
- }
422
- catch (readError) {
423
- this.ctx.logger.warn(`agent-team: could not decode the stored artifact of Session '${sessionId}' (${basename(path)}): ${readError instanceof Error ? readError.message : String(readError)}`);
424
- return { status: 'repair-failed', parentSession: undefined, reason: 'artifact undecodable' };
425
- }
426
- const parentSession = physicalParentSession(rows);
427
- if (!rows.some(row => containsLegacySource(row))) {
428
- this.ctx.logger.warn(`agent-team: Session '${sessionId}' of member '${member.handle}' is refused for a reason outside this plugin's writes: ${refusal.message.split('\n')[0]}; leaving it untouched`);
429
- return { status: 'left-untouched', parentSession, reason: 'refusal outside this plugin writes' };
430
- }
431
- const admitted = rows.map(admitLegacyRow);
432
- let artifact;
433
- try {
434
- artifact = migrateForProof(admitted.map(row => structuredClone(row)));
435
- }
436
- catch (proofError) {
437
- this.ctx.logger.warn(`agent-team: Session '${sessionId}' of member '${member.handle}' still refuses after the legacy source rewrite (${proofError instanceof Error ? proofError.message : String(proofError)}); leaving it untouched`);
438
- return { status: 'repair-failed', parentSession, reason: 'residual refusal after rewrite' };
439
- }
440
- try {
441
- const published = await this.publishSibling(path, encodeCurrentArtifact(artifact));
442
- if (published === 'already-present') {
443
- this.ctx.logger.warn(`agent-team: Session '${sessionId}' already has a current-format sibling; leaving both untouched`);
444
- return { status: 'left-untouched', parentSession, reason: 'sibling already present' };
445
- }
446
- }
447
- catch (publishError) {
448
- this.ctx.logger.warn(`agent-team: could not publish the remediated sibling for Session '${sessionId}' (${basename(path)}): ${publishError instanceof Error ? publishError.message : String(publishError)}`);
449
- return { status: 'repair-failed', parentSession, reason: 'publish failed' };
450
- }
451
- // Success is proven by the storage layer itself, not by our own writer.
452
- try {
453
- const handle = await this.persistence.open(sessionId, 'read');
454
- try {
455
- await handle.read();
456
- }
457
- finally {
458
- await handle.close();
459
- }
460
- }
461
- catch (verifyError) {
462
- this.ctx.logger.warn(`agent-team: the remediated sibling for Session '${sessionId}' was published but still refuses: ${verifyError instanceof Error ? verifyError.message : String(verifyError)}`);
463
- return { status: 'repair-failed', parentSession, reason: 'sibling refused after publish' };
464
- }
465
- this.ctx.logger.info(`agent-team: repaired legacy source kinds in Session '${sessionId}' of member '${member.handle}' by publishing a current-format sibling; the original artifact is untouched`);
466
- return { status: 'repaired', parentSession };
467
- }
468
- /**
469
- * Atomically place the sibling file: stage in the same directory, hardlink
470
- * into place (an existing target refuses, never overwrites), remove the
471
- * stage. The link dance matches the storage layer's own exclusive publish.
472
- */
473
- async publishSibling(sourcePath, bytes) {
474
- const directory = dirname(sourcePath);
475
- const target = join(directory, CURRENT_GENERATION_FILENAME);
476
- if (await stat(target).then(() => true, () => false))
477
- return 'already-present';
478
- const staged = join(directory, `.remediation-${randomUUID()}.tmp`);
479
- await writeFile(staged, bytes, { flag: 'wx' });
480
- try {
481
- await link(staged, target);
482
- }
483
- catch (error) {
484
- if (await stat(target).then(() => true, () => false))
485
- return 'already-present';
486
- throw error;
487
- }
488
- finally {
489
- await rm(staged, { force: true });
490
- }
491
- return 'published';
492
- }
493
- }
@@ -1,5 +0,0 @@
1
- import type { Context } from '@deepseek-ai/cordis';
2
- export declare const inject: string[];
3
- /** Bundle-private Agent preset roster. The composition must isolate `agentPresets` around this provider and AgentTeam. */
4
- export declare function apply(ctx: Context): void;
5
- //# sourceMappingURL=preset-roster.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"preset-roster.d.ts","sourceRoot":"","sources":["../../src/preset-roster.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAGlD,eAAO,MAAM,MAAM,UAAa,CAAA;AAEhC,0HAA0H;AAC1H,wBAAgB,KAAK,CAAC,GAAG,EAAE,OAAO,GAAG,IAAI,CASxC"}