@aiwuk/team 0.0.0-stage → 5.0.3

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 (116) hide show
  1. package/CHANGELOG.md +156 -0
  2. package/LICENSE +21 -0
  3. package/README.md +149 -2
  4. package/README.zh-CN.md +150 -0
  5. package/access.js +93 -0
  6. package/activity-summary.js +36 -0
  7. package/allocation.js +34 -0
  8. package/board-dispatch.js +141 -0
  9. package/brand.js +41 -0
  10. package/client/00-core-zh.js +284 -0
  11. package/client/01-i18n-en.js +265 -0
  12. package/client/02-styles.js +336 -0
  13. package/client/02b-stage-styles.js +65 -0
  14. package/client/03-shared.js +82 -0
  15. package/client/03b-collapsible-list.js +34 -0
  16. package/client/03c-config-panel.js +605 -0
  17. package/client/04-tool-cards.js +110 -0
  18. package/client/04a-dispatch-confirm.js +162 -0
  19. package/client/04b-more-cards.js +78 -0
  20. package/client/04c-model-picker.js +68 -0
  21. package/client/04d-confirm-dock.js +40 -0
  22. package/client/05-policy.js +217 -0
  23. package/client/06-usage.js +195 -0
  24. package/client/06b-model-source.js +67 -0
  25. package/client/07-board-parts.js +312 -0
  26. package/client/07a-role-theme.js +25 -0
  27. package/client/08-artifacts-report.js +375 -0
  28. package/client/08a-task-control.js +126 -0
  29. package/client/08b-stage-sprites.js +97 -0
  30. package/client/08c-stage-colors.js +55 -0
  31. package/client/08c-stage-scene.js +238 -0
  32. package/client/08d-stage-adapter.js +65 -0
  33. package/client/08e-stage-activity.js +83 -0
  34. package/client/09-team-board.js +181 -0
  35. package/client/09b-role-view.js +95 -0
  36. package/client/09c-model-tabs.js +20 -0
  37. package/client/10-overview.js +364 -0
  38. package/client/10b-onboarding.js +121 -0
  39. package/client/11-register.js +249 -0
  40. package/client.js +5053 -0
  41. package/config-projection.js +40 -0
  42. package/config.js +411 -0
  43. package/cordis.patch.yml +110 -0
  44. package/defaults.js +222 -0
  45. package/dispatch-confirm.js +293 -0
  46. package/dispatch-routes-store.js +31 -0
  47. package/failover.js +85 -0
  48. package/file-limits.js +119 -0
  49. package/free-worker.mjs +125 -0
  50. package/http-guard.js +14 -0
  51. package/i18n/roles.en.json +23 -0
  52. package/i18n/roles.zh.json +23 -0
  53. package/i18n/server.en.json +659 -0
  54. package/i18n/server.zh.json +659 -0
  55. package/i18n.js +34 -0
  56. package/icon.svg +7 -0
  57. package/index.js +111 -0
  58. package/integrations-config.js +301 -0
  59. package/integrations-messages.js +62 -0
  60. package/integrations.js +17 -0
  61. package/lead-route.js +30 -0
  62. package/locale/en.json +6 -0
  63. package/locale/zh.json +6 -0
  64. package/long-context.js +30 -0
  65. package/managed-config-route.js +49 -0
  66. package/managed-ref.js +38 -0
  67. package/managed-resolve.js +47 -0
  68. package/member-task-control.js +242 -0
  69. package/memory-backends.js +89 -0
  70. package/memory-embed.js +70 -0
  71. package/memory-hub.js +150 -0
  72. package/memory-openviking.js +101 -0
  73. package/memory-redact.js +34 -0
  74. package/memory-store.js +219 -0
  75. package/memory-sync-queue.js +28 -0
  76. package/model-policy.js +85 -0
  77. package/model-prefs.js +104 -0
  78. package/observability-api.js +76 -0
  79. package/observability-projection.js +141 -0
  80. package/onboarding.js +47 -0
  81. package/opencode.js +74 -0
  82. package/package.json +153 -4
  83. package/plan-gate.js +92 -0
  84. package/preset-guard.js +47 -0
  85. package/request-evidence.js +41 -0
  86. package/roles.js +69 -0
  87. package/roster-defaults.js +70 -0
  88. package/route-health.js +39 -0
  89. package/runtime-observability.js +78 -0
  90. package/runtime-view.js +21 -0
  91. package/scripts/memory-cli.mjs +138 -0
  92. package/search-engines.js +119 -0
  93. package/search.js +164 -0
  94. package/session-policy.js +59 -0
  95. package/session-rotation.js +206 -0
  96. package/settings-schema.js +40 -0
  97. package/shared/managed-config.js +84 -0
  98. package/single-model.js +44 -0
  99. package/skills/project-role-theme/SKILL.md +46 -0
  100. package/task-bridge.js +32 -0
  101. package/task-control-api.js +36 -0
  102. package/task-control-audit.js +60 -0
  103. package/team.js +101 -0
  104. package/teammate-context.js +120 -0
  105. package/teammate-wait.js +18 -0
  106. package/template.js +46 -0
  107. package/theme-plugin.js +172 -0
  108. package/theme.js +115 -0
  109. package/token-discipline.js +116 -0
  110. package/token-metrics.js +37 -0
  111. package/tool-names.js +10 -0
  112. package/tool-schema.js +23 -0
  113. package/transcribe.js +104 -0
  114. package/ui.js +113 -0
  115. package/usage.js +92 -0
  116. package/workflow.js +246 -0
package/model-prefs.js ADDED
@@ -0,0 +1,104 @@
1
+ import { m as modelText } from './i18n.js';
2
+ // Learned model preferences: every confirmed dispatch records, per capability, which primary and fallbacks the
3
+ // user chose. Later confirmation cards preselect a habit (a primary chosen at least twice and in most recent
4
+ // choices) unless that route keeps failing, and suggest saving it as the project default once it is settled.
5
+ // Nothing is applied silently: the card still asks, and the learned data can be cleared.
6
+ import { createHash, randomUUID } from 'node:crypto';
7
+ import { mkdir, readFile, rename, unlink, writeFile } from 'node:fs/promises';
8
+ import { join } from 'node:path';
9
+ export const PREF_LIMITS = { keep: 20, window: 10, minCount: 2, settled: 3, unhealthyStrikes: 2 };
10
+ const routeKey = r => (r ? `${r.provider}/${r.model}` : null);
11
+ const parse = key => { const i = key.indexOf('/'); return { provider: key.slice(0, i), model: key.slice(i + 1) }; };
12
+ /** Habit for one capability from its recent choices, or undefined. Pure. */
13
+ export function habitOf(choices, limits = PREF_LIMITS) {
14
+ const recent = (choices ?? []).slice(-limits.window);
15
+ if (!recent.length) return undefined;
16
+ const counts = new Map();
17
+ for (const c of recent) counts.set(c.primary, (counts.get(c.primary) ?? 0) + 1);
18
+ const [primary, count] = [...counts].sort((a, b) => b[1] - a[1] || recent.findLastIndex(c => c.primary === b[0]) - recent.findLastIndex(c => c.primary === a[0]))[0];
19
+ if (count < limits.minCount || count * 2 < recent.length) return undefined;
20
+ const last = recent.findLast(c => c.primary === primary);
21
+ const tail = recent.slice(-limits.settled);
22
+ const settled = tail.length === limits.settled && tail.every(c => c.primary === primary && c.fallbacks.join() === last.fallbacks.join());
23
+ return { primary, fallbacks: last.fallbacks, count, of: recent.length, settled };
24
+ }
25
+ /** Health notes for routes: strikes from the failover layer; a route is unhealthy at PREF_LIMITS.unhealthyStrikes. */
26
+ export function unhealthyRoutes(health = {}, limits = PREF_LIMITS) {
27
+ // A credential failure (wrong or missing key) stays bad until fixed, so one is enough.
28
+ return Object.fromEntries(Object.entries(health).filter(([, h]) => h.cooling || h.credential || (h.strikes ?? 0) >= limits.unhealthyStrikes).map(([k, h]) => [k, h.strikes ?? 1]));
29
+ }
30
+ /**
31
+ * Apply learning to one offered row: preselect the habit when every route in it is offered and healthy, and
32
+ * move unhealthy fallbacks to the end. Returns a new row with `learned` notes for the card.
33
+ */
34
+ export function applyPreferences(row, habit, unhealthy) {
35
+ const offeredKeys = new Set([routeKey(row.primary), ...row.fallbacks.map(routeKey), ...row.candidates.map(routeKey)].filter(Boolean));
36
+ const learned = { unhealthy: Object.fromEntries(Object.entries(unhealthy).filter(([k]) => offeredKeys.has(k))) };
37
+ let primary = row.primary, fallbacks = row.fallbacks;
38
+ if (habit && offeredKeys.has(habit.primary) && !unhealthy[habit.primary] && habit.fallbacks.every(k => offeredKeys.has(k))) {
39
+ learned.habit = { count: habit.count, of: habit.of, settled: habit.settled };
40
+ if (habit.primary !== routeKey(primary) || habit.fallbacks.join() !== fallbacks.map(routeKey).join()) {
41
+ primary = parse(habit.primary); fallbacks = habit.fallbacks.filter(k => k !== habit.primary).map(parse); learned.preselected = true;
42
+ }
43
+ }
44
+ fallbacks = [...fallbacks.filter(f => !unhealthy[routeKey(f)]), ...fallbacks.filter(f => unhealthy[routeKey(f)])];
45
+ return { ...row, primary, fallbacks, learned };
46
+ }
47
+ export function createModelPrefs(root) {
48
+ const pathOf = project => join(root, 'model-prefs', `${createHash('sha256').update(project).digest('hex').slice(0, 32)}.json`);
49
+ async function read(project) {
50
+ try { const d = JSON.parse(await readFile(pathOf(project), 'utf8')); return d && typeof d.byCapability === 'object' ? d : { byCapability: {} }; }
51
+ catch (error) { if (error.code === 'ENOENT' || error instanceof SyntaxError) return { byCapability: {} }; throw error; }
52
+ }
53
+ async function write(project, data) {
54
+ const path = pathOf(project);
55
+ await mkdir(join(root, 'model-prefs'), { recursive: true });
56
+ const temp = `${path}.${randomUUID()}.tmp`;
57
+ try { await writeFile(temp, JSON.stringify(data, null, 2) + '\n'); await rename(temp, path); }
58
+ finally { await unlink(temp).catch(() => {}); }
59
+ }
60
+ let queue = Promise.resolve();
61
+ const serial = fn => (queue = queue.catch(() => {}).then(fn));
62
+ return {
63
+ read,
64
+ /** Habit per capability for a project. */
65
+ async habits(project) {
66
+ // Wait for queued records so a habit read right after a confirmation sees it (rename timing differs by OS).
67
+ await queue.catch(() => {});
68
+ const data = await read(project);
69
+ return Object.fromEntries(Object.entries(data.byCapability).map(([cap, v]) => [cap, habitOf(v.choices)]).filter(([, h]) => h));
70
+ },
71
+ /** Record the confirmed routes of one dispatch (each capability once). */
72
+ record(project, routes, at = Date.now()) {
73
+ return serial(async () => {
74
+ const data = await read(project);
75
+ const seen = new Set();
76
+ for (const r of routes) {
77
+ if (!r.primary || seen.has(r.capability)) continue;
78
+ seen.add(r.capability);
79
+ const entry = (data.byCapability[r.capability] ??= { choices: [] });
80
+ entry.choices = [...entry.choices, { primary: routeKey(r.primary), fallbacks: r.fallbacks.map(routeKey), changed: !!r.changed, at }].slice(-PREF_LIMITS.keep);
81
+ }
82
+ await write(project, data);
83
+ });
84
+ },
85
+ clear(project) { return serial(() => unlink(pathOf(project)).catch(error => { if (error.code !== 'ENOENT') throw error; })); },
86
+ };
87
+ }
88
+ const output = { schema: { type: 'string' }, render: (_a, v) => [{ type: 'text', text: v }] };
89
+ /** Lead tool to show or clear what was learned for the current project. */
90
+ export function applyModelPrefsTool(ctx, team, prefs) {
91
+ ctx.effect(() => ctx.tools.register({ name: 'capability_model_prefs', output,
92
+ description: modelText('model-prefs.316'),
93
+ parameters: { type: 'object', properties: { action: { type: 'string', enum: ['read', 'clear'] } }, required: ['action'], additionalProperties: false },
94
+ async execute(args, exec) {
95
+ const project = await team.pin(exec.agent);
96
+ if (args.action === 'clear') {
97
+ if (ctx.agentTeams.tryMembership(exec.agent)?.role !== 'lead') throw new Error(modelText('model-prefs.317'));
98
+ await prefs.clear(project);
99
+ return JSON.stringify({ cleared: true, project });
100
+ }
101
+ return JSON.stringify({ project, habits: await prefs.habits(project) });
102
+ },
103
+ }));
104
+ }
@@ -0,0 +1,76 @@
1
+ import { runtimeBinding } from './runtime-observability.js';
2
+ import { safeId } from './activity-summary.js';
3
+ export const RUNTIME_PATH = '/api/capability-team.runtime';
4
+ export const RUNTIME_STREAM_PATH = `${RUNTIME_PATH}/stream`;
5
+ /** Epoch of the placeholder snapshot of a team lead whose runtime is not bound yet (a draft session before its first message). */
6
+ export const UNBOUND_EPOCH = 'unbound';
7
+ const UNBOUND_POLL_MS = 1000;
8
+ /** Empty but well-formed snapshot: the Team overview renders "no activity yet" instead of an error. */
9
+ export function emptySnapshot(leadSessionId) {
10
+ return { schemaVersion: 1, leadSessionId, epoch: UNBOUND_EPOCH, revision: 0, generatedAt: 0, coverage: { mode: 'unbound', since: 0 }, members: [], taskControl: null };
11
+ }
12
+ function resolve(ctx, request) {
13
+ const url = new URL(request.url), ids = url.searchParams.getAll('sessionId');
14
+ if (ids.length !== 1 || !safeId(ids[0]) || [...url.searchParams.keys()].some(k => k !== 'sessionId')) return { error: 400 };
15
+ // Never resolve/activate a session from user-controlled input.
16
+ const agent = ctx.get?.('agents')?.get(ids[0]);
17
+ if (!agent) return { error: 404 };
18
+ const teams = ctx.get?.('agentTeams');
19
+ if (!teams || !ctx.agentPresets) return { error: 503 };
20
+ const membership = teams.tryMembership(agent);
21
+ if (!membership || membership.role !== 'lead' || ctx.agentPresets?.composedPreset(agent.ctx) !== 'capability-team') return { error: 404 };
22
+ // A draft lead has no runtime until its first message: answer with an empty snapshot, not an error.
23
+ const current = () => runtimeBinding(agent);
24
+ const snapshot = () => { const binding = current(); return binding ? binding.runtime.snapshot(agent.id, teams.listMembers(agent)) : emptySnapshot(agent.id); };
25
+ return { current, agent, snapshot };
26
+ }
27
+ export function handleRuntime(ctx, request, stream = false) {
28
+ if (request.method !== 'GET') return new Response('method not allowed', { status: 405 });
29
+ // In production Connection refuses non-owner peers before invoking any registered fetch route.
30
+ let resolved;
31
+ try { resolved = resolve(ctx, request); } catch { return Response.json({ error: 503 }, { status: 503 }); }
32
+ if (resolved.error) return Response.json({ error: resolved.error }, { status: resolved.error, headers: { 'cache-control': 'no-store' } });
33
+ if (!stream) {
34
+ try { return Response.json(resolved.snapshot(), { headers: { 'cache-control': 'no-store' } }); }
35
+ catch { return Response.json({ error: 503 }, { status: 503 }); }
36
+ }
37
+ let dispose = () => {}, timer = null, poll = null, closed = false, abortCleanup = () => {};
38
+ const body = new ReadableStream({
39
+ start(controller) {
40
+ const encoder = new TextEncoder();
41
+ const close = () => { if (closed) return; closed = true; if (timer) clearTimeout(timer); if (poll) clearInterval(poll); dispose(); request.signal.removeEventListener('abort', close); controller.close(); };
42
+ const emit = () => {
43
+ timer = null; if (closed) return;
44
+ try { controller.enqueue(encoder.encode(`data: ${JSON.stringify(resolved.snapshot())}\n\n`)); }
45
+ catch { close(); }
46
+ };
47
+ emit();
48
+ const attach = binding => { dispose = binding.runtime.onChanged(() => { if (!closed && timer === null) timer = setTimeout(emit, 350); }); };
49
+ const bound = resolved.current();
50
+ if (bound) attach(bound);
51
+ else {
52
+ // Idle until the lead's runtime appears (first message), then push the real snapshot and follow its changes.
53
+ poll = setInterval(() => {
54
+ if (closed) return;
55
+ const binding = resolved.current();
56
+ if (!binding) return;
57
+ clearInterval(poll); poll = null; attach(binding); emit();
58
+ }, UNBOUND_POLL_MS);
59
+ poll.unref?.();
60
+ }
61
+ abortCleanup = () => request.signal.removeEventListener('abort', close);
62
+ request.signal.addEventListener('abort', close, { once: true });
63
+ if (request.signal.aborted) close();
64
+ },
65
+ cancel() { closed = true; if (timer) clearTimeout(timer); if (poll) clearInterval(poll); dispose(); abortCleanup(); },
66
+ });
67
+ return new Response(body, { headers: { 'content-type': 'text/event-stream', 'cache-control': 'no-store', 'x-accel-buffering': 'no' } });
68
+ }
69
+ export function applyObservabilityApi(ctx) {
70
+ ctx.inject?.(['connection', 'agents', 'agentTeams', 'agentPresets'], scoped => {
71
+ for (const [path, stream] of [[RUNTIME_PATH, false], [RUNTIME_STREAM_PATH, true]]) {
72
+ // Host createSharedFetchHandler enforces peer.principal.kind === 'owner'.
73
+ scoped.connection.fetch.register({ path, methods: ['GET'], requestBody: 'buffered', fetch: request => Promise.resolve(handleRuntime(scoped, request, stream)) });
74
+ }
75
+ });
76
+ }
@@ -0,0 +1,141 @@
1
+ import { t } from './i18n.js';
2
+ import { safeId, safeTarget, safeTime, summarizeActivity } from './activity-summary.js';
3
+ import { isolatedProjection } from './preset-guard.js';
4
+ import { OWN_USAGE_KEY, initOwnUsage, applyOwnUsage, ownUsageView } from './usage.js';
5
+ const schema = { parse(v) { if (!v || typeof v !== 'object' || Array.isArray(v)) throw new Error('Invalid observability state'); return v; } };
6
+ const n = x => typeof x === 'number' && x >= 0 ? Math.min(x, Number.MAX_SAFE_INTEGER) : 0;
7
+ const base = (header = {}, inherited = 0) => ({ sessionId: safeId(header.id) ?? 'unknown', cwd: header.cwd ?? null, root: header.cwd ?? null, inherited, cursor: -1 });
8
+ const ignored = (s, e) => !Number.isSafeInteger(e.seq) || e.seq < s.inherited || e.seq <= s.cursor;
9
+ const coord = x => Number.isSafeInteger(x) && x >= 0 ? x : null;
10
+ const callId = e => safeId(e.data?.message?.source?.callId ?? e.data?.message?.toolCallId);
11
+ const definition = (key, init, apply) => ({ key, stateVersion: 1,
12
+ stateSchema: { parse(value) {
13
+ schema.parse(value);
14
+ if (!Number.isSafeInteger(value.inherited) || value.inherited < 0 || !Number.isSafeInteger(value.cursor) || value.cursor < -1 || !value.view || value.view.schemaVersion !== 1) throw new Error('Invalid observability checkpoint');
15
+ if (key === 'capabilityTeamActivity' && (!Array.isArray(value.pending) || value.pending.length > 64 || !Array.isArray(value.view.recent) || value.view.recent.length > 8)) throw new Error('Invalid activity checkpoint');
16
+ if (key === 'capabilityTeamArtifacts' && (!Array.isArray(value.view.items) || value.view.items.length > 20 || !Array.isArray(value.view.changes) || value.view.changes.length > 10)) throw new Error('Invalid artifacts checkpoint');
17
+ if (key === 'capabilityTeamReport' && (!Array.isArray(value.pending) || value.pending.length > 64 || !value.view.usage || !value.view.timing || !value.view.counters)) throw new Error('Invalid report checkpoint');
18
+ if (Buffer.byteLength(JSON.stringify(value.view)) > 32768) throw new Error('Oversized observability checkpoint');
19
+ return value;
20
+ } }, init, apply, wire: { viewSchema: schema, view: s => s.view } });
21
+ const cwdChange = (s, e) => e.type === 'working-directory/change' ? { ...s, cwd: e.data?.cwd ?? e.data?.path ?? null, cursor: e.seq } : null;
22
+ export const activityDefinition = definition('capabilityTeamActivity', (h, i) => ({ ...base(h, i), pending: [], view: { schemaVersion: 1, phase: 'idle', current: null, recent: [], activeCount: 0, truncated: false } }), (s, e) => {
23
+ if (ignored(s, e)) return s;
24
+ const cwd = cwdChange(s, e); if (cwd) return cwd;
25
+ const d = e.data ?? {}; let pending = s.pending, recent = s.view.recent, phase = s.view.phase, truncated = s.view.truncated;
26
+ if (e.type === 'tool/call') {
27
+ const id = safeId(d.callId); if (!id) return s;
28
+ const item = { id: `${s.sessionId}:${e.seq}`, callId: id, callSeq: e.seq, ...summarizeActivity(d.name, d.arguments, s.cwd, s.root), startedAt: safeTime(e.time), endedAt: null, status: 'running', turn: coord(d.turn), step: coord(d.step) };
29
+ pending = [...pending.filter(p => p.callId !== id), item];
30
+ if (pending.length > 64) { pending = pending.slice(-64); truncated = true; }
31
+ const history = [item, ...recent.filter(p => p.callId !== id)];
32
+ if (history.length > 8) truncated = true;
33
+ recent = history.slice(0, 8); phase = 'tool';
34
+ } else if (e.type === 'tool/result') {
35
+ const id = callId(e), item = pending.find(p => p.callId === id); if (!item) return s;
36
+ pending = pending.filter(p => p.callId !== id);
37
+ const done = { ...item, endedAt: safeTime(e.time), status: d.message?.isError ? 'failed' : 'succeeded' };
38
+ const history = [done, ...recent.filter(p => p.id !== item.id)];
39
+ if (history.length > 8) truncated = true;
40
+ recent = history.slice(0, 8); phase = pending.length ? 'tool' : 'model';
41
+ } else if (e.type === 'step/start') phase = pending.length ? 'tool' : 'model';
42
+ else if (e.type === 'step/end') phase = pending.length ? 'tool' : 'settled';
43
+ else if (e.type === 'turn/end') {
44
+ const ids = new Set(pending.map(p => p.id));
45
+ recent = recent.map(p => ids.has(p.id) ? { ...p, endedAt: safeTime(e.time), status: d.reason?.kind === 'cancelled' || d.reason?.kind === 'interrupted' ? 'interrupted' : 'unknown' } : p);
46
+ pending = []; phase = 'settled';
47
+ } else return s;
48
+ return { ...s, cursor: e.seq, pending, view: { schemaVersion: 1, phase, current: recent[0] ?? null, recent, activeCount: pending.length, truncated } };
49
+ });
50
+ export const artifactsDefinition = definition('capabilityTeamArtifacts', (h, i) => ({ ...base(h, i), view: { schemaVersion: 1, items: [], changes: [], truncated: false } }), (s, e) => {
51
+ if (ignored(s, e)) return s;
52
+ const cwd = cwdChange(s, e); if (cwd) return cwd;
53
+ const d = e.data ?? {}; let { items, changes, truncated } = s.view;
54
+ if (e.type === 'workspace/changes') {
55
+ changes = [{ seq: e.seq, turn: coord(d.turn), at: safeTime(e.time) }, ...changes];
56
+ if (changes.length > 10) { changes = changes.slice(0, 10); truncated = true; }
57
+ } else if (e.type === 'deliverables/presented' && Array.isArray(d.files)) {
58
+ const additions = [];
59
+ for (let index = 0; index < Math.min(d.files.length, 256); index++) {
60
+ const file = d.files[index]; const relative = safeTarget(file?.path, s.cwd, 4096, s.root);
61
+ if (!relative || relative === t('observability-projection.318')) continue;
62
+ additions.push({ id: `${s.sessionId}:${e.seq}:${index}`, sessionId: s.sessionId, presentSeq: e.seq, fileIndex: index, turn: coord(d.turn), at: safeTime(e.time), path: file.path, displayPath: relative.slice(0, 240), changeRef: null });
63
+ }
64
+ items = [...additions, ...items];
65
+ if (items.length > 20 || d.files.length > 256) { items = items.slice(0, 20); truncated = true; }
66
+ // A UTF-8 byte ceiling, not merely a character limit.
67
+ while (Buffer.byteLength(JSON.stringify({ items, changes })) > 31000) { items.pop(); truncated = true; }
68
+ } else return s;
69
+ return { ...s, cursor: e.seq, view: { schemaVersion: 1, items, changes, truncated } };
70
+ });
71
+ const zeroUsage = () => ({ uncachedInputTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0, outputTokens: 0 });
72
+ function firstToken(stream) {
73
+ for (const part of stream ?? []) {
74
+ if (part.type === 'chunk') {
75
+ const c = part.chunk;
76
+ if ((['text-delta', 'reasoning-delta'].includes(c?.type) && c.text !== '') || (c?.type === 'tool-call-delta' && (c.argumentsDelta !== '' || c.name !== undefined))) return safeTime(part.time);
77
+ } else {
78
+ if (part.type === 'tool-call-chunks' && part.name !== undefined) return safeTime(part.time0);
79
+ const fragments = part.type === 'tool-call-chunks' ? part.args : part.texts;
80
+ let time = part.time0;
81
+ for (let i = 0; i < (fragments?.length ?? 0); i++) { if (i) time += part.dt?.[i - 1] ?? 0; if (fragments[i] !== '') return safeTime(time); }
82
+ }
83
+ }
84
+ return null;
85
+ }
86
+ export const reportDefinition = definition('capabilityTeamReport', (h, i) => ({ ...base(h, i), ownUsage: initOwnUsage(i), openStep: null, pending: [], lastTurn: null, view: { schemaVersion: 1, scope: 'own-session-events', firstTurnAt: null, lastTurnEndAt: null, openTurnAt: null, completedTurnMs: 0, turns: 0, steps: 0, stepsStarted: 0, usage: zeroUsage(), usageCoverage: { reportedSamples: 0, attemptsWithoutUsage: 0, coverage: 'unknown' }, timing: { llmMs: 0, toolMs: 0, ttftMs: 0, ttftSteps: 0, decodeMs: 0, decodeTokens: 0 }, counters: { hostRetriesStarted: 0, failedTurns: 0, sendMessageAccepted: 0, boardMutations: h?.origin === 'subagent' ? null : 0 }, truncated: false } }), (s, e) => {
87
+ if (ignored(s, e)) return s;
88
+ const relevant = ['request/header', 'turn/start', 'turn/end', 'step/start', 'step/end', 'assistant/attempt', 'assistant/message', 'llm/retry-started', 'tool/call', 'tool/result', 'team/task'];
89
+ if (!relevant.includes(e.type)) return s;
90
+ const d = e.data ?? {}, v = { ...s.view, usage: { ...s.view.usage }, timing: { ...s.view.timing }, counters: { ...s.view.counters } };
91
+ const next = { ...s, cursor: e.seq, view: v }; const time = safeTime(e.time);
92
+ const add = (obj, key, value) => { if (typeof value === 'number' && value > Number.MAX_SAFE_INTEGER) v.truncated = true; const sum = obj[key] + n(value); if (!Number.isFinite(sum) || sum > Number.MAX_SAFE_INTEGER) v.truncated = true; obj[key] = n(sum); };
93
+ if (e.type === 'turn/start') { v.firstTurnAt ??= time; v.openTurnAt = time; }
94
+ if (e.type === 'turn/end') {
95
+ if (v.openTurnAt !== null && time !== null) add(v, 'completedTurnMs', Math.max(0, time - v.openTurnAt));
96
+ v.openTurnAt = null; v.lastTurnEndAt = time; next.pending = []; next.openStep = null;
97
+ if (d.reason?.kind === 'error') add(v.counters, 'failedTurns', 1);
98
+ }
99
+ if (e.type === 'step/start') { add(v, 'stepsStarted', 1); next.openStep = { turn: d.turn, step: d.step, at: time, first: null }; }
100
+ if (e.type === 'step/end') { add(v, 'steps', 1); if (s.lastTurn !== d.turn) add(v, 'turns', 1); next.lastTurn = d.turn; next.openStep = null; }
101
+ if (e.type === 'llm/retry-started') { add(v.counters, 'hostRetriesStarted', 1); }
102
+ if (e.type === 'tool/call' && safeId(d.callId)) {
103
+ next.pending = [...s.pending, { id: d.callId, at: time, send: d.name === 'send_message' }].slice(-64);
104
+ if (s.pending.length >= 64) v.truncated = true;
105
+ }
106
+ if (e.type === 'tool/result') {
107
+ const id = callId(e), p = s.pending.find(p => p.id === id);
108
+ if (p) { next.pending = s.pending.filter(p => p.id !== id); if (time !== null && p.at !== null) add(v.timing, 'toolMs', time - p.at); if (p.send && !d.message?.isError) add(v.counters, 'sendMessageAccepted', 1); }
109
+ }
110
+ if (e.type === 'team/task' && v.counters.boardMutations !== null) add(v.counters, 'boardMutations', 1);
111
+ if (['assistant/message', 'assistant/attempt'].includes(e.type)) {
112
+ const open = s.openStep;
113
+ if (open && open.turn === d.turn && open.step === d.step) {
114
+ const first = open.first ?? firstToken(d.stream);
115
+ next.openStep = { ...open, first };
116
+ if (e.type === 'assistant/message') {
117
+ if (time !== null && open.at !== null) add(v.timing, 'llmMs', time - open.at);
118
+ if (first !== null && open.at !== null) { add(v.timing, 'ttftMs', first - open.at); add(v.timing, 'ttftSteps', 1); if (d.usage?.outputTokens != null) { add(v.timing, 'decodeMs', time - first); add(v.timing, 'decodeTokens', d.usage.outputTokens); } }
119
+ next.openStep = null;
120
+ }
121
+ }
122
+ }
123
+ next.ownUsage = applyOwnUsage(s.ownUsage, e);
124
+ v.usage = next.ownUsage.totals;
125
+ v.truncated ||= next.ownUsage.truncated;
126
+ v.usageCoverage = { reportedSamples: next.ownUsage.reportedSamples, attemptsWithoutUsage: next.ownUsage.attemptsWithoutUsage, coverage: next.ownUsage.coverage };
127
+ return next;
128
+ });
129
+ reportDefinition.stateVersion = 3; // Own-session attempt-aware fold; discard v1 checkpoints.
130
+ export const ownUsageDefinition = {
131
+ key: OWN_USAGE_KEY, stateVersion: 1, stateSchema: { parse(v) { schema.parse(v); if (!Number.isSafeInteger(v.inherited) || !v.totals || !Array.isArray(v.routes)) throw new Error('Invalid own usage checkpoint'); return v; } },
132
+ init: (_header, inherited) => { const s = initOwnUsage(inherited); return { ...s, view: ownUsageView(s) }; },
133
+ apply: (s, e) => { const next = applyOwnUsage(s, e); return next === s ? s : { ...next, view: ownUsageView(next) }; },
134
+ wire: { viewSchema: schema, view: s => s.view },
135
+ };
136
+ export const observabilityDefinitions = [activityDefinition, artifactsDefinition, reportDefinition, ownUsageDefinition];
137
+ export function applyObservabilityProjections(ctx) {
138
+ ctx.inject?.(['sessionProjections'], scoped => {
139
+ for (const def of observabilityDefinitions) scoped.effect(() => scoped.sessionProjections.register(isolatedProjection(ctx, def)), `capability-team: ${def.key}`);
140
+ });
141
+ }
package/onboarding.js ADDED
@@ -0,0 +1,47 @@
1
+ // Profile-wide first-run status for the Web onboarding card: whether DSH's official Agent Teams service is
2
+ // running, the official bundle's selection state, and how far the live model catalog covers the default tiers.
3
+ // Read-only: enabling the bundle is a user click in the browser through DSH's own pluginManager Remote.
4
+ import { seed } from './config.js';
5
+ import { autoMatch } from './defaults.js';
6
+ export const ONBOARDING_PATH = '/api/capability-team.onboarding';
7
+ export const AGENT_TEAM_BUNDLE = '@deepseek-ai/dsh-experimental-agent-team-profile';
8
+ /** The official bundle row from pluginManager.listBundles(), reduced to what the card needs. */
9
+ export async function teamBundleStatus(pluginManager) {
10
+ if (typeof pluginManager?.listBundles !== 'function') return { known: false };
11
+ try {
12
+ const listed = await pluginManager.listBundles();
13
+ const rows = Array.isArray(listed) ? listed : listed?.ok ? listed.value : [];
14
+ const row = rows.find(b => b?.name === AGENT_TEAM_BUNDLE);
15
+ if (!row) return { known: true, listed: false };
16
+ return { known: true, listed: true, enabled: !!row.enabled, installed: !!row.installed, availability: row.availability ?? null, ...(row.error?.code ? { error: row.error.code } : {}) };
17
+ } catch (error) { return { known: false, error: String(error?.message ?? error).slice(0, 200) }; }
18
+ }
19
+ /** Tier coverage of the seed capabilities against the live catalog; no model is called. */
20
+ export async function tierCoverage(llm, capabilities = seed.capabilities) {
21
+ if (typeof llm?.listProviders !== 'function') return { known: false };
22
+ const entries = []; const errors = [];
23
+ for (const provider of llm.listProviders()) {
24
+ try { for (const m of await llm.listModels(provider.id)) entries.push({ provider: provider.id, model: m.id }); }
25
+ catch { errors.push(provider.id); }
26
+ }
27
+ const { report } = autoMatch(entries, capabilities);
28
+ const unassigned = report.filter(r => r.status === 'unassigned').map(r => r.capability);
29
+ return { known: true, models: entries.length, capabilities: report.length, covered: report.length - unassigned.length, unassigned, ...(errors.length ? { catalogErrors: errors } : {}) };
30
+ }
31
+ /** Everything the onboarding card shows; each part degrades to `known: false` instead of failing. */
32
+ export async function onboardingStatus(ctx) {
33
+ const selection = (() => { try { return ctx.get?.('agentDefaultModel')?.currentSelection?.(); } catch { return undefined; } })();
34
+ return {
35
+ teamServicePresent: !!ctx.get?.('agentTeams'),
36
+ bundle: await teamBundleStatus(ctx.get?.('pluginManager')),
37
+ coverage: await tierCoverage(ctx.get?.('llm')).catch(() => ({ known: false })),
38
+ ...(selection?.provider && selection?.model ? { defaultModel: `${selection.provider}/${selection.model}` } : {}),
39
+ };
40
+ }
41
+ export function applyOnboardingApi(ctx) {
42
+ ctx.inject?.(['connection'], scoped => scoped.connection.fetch.register({ path: ONBOARDING_PATH, methods: ['GET'], requestBody: 'buffered',
43
+ fetch: async () => {
44
+ try { return Response.json(await onboardingStatus(ctx), { headers: { 'cache-control': 'no-store' } }); }
45
+ catch { return Response.json({ code: 'UNAVAILABLE' }, { status: 503 }); }
46
+ } }));
47
+ }
package/opencode.js ADDED
@@ -0,0 +1,74 @@
1
+ import { m as modelText } from './i18n.js';
2
+ // Locating OpenCode and its free models on any platform. Nothing here names a user, a machine or a
3
+ // fixed model list: the executable comes from an env override or PATH, the models from OpenCode's own
4
+ // catalog cache, filtered to zero-cost, non-deprecated entries.
5
+ import { access, readFile, constants } from 'node:fs/promises';
6
+ import { homedir } from 'node:os';
7
+ import { posix, win32 } from 'node:path';
8
+
9
+ const ENV_EXE = 'CAPABILITY_TEAM_OPENCODE_EXECUTABLE';
10
+ const ENV_CATALOG = 'CAPABILITY_TEAM_OPENCODE_MODELS';
11
+ const pathApi = platform => (platform === 'win32' ? win32 : posix);
12
+
13
+ /** Candidate native executables, most specific first. Shell shims (.cmd/.ps1) are never returned. */
14
+ export function openCodeCandidates({ env = process.env, platform = process.platform, home = homedir() } = {}) {
15
+ const p = pathApi(platform);
16
+ const dirs = (env.PATH ?? env.Path ?? '').split(p.delimiter).filter(Boolean);
17
+ const list = [];
18
+ for (const dir of dirs) {
19
+ if (platform === 'win32') {
20
+ list.push(p.join(dir, 'opencode.exe'));
21
+ // npm on Windows puts a .cmd shim on PATH; the native binary sits in the package next to it.
22
+ list.push(p.join(dir, 'node_modules', 'opencode-ai', 'bin', 'opencode.exe'));
23
+ } else list.push(p.join(dir, 'opencode'));
24
+ }
25
+ // Location used by the official install script on every platform.
26
+ list.push(p.join(home, '.opencode', 'bin', platform === 'win32' ? 'opencode.exe' : 'opencode'));
27
+ return [...new Set(list)];
28
+ }
29
+
30
+ export async function findOpenCode({ env = process.env, platform = process.platform, home = homedir(), exists = defaultExists(platform) } = {}) {
31
+ if (env[ENV_EXE]) return env[ENV_EXE];
32
+ for (const file of openCodeCandidates({ env, platform, home })) if (await exists(file)) return file;
33
+ throw new Error(modelText('opencode.319', { 0: ENV_EXE }));
34
+ }
35
+ function defaultExists(platform) {
36
+ return file => access(file, platform === 'win32' ? constants.F_OK : constants.X_OK).then(() => true, () => false);
37
+ }
38
+
39
+ /** OpenCode keeps its model catalog under the XDG cache directory on all platforms. */
40
+ export function catalogFile({ env = process.env, platform = process.platform, home = homedir() } = {}) {
41
+ if (env[ENV_CATALOG]) return env[ENV_CATALOG];
42
+ const p = pathApi(platform);
43
+ return p.join(env.XDG_CACHE_HOME || p.join(home, '.cache'), 'opencode', 'models.json');
44
+ }
45
+
46
+ const isZero = v => v === undefined || v === 0;
47
+ /** Zero-cost, non-deprecated, tool-capable models of OpenCode's own provider, newest first. */
48
+ export function freeModelsFromCatalog(catalog) {
49
+ const models = catalog?.opencode?.models ?? {};
50
+ return Object.entries(models)
51
+ .filter(([, m]) => m && m.cost && m.cost.input === 0 && m.cost.output === 0 && isZero(m.cost.cache_read) && isZero(m.cost.cache_write)
52
+ && m.status !== 'deprecated' && m.tool_call !== false)
53
+ .sort(([a, x], [b, y]) => String(y.release_date ?? '').localeCompare(String(x.release_date ?? '')) || a.localeCompare(b))
54
+ .map(([id]) => `opencode/${id}`);
55
+ }
56
+
57
+ /**
58
+ * Models the free channel may use. A configured list only orders and narrows; every entry must still be
59
+ * free in the catalog, so a paid model can never be configured in by mistake.
60
+ */
61
+ export async function resolveFreeModels(configured = [], { file = catalogFile(), read = readFile } = {}) {
62
+ let catalog;
63
+ try { catalog = JSON.parse(await read(file, 'utf8')); } catch (error) {
64
+ throw new Error(modelText('opencode.320', { 0: file, 1: error.code ?? error.message, 2: ENV_CATALOG }));
65
+ }
66
+ const free = freeModelsFromCatalog(catalog);
67
+ if (!configured.length) {
68
+ if (!free.length) throw new Error(modelText('opencode.321'));
69
+ return free;
70
+ }
71
+ const rejected = configured.filter(m => !free.includes(m));
72
+ if (rejected.length) throw new Error(modelText('opencode.322', { 0: rejected.join('、'), 1: free.join('、') || modelText('opencode.323') }));
73
+ return configured;
74
+ }
package/package.json CHANGED
@@ -1,6 +1,155 @@
1
1
  {
2
2
  "name": "@aiwuk/team",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "5.0.3",
4
+ "description": "AIWUK Team, a capability-routed multi-agent team preset for DeepSeek Harness (DSH) with per-role model routing, same-tier failover and conversational team configuration",
5
+ "license": "MIT",
6
+ "author": "walke2019",
7
+ "homepage": "https://github.com/walke2019/dsh-capability-team#readme",
8
+ "repository": { "type": "git", "url": "git+https://github.com/walke2019/dsh-capability-team.git" },
9
+ "bugs": { "url": "https://github.com/walke2019/dsh-capability-team/issues" },
10
+ "keywords": ["aiwuk", "dsh", "dsh-plugin", "deepseek-harness", "cordis", "agent-team", "multi-agent", "model-routing", "failover", "ai-agent", "llm"],
11
+ "engines": { "node": ">=22", "dsh": ">=0.2.2-rc.1 <0.3.0" },
12
+ "peerDependencies": {
13
+ "@deepseek-ai/cordis": ">=4.0.1",
14
+ "@deepseek-ai/dsh-agent-preset": ">=0.2.2-rc.1",
15
+ "@deepseek-ai/dsh-persona": ">=0.2.2-rc.1",
16
+ "@deepseek-ai/dsh-experimental-agent-team": ">=0.2.2-rc.1",
17
+ "@deepseek-ai/dsh-client-locale": ">=0.2.2-rc.1",
18
+ "@deepseek-ai/dsh-client-ui-conversation": ">=0.2.2-rc.1",
19
+ "@deepseek-ai/dsh-client-ui-tool": ">=0.2.2-rc.1",
20
+ "@deepseek-ai/dsh-api-remotes": ">=0.2.2-rc.1",
21
+ "@deepseek-ai/dsh-client-ui-sidebar-right": ">=0.2.2-rc.1",
22
+ "@deepseek-ai/schemastery": ">=3.18.4"
23
+ },
24
+ "peerDependenciesMeta": {
25
+ "@deepseek-ai/cordis": { "optional": true },
26
+ "@deepseek-ai/dsh-agent-preset": { "optional": true },
27
+ "@deepseek-ai/dsh-persona": { "optional": true },
28
+ "@deepseek-ai/dsh-experimental-agent-team": { "optional": true },
29
+ "@deepseek-ai/dsh-client-locale": { "optional": true },
30
+ "@deepseek-ai/dsh-client-ui-conversation": { "optional": true },
31
+ "@deepseek-ai/dsh-client-ui-tool": { "optional": true },
32
+ "@deepseek-ai/dsh-api-remotes": { "optional": true },
33
+ "@deepseek-ai/dsh-client-ui-sidebar-right": { "optional": true },
34
+ "@deepseek-ai/schemastery": { "optional": true }
35
+ },
36
+ "type": "module",
37
+ "scripts": {
38
+ "build:client": "node shared-ui/build.mjs && node scripts/build-client.mjs",
39
+ "pretest": "node shared-ui/build.mjs && node scripts/build-client.mjs",
40
+ "test": "node --import ./tests/isolated-home.mjs --test tests/*.test.mjs && node --import ./tests/isolated-home.mjs --test tests/global-memory/*.test.mjs && node --import ./tests/isolated-home.mjs --test tests/omni-search/*.test.mjs && node --import ./tests/isolated-home.mjs --test tests/autocomplete/*.test.mjs",
41
+ "build:global-memory": "node packages/global-memory/scripts/build-shared.mjs",
42
+ "build:omni-search": "node packages/omni-search/scripts/build-shared.mjs",
43
+ "build:autocomplete": "node packages/autocomplete/scripts/build-client.mjs",
44
+ "lint": "node scripts/check.mjs syntax",
45
+ "build": "node scripts/check.mjs package"
46
+ },
47
+ "exports": {
48
+ ".": "./ui.js",
49
+ "./client": "./client.js",
50
+ "./roles": "./index.js",
51
+ "./locale/*.json": "./locale/*.json",
52
+ "./i18n/*.json": "./i18n/*.json",
53
+ "./icon": "./icon.svg"
54
+ },
55
+ "files": [
56
+ "brand.js",
57
+ "i18n.js",
58
+ "roles.js",
59
+ "lead-route.js",
60
+ "config.js",
61
+ "roster-defaults.js",
62
+ "single-model.js",
63
+ "onboarding.js",
64
+ "settings-schema.js",
65
+ "team.js",
66
+ "failover.js",
67
+ "defaults.js",
68
+ "index.js",
69
+ "free-worker.mjs",
70
+ "integrations.js",
71
+ "search.js",
72
+ "search-engines.js",
73
+ "transcribe.js",
74
+ "task-bridge.js",
75
+ "theme.js",
76
+ "allocation.js",
77
+ "theme-plugin.js",
78
+ "tool-schema.js",
79
+ "tool-names.js",
80
+ "workflow.js",
81
+ "opencode.js",
82
+ "ui.js",
83
+ "runtime-view.js",
84
+ "model-policy.js",
85
+ "access.js",
86
+ "teammate-wait.js",
87
+ "usage.js",
88
+ "long-context.js",
89
+ "plan-gate.js",
90
+ "template.js",
91
+ "config-projection.js",
92
+ "session-policy.js",
93
+ "request-evidence.js",
94
+ "activity-summary.js",
95
+ "observability-projection.js",
96
+ "runtime-observability.js",
97
+ "observability-api.js",
98
+ "member-task-control.js",
99
+ "task-control-audit.js",
100
+ "task-control-api.js",
101
+ "client.js",
102
+ "client/*.js",
103
+ "file-limits.js",
104
+ "dispatch-confirm.js",
105
+ "dispatch-routes-store.js",
106
+ "session-rotation.js",
107
+ "memory-embed.js",
108
+ "memory-store.js",
109
+ "memory-hub.js",
110
+ "memory-openviking.js",
111
+ "memory-backends.js",
112
+ "preset-guard.js",
113
+ "token-metrics.js",
114
+ "memory-sync-queue.js",
115
+ "memory-redact.js",
116
+ "integrations-config.js",
117
+ "integrations-messages.js",
118
+ "managed-resolve.js",
119
+ "managed-ref.js",
120
+ "managed-config-route.js",
121
+ "shared/managed-config.js",
122
+ "http-guard.js",
123
+ "model-prefs.js",
124
+ "board-dispatch.js",
125
+ "teammate-context.js",
126
+ "token-discipline.js",
127
+ "route-health.js",
128
+ "skills/project-role-theme/SKILL.md",
129
+ "README.md",
130
+ "README.zh-CN.md",
131
+ "CHANGELOG.md",
132
+ "LICENSE",
133
+ "scripts/memory-cli.mjs",
134
+ "cordis.patch.yml",
135
+ "locale/*.json",
136
+ "i18n/*.json",
137
+ "icon.svg"
138
+ ],
139
+ "dsh": {
140
+ "bundle": {
141
+ "patch": "./cordis.patch.yml"
142
+ },
143
+ "client": {
144
+ "platform": "web",
145
+ "immediately": true,
146
+ "inject": [
147
+ "@deepseek-ai/dsh-client-locale",
148
+ "@deepseek-ai/dsh-client-ui-conversation",
149
+ "@deepseek-ai/dsh-client-ui-tool",
150
+ "@deepseek-ai/dsh-api-remotes",
151
+ "@deepseek-ai/dsh-client-ui-sidebar-right"
152
+ ]
153
+ }
154
+ }
155
+ }