@aiwuk/team 0.0.0-stage → 5.0.8

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 (117) hide show
  1. package/CHANGELOG.md +175 -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 +113 -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-models.js +84 -0
  69. package/member-task-control.js +242 -0
  70. package/memory-backends.js +89 -0
  71. package/memory-embed.js +70 -0
  72. package/memory-hub.js +140 -0
  73. package/memory-openviking.js +101 -0
  74. package/memory-redact.js +34 -0
  75. package/memory-store.js +270 -0
  76. package/memory-sync-queue.js +28 -0
  77. package/model-policy.js +85 -0
  78. package/model-prefs.js +104 -0
  79. package/observability-api.js +76 -0
  80. package/observability-projection.js +116 -0
  81. package/onboarding.js +47 -0
  82. package/opencode.js +74 -0
  83. package/package.json +155 -4
  84. package/plan-gate.js +92 -0
  85. package/preset-guard.js +47 -0
  86. package/request-evidence.js +41 -0
  87. package/roles.js +105 -0
  88. package/roster-defaults.js +70 -0
  89. package/route-health.js +39 -0
  90. package/runtime-observability.js +78 -0
  91. package/runtime-view.js +21 -0
  92. package/scripts/memory-cli.mjs +138 -0
  93. package/search-engines.js +119 -0
  94. package/search.js +164 -0
  95. package/session-policy.js +59 -0
  96. package/session-rotation.js +206 -0
  97. package/settings-schema.js +40 -0
  98. package/shared/managed-config.js +84 -0
  99. package/single-model.js +44 -0
  100. package/skills/project-role-theme/SKILL.md +46 -0
  101. package/task-bridge.js +32 -0
  102. package/task-control-api.js +36 -0
  103. package/task-control-audit.js +60 -0
  104. package/team.js +101 -0
  105. package/teammate-context.js +120 -0
  106. package/teammate-wait.js +18 -0
  107. package/template.js +46 -0
  108. package/theme-plugin.js +172 -0
  109. package/theme.js +115 -0
  110. package/token-discipline.js +116 -0
  111. package/token-metrics.js +37 -0
  112. package/tool-names.js +10 -0
  113. package/tool-schema.js +23 -0
  114. package/transcribe.js +104 -0
  115. package/ui.js +113 -0
  116. package/usage.js +96 -0
  117. package/workflow.js +246 -0
package/workflow.js ADDED
@@ -0,0 +1,246 @@
1
+ import { m as modelText } from './i18n.js';
2
+ // Dispatch workflow: for each user request the Lead analyses the conversation, then either assigns
3
+ // registered roles or justifies handling it alone. A tool guard enforces the declared plan, so the
4
+ // Lead cannot silently do specialist work itself. All limits are project data (config.workflow).
5
+ import { fileLimitRules } from './file-limits.js';
6
+ import { tokenRuleText } from './token-discipline.js';
7
+ import { baseName } from './session-rotation.js';
8
+ import { arrayArg, planTasks } from './board-dispatch.js';
9
+ import { params } from './tool-schema.js';
10
+ import { accessSummary } from './access.js';
11
+
12
+ export const WORKFLOW_MODES = ['enforce', 'remind', 'off'];
13
+ const output = { schema: { type: 'string' }, render: (_args, value) => [{ type: 'text', text: value }] };
14
+
15
+ /** Classify one tool name for the Lead under the given workflow policy. */
16
+ export const FREE_TOOL = 'capability_free_task';
17
+ const FREE_TOOL_TEXT = {
18
+ none: modelText('workflow.608'),
19
+ read: modelText('workflow.609'),
20
+ write: modelText('workflow.610'),
21
+ };
22
+ const DECISION = { delegate: modelText('workflow.611'), free: modelText('workflow.612'), direct: modelText('workflow.613') };
23
+ const OWN_TOOLS = ['capability_dispatch_plan', 'capability_submit_plan', 'capability_review_plan', 'capability_task_budget', 'capability_member_override', 'capability_handoff', 'capability_rotate_member', 'capability_memory_search', 'capability_memory_write', 'capability_model_prefs'];
24
+ /** Hosts may expose tools as `mcp__<server>__<word>_<name>` (e.g. `brisk_spawn_teammate`); match on `<name>`. */
25
+ export function canonicalTool(name, workflow) {
26
+ const raw = String(name ?? '').replace(/^mcp__.+?__/, '');
27
+ const known = [FREE_TOOL, ...OWN_TOOLS, ...workflow.coordinationTools, ...workflow.analysisTools];
28
+ if (known.includes(raw)) return raw;
29
+ const tail = raw.slice(raw.indexOf('_') + 1);
30
+ return raw.includes('_') && known.includes(tail) ? tail : raw;
31
+ }
32
+ export function toolClass(name, workflow) {
33
+ name = canonicalTool(name, workflow);
34
+ if (name === FREE_TOOL) return 'free';
35
+ // The team's own planning tools always coordinate, whatever a project lists.
36
+ if (OWN_TOOLS.includes(name) || workflow.coordinationTools.includes(name)) return 'coordination';
37
+ if (workflow.analysisTools.includes(name)) return 'analysis';
38
+ return 'execution';
39
+ }
40
+
41
+ /**
42
+ * Pure decision for one Lead tool call; mutates `state` counters only when the call is allowed.
43
+ * Returns a denial reason string, or undefined to allow.
44
+ */
45
+ export function decide(state, exec, workflow) {
46
+ const kind = toolClass(exec.name, workflow);
47
+ const plan = state.plan;
48
+ const replan = modelText('workflow.614');
49
+ if (kind === 'coordination') {
50
+ if (canonicalTool(exec.name, workflow) !== 'spawn_teammate') return undefined;
51
+ const name = exec.arguments?.name;
52
+ if (!plan) return modelText('workflow.615');
53
+ if (plan.mode !== 'delegate' || !plan.assignments.some(a => a.role === name)) {
54
+ return modelText('workflow.616', { 0: name, 1: planSummary(plan), 2: replan });
55
+ }
56
+ return undefined;
57
+ }
58
+ if (!plan) {
59
+ if (kind === 'analysis' && state.analysisUsed < workflow.analysisBudget) { state.analysisUsed += 1; return undefined; }
60
+ const why = kind === 'analysis' ? modelText('workflow.617', { 0: workflow.analysisBudget }) : modelText('workflow.618', { 0: exec.name });
61
+ return modelText('workflow.619', { 0: why });
62
+ }
63
+ if (kind === 'free') {
64
+ if (plan.mode === 'free') return undefined;
65
+ return modelText('workflow.620', { 0: planSummary(plan), 1: replan });
66
+ }
67
+ if (kind === 'analysis') return undefined;
68
+ if (plan.mode === 'free') {
69
+ return modelText('workflow.621', { 0: FREE_TOOL, 1: exec.name, 2: replan });
70
+ }
71
+ if (plan.mode === 'delegate') {
72
+ return modelText('workflow.622', { 0: planSummary(plan), 1: exec.name, 2: replan });
73
+ }
74
+ // Only the user's answer on the confirmation card sets this; plan fields supplied by the model never waive the budget.
75
+ if (state.directConfirmed) return undefined;
76
+ if (state.directUsed < workflow.directBudget) { state.directUsed += 1; return undefined; }
77
+ return modelText('workflow.623', { 0: workflow.directBudget, 1: replan, 2: replan });
78
+ }
79
+
80
+ export function planSummary(plan) {
81
+ if (!plan) return modelText('workflow.624');
82
+ if (plan.mode === 'direct') return modelText('workflow.625', { 0: plan.reason });
83
+ if (plan.mode === 'free') return modelText('workflow.626', { 0: plan.reason });
84
+ return plan.assignments.map(a => `${a.displayName ?? a.role}(${a.role})`).join('、');
85
+ }
86
+
87
+ /** Validate a dispatch plan against the effective team configuration. */
88
+ export function validatePlan(args, effective) {
89
+ const text = (v, max, label) => {
90
+ if (typeof v !== 'string' || !v.trim() || v.length > max) throw new Error(modelText('workflow.627', { 0: label, 1: max }));
91
+ return v.trim();
92
+ };
93
+ const analysis = text(args.analysis, 4000, modelText('workflow.628'));
94
+ if (!['direct', 'delegate', 'free'].includes(args.mode)) throw new Error(modelText('workflow.629'));
95
+ const assignments = arrayArg(args.assignments) ?? [];
96
+ if (!Array.isArray(assignments) || assignments.length > 20) throw new Error(modelText('workflow.630'));
97
+ if (args.mode !== 'delegate') {
98
+ if (assignments.length) throw new Error(modelText('workflow.631', { 0: args.mode }));
99
+ const plan = { mode: args.mode, analysis, reason: text(args.reason, 500, modelText('workflow.632', { 0: args.mode })), assignments: [] };
100
+ // userRequested stays out of the plan: only the user's answer, held in workflow state, can lift the budget.
101
+ return plan;
102
+ }
103
+ if (!assignments.length) throw new Error(modelText('workflow.633'));
104
+ const seen = new Set();
105
+ const list = assignments.map((a, i) => {
106
+ const role = text(a?.role, 64, modelText('workflow.634', { 0: i + 1 }));
107
+ if (role === 'lead') throw new Error(modelText('workflow.635'));
108
+ const entry = effective.roles[role];
109
+ if (!entry) throw new Error(modelText('workflow.636', { 0: role, 1: Object.keys(effective.roles).filter(r => r !== 'lead').join('、') }));
110
+ if (seen.has(role)) throw new Error(modelText('workflow.637', { 0: role }));
111
+ seen.add(role);
112
+ if (a.complexity !== undefined && !['simple','medium','complex'].includes(a.complexity)) throw new Error(modelText('workflow.638'));
113
+ return { role, complexity: a.complexity ?? 'medium', displayName: entry.displayName, capability: entry.capability, task: text(a.task, 2000, modelText('workflow.639', { 0: role })) };
114
+ });
115
+ return { mode: 'delegate', analysis, ...(args.reason ? { reason: text(args.reason, 500, modelText('workflow.640')) } : {}), assignments: list };
116
+ }
117
+
118
+ import { ownsCapabilityTeam } from './preset-guard.js';
119
+ export function createWorkflow(ctx, team, { onDelegate, onAssignments, dispatchConfirm, rotation, board } = {}) {
120
+ const states = new WeakMap();
121
+ const state = agent => {
122
+ if (!states.has(agent)) states.set(agent, { plan: undefined, analysisUsed: 0, directUsed: 0 });
123
+ return states.get(agent);
124
+ };
125
+ const isLead = agent => ownsCapabilityTeam(ctx, agent) && ctx.agentTeams.tryMembership(agent)?.role === 'lead';
126
+ const effectiveOf = agent => {
127
+ const project = team.pinned(agent);
128
+ return project ? team.loadSync(project, agent).effective : team.base;
129
+ };
130
+ return {
131
+ state,
132
+ /** A new user request starts a new dispatch cycle; teammate reports keep the current plan. */
133
+ onClaimed(agent, message) {
134
+ if (message?.source?.kind !== 'user' || !isLead(agent)) return;
135
+ states.set(agent, { plan: undefined, analysisUsed: 0, directUsed: 0 });
136
+ const invalidation=onAssignments?.(agent, []);
137
+ void invalidation?.catch?.(() => {}); // a newer dispatch may supersede this invalidation
138
+ },
139
+ guard(exec) {
140
+ if (!isLead(exec.agent)) return undefined;
141
+ let effective;
142
+ try { effective = effectiveOf(exec.agent); }
143
+ catch (error) { return modelText('workflow.641', { 0: error.message }); }
144
+ const workflow = effective.workflow;
145
+ if (workflow.mode !== 'enforce') return undefined;
146
+ // A successor planned by capability_rotate_member stands in for its assigned role.
147
+ const name = exec.arguments?.name;
148
+ if (canonicalTool(exec.name, workflow) === 'spawn_teammate' && rotation?.plannedSuccessor(exec.agent.id, name)) {
149
+ const plan = state(exec.agent).plan;
150
+ if (plan?.mode === 'delegate' && plan.assignments.some(a => a.role === baseName(name))) return undefined;
151
+ }
152
+ return decide(state(exec.agent), exec, workflow);
153
+ },
154
+ /** Model-facing status line for the Lead's team context. */
155
+ status(agent, workflow) {
156
+ if (workflow.mode === 'off') return undefined;
157
+ const s = state(agent);
158
+ const head = workflow.mode === 'enforce' ? modelText('workflow.642') : modelText('workflow.643');
159
+ // Budgets only, no running counters: a counter changes every step and would resend the whole runtime
160
+ // context snapshot each time. The guard still enforces the limits and says how many were used.
161
+ const direct = s.plan?.mode !== 'direct' ? '' : s.directConfirmed ? modelText('workflow.644') : modelText('workflow.645', { 0: workflow.directBudget });
162
+ const plan = s.plan ? modelText('workflow.646', { 0: planSummary(s.plan), 1: direct })
163
+ : modelText('workflow.647', { 0: workflow.analysisBudget });
164
+ return modelText('workflow.648', { 0: head, 1: plan, 2: workflow.freeTools });
165
+ },
166
+ register() {
167
+ ctx.effect(() => ctx.tools.guard(exec => this.guard(exec)));
168
+ ctx.on('agent/inbox/claimed', ({ agent, message }) => this.onClaimed(agent, message));
169
+ ctx.effect(() => ctx.tools.register({ name: 'capability_dispatch_plan', output,
170
+ description: modelText('workflow.649'),
171
+ parameters: params({
172
+ analysis: { type: 'string', description: modelText('workflow.650') },
173
+ mode: { type: 'string', enum: ['delegate', 'free', 'direct'], description: modelText('workflow.651') },
174
+ reason: { type: 'string', description: modelText('workflow.652') },
175
+ userRequested: { type: 'boolean', description: modelText('workflow.653') },
176
+ assignments: { type: 'array', description: modelText('workflow.654'), items: params({
177
+ role: { type: 'string', description: modelText('workflow.655') },
178
+ complexity: { type: 'string', enum: ['simple','medium','complex'], description: modelText('workflow.656') },
179
+ task: { type: 'string', description: modelText('workflow.657') },
180
+ tasks: { type: 'array', description: modelText('workflow.658'), items: params({
181
+ key: { type: 'string', description: modelText('workflow.659') },
182
+ subject: { type: 'string', description: modelText('workflow.660') },
183
+ description: { type: 'string', description: modelText('workflow.661') },
184
+ write_scopes: { type: 'array', items: { type: 'string' }, description: modelText('workflow.662') },
185
+ blocked_by: { type: 'array', items: { type: 'string' }, description: modelText('workflow.663') },
186
+ }, ['subject']) },
187
+ }, ['role', 'task']) },
188
+ }, ['analysis', 'mode']),
189
+ async execute(args, exec) {
190
+ if (!isLead(exec.agent)) throw new Error(modelText('workflow.664'));
191
+ const { effective, policy } = await team.load(exec.agent);
192
+ const plan = validatePlan(args, effective);
193
+ // Board subtasks are validated before the user is asked, so a malformed split never reaches the card.
194
+ const rawTasks = plan.mode === 'delegate' ? Object.fromEntries((arrayArg(args.assignments) ?? []).filter(a => a?.tasks !== undefined).map(a => [String(a.role).trim(), a.tasks])) : {};
195
+ if (plan.mode === 'delegate') planTasks(plan.assignments, rawTasks);
196
+ // The user confirms roles and models first; a cancel throws, so no plan exists and nobody is dispatched.
197
+ const confirmation = plan.mode === 'delegate' && dispatchConfirm
198
+ ? await dispatchConfirm.confirm(exec, plan, effective, policy, effective.workflow.confirmDispatch) : null;
199
+ // Lifting the Lead's step limit is the user's call and lasts until their next message.
200
+ const cycle = state(exec.agent);
201
+ const directConfirmed = plan.mode === 'direct' && args.userRequested === true && effective.workflow.mode === 'enforce' && dispatchConfirm
202
+ ? await dispatchConfirm.confirmDirect(exec, plan) : false;
203
+ if (directConfirmed && state(exec.agent) !== cycle) throw new Error(modelText('workflow.665'));
204
+ const s = state(exec.agent);
205
+ s.plan = plan;
206
+ s.directConfirmed = directConfirmed;
207
+ if (plan.mode === 'delegate') onDelegate?.(ctx.agentTeams.membership(exec.agent), plan.assignments.map(a => a.role));
208
+ await onAssignments?.(exec.agent, plan.mode === 'delegate' ? plan.assignments : []);
209
+ // Structured result: the model reads the fields, the Web card renders them without parsing prose.
210
+ const result = { mode: plan.mode, decision: DECISION[plan.mode], analysis: plan.analysis, ...(plan.reason ? { reason: plan.reason } : {}) };
211
+ if (plan.mode === 'direct') {
212
+ Object.assign(result, directConfirmed
213
+ ? { directBudget: null, directUsed: s.directUsed, confirmed: { by: 'user', scope: 'turn' }, next: modelText('workflow.666') }
214
+ : { directBudget: effective.workflow.directBudget, directUsed: s.directUsed, next: modelText('workflow.667', { 0: effective.workflow.directBudget }) });
215
+ } else if (plan.mode === 'free') {
216
+ Object.assign(result, { freeTools: effective.workflow.freeTools,
217
+ next: modelText('workflow.668', { 0: FREE_TOOL_TEXT[effective.workflow.freeTools] }) });
218
+ } else {
219
+ result.assignments = plan.assignments.map(a => {
220
+ const c = effective.capabilities[a.capability];
221
+ const limits = accessSummary(c.access);
222
+ const route = confirmation?.routes.find(r => r.role === a.role);
223
+ const model = route?.primary ? `${route.primary.provider}/${route.primary.model}` : c.provider ? `${c.provider}/${c.model}` : modelText('workflow.669');
224
+ const fallbacks = (route?.fallbacks ?? c.fallbacks ?? []).slice(0, 3).map(f => `${f.provider}/${f.model}`);
225
+ return { role: a.role, complexity: a.complexity, estimatedSteps: {simple:15,medium:40,complex:80}[a.complexity], displayName: a.displayName, capability: a.capability, model, fallbacks, ...(limits ? { access: limits } : {}),
226
+ ...(c.planFirst ? { planFirst: modelText('workflow.670') } : {}), task: a.task };
227
+ });
228
+ result.next = modelText('workflow.671');
229
+ }
230
+ if (plan.mode !== 'free' && (effective.workflow.fileLimits || effective.workflow.tokens)) result.fileRules = [fileLimitRules(effective.workflow.fileLimits), tokenRuleText(effective.workflow.tokens)].filter(Boolean).join('\n');
231
+ if (plan.mode === 'delegate' && board) {
232
+ try {
233
+ const byRole = await board.create(exec.agent, plan, rawTasks);
234
+ if (byRole) {
235
+ for (const a of result.assignments) a.boardTasks = byRole[a.role] ?? [];
236
+ result.next = modelText('workflow.672');
237
+ }
238
+ } catch (error) { result.boardError = modelText('workflow.673', { 0: error.message }); }
239
+ }
240
+ if (confirmation?.asked) result.confirmed = { by: 'user', scope: confirmation.scope, changed: confirmation.routes.filter(r => r.changed).map(r => r.role) };
241
+ return JSON.stringify(result);
242
+ },
243
+ }));
244
+ },
245
+ };
246
+ }