dsh-plugin-t-expert 0.3.88 → 0.3.89

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.
@@ -1,147 +0,0 @@
1
- import { createUserMessage } from '@deepseek-ai/dsh-llm';
2
- import { parseProfileInvocation, resolveProfileTaskPlanning } from "./profiles.js";
3
- export const T_TEAM_COMMAND = 't';
4
- const PROFILE_COMMAND_PREFIX = `${T_TEAM_COMMAND}-`;
5
- const GESTURE = /^\/t(?=$|[\t\n\r ])/u;
6
- /**
7
- * Convert a configured profile key into a stable, closed-namespace command
8
- * suffix. Only lowercase ASCII letters, digits and dashes are representable;
9
- * this deliberately prevents accidental command aliases for ambiguous profile
10
- * names such as `foo bar`, `foo_bar`, or non-ASCII keys.
11
- */
12
- export function profileCommandName(profileName) {
13
- const normalized = profileName.trim().toLowerCase();
14
- if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/u.test(normalized))
15
- return undefined;
16
- return `${PROFILE_COMMAND_PREFIX}${normalized}`;
17
- }
18
- /** Resolve a profile command only when it maps uniquely to a live profile. */
19
- function profileForCommand(commandName, profiles) {
20
- const matches = Object.keys(profiles).filter((profileName) => profileCommandName(profileName) === commandName);
21
- return matches.length === 1 ? matches[0] : undefined;
22
- }
23
- /** Parse either the generic command or one generated profile alias. */
24
- function parseCommandText(text, profiles) {
25
- const trimmed = text.trimStart();
26
- if (GESTURE.test(trimmed))
27
- return parseProfileInvocation(trimmed.slice(T_TEAM_COMMAND.length + 1).trim());
28
- if (!trimmed.startsWith(`/${PROFILE_COMMAND_PREFIX}`))
29
- return undefined;
30
- const tokenEnd = trimmed.search(/[\t\n\r ]/u);
31
- const commandName = trimmed.slice(1, tokenEnd === -1 ? undefined : tokenEnd);
32
- const profile = profileForCommand(commandName, profiles);
33
- if (profile === undefined)
34
- return undefined;
35
- return { profile, goal: (tokenEnd === -1 ? '' : trimmed.slice(tokenEnd)).trim() };
36
- }
37
- export function invokedTTeamInvocation(messages, getProfiles = () => ({})) {
38
- for (let index = messages.length - 1; index >= 0; index -= 1) {
39
- const message = messages[index];
40
- if (message === undefined || message.source.kind !== 'user')
41
- continue;
42
- for (const block of message.content) {
43
- if (block.type !== 'text')
44
- continue;
45
- const invocation = parseCommandText(block.text, getProfiles());
46
- if (invocation !== undefined)
47
- return invocation;
48
- }
49
- }
50
- return undefined;
51
- }
52
- export function invokedTTeamGoal(messages) {
53
- return invokedTTeamInvocation(messages)?.goal;
54
- }
55
- export function buildActivationDirective(goal, profile, taskPlanning = 'seed') {
56
- const lines = [
57
- 'The user invoked a T Team slash command. Follow the T Team protocol already in your system instructions. Inspect existing team state with t_team_status when needed.',
58
- 'Respect the current team state. Continue an existing plan or team without recreating it. Only when no current team exists, call t_team_create with approval="required". Build the complete staged roster and DAG, then stop and ask the user to review the Web plan. Do not approve or start it in this same turn.',
59
- ];
60
- if (profile !== undefined) {
61
- lines.push(`Use profile="${profile}" when creating a new team.`);
62
- if (taskPlanning === 'captain') {
63
- // 编制授权:profile 是「这类目标通常涉及哪些角色」,不是「这次必须全员上场」。
64
- // 缺了这句,队长会把上面那句 "do not recreate members" 读成「roster 不可动」,转而给
65
- // 边缘角色硬造任务;而下面那句原文(idle members can run in parallel)更是在明示
66
- // 「给闲着的成员找活」—— 实测一份单文件 HTML 简历就是这样被派给 5 个人的。
67
- lines.push('This profile supplies the roster and guardrails. After create, do not recreate members.', 'The roster is a staffing list, not a mandate to field everyone: drop any role whose distinct output this goal does not need (t_team_edit_plan remove_member while staged), and trim BEFORE creating tasks — once a task references a member, removing that member means fixing references first. When unsure, keep the role. Keep at least two members (a producer plus an independent verifier). State in the plan whom you kept, whom you trimmed, and why.', 'Derive the smallest useful task graph from the goal while the team is staged; do not ask the user whether to split, merge, serialize, or parallelize.', 'An idle member is not a reason to invent work: add a task only when the goal itself needs that output, and say why. When work genuinely is independent, make it a separate ready task so it can run in parallel instead of serializing it. Add dependencies only for genuine prerequisites and later synthesis.');
68
- }
69
- else {
70
- lines.push('Do not recreate the same members or seed tasks manually.', 'You may still drop a role this goal does not need (t_team_edit_plan remove_member while the team is staged); state in the plan whom you kept, whom you trimmed, and why.');
71
- }
72
- }
73
- lines.push(goal === '' ? 'The goal was not given — ask the user what the team should accomplish.' : `Goal: ${goal}`);
74
- return lines.join('\n');
75
- }
76
- export function registerTTeamCommand(ctx, getProfiles = () => ({})) {
77
- ctx.effect(() => {
78
- const dispose = [];
79
- dispose.push(ctx.commands.register({
80
- name: T_TEAM_COMMAND,
81
- description: 'run a goal with a multi-agent team (you become the captain)',
82
- input: { hint: '[--profile <name>] <goal>' },
83
- handler(invocation) {
84
- let parsed;
85
- try {
86
- parsed = parseProfileInvocation(invocation.rawInput.trim());
87
- }
88
- catch (error) {
89
- return { kind: 'error', text: String(error) };
90
- }
91
- if (parsed.profile !== undefined && !Object.keys(getProfiles()).some(key => key.trim() === parsed.profile))
92
- return { kind: 'error', text: `未知的 T专家 小队「${parsed.profile}」` };
93
- if (parsed.profile === undefined && parsed.goal === '')
94
- return { kind: 'error', text: `Usage: /${T_TEAM_COMMAND} [--profile <name>] <goal>` };
95
- invocation.agent.followup(createUserMessage({ content: [{ type: 'text', text: `/${T_TEAM_COMMAND}${invocation.rawInput}` }], source: { kind: 'user' } }));
96
- return { kind: 'success', text: `T专家 小队已激活${parsed.profile === undefined ? '' : `(${parsed.profile})`} —— 队长会先组装团队。` };
97
- },
98
- }));
99
- for (const profileName of Object.keys(getProfiles())) {
100
- const commandName = profileCommandName(profileName);
101
- if (commandName === undefined)
102
- continue;
103
- dispose.push(ctx.commands.register({
104
- name: commandName,
105
- description: `run a goal with the T Team ${profileName} profile`,
106
- input: { hint: '<goal>' },
107
- handler(invocation) {
108
- const profile = profileForCommand(commandName, getProfiles());
109
- if (profile === undefined)
110
- return { kind: 'error', text: `T Team profile command "/${commandName}" is unavailable` };
111
- invocation.agent.followup(createUserMessage({ content: [{ type: 'text', text: `/${commandName}${invocation.rawInput}` }], source: { kind: 'user' } }));
112
- return { kind: 'success', text: `T专家 小队已激活(${profile})—— 队长会先组装团队。` };
113
- },
114
- }));
115
- }
116
- return () => {
117
- for (const unregister of dispose.reverse())
118
- unregister();
119
- };
120
- }, 't-team: slash commands');
121
- }
122
- export function installTTeamGestureBoundary(ctx, getProfiles = () => ({})) {
123
- ctx.on('agent/pre-step', async ({ messages, signal }, next) => {
124
- const decision = await next();
125
- if (decision.kind === 'reject')
126
- return decision;
127
- let invocation;
128
- try {
129
- invocation = invokedTTeamInvocation(messages, getProfiles);
130
- }
131
- catch (error) {
132
- return { kind: 'enter', messages: [...decision.messages, createUserMessage({ content: [{ type: 'text', text: `T专家 小队解析失败:${String(error)}` }], source: { kind: 't-team-command' } })] };
133
- }
134
- if (invocation === undefined)
135
- return decision;
136
- signal.throwIfAborted();
137
- const profiles = getProfiles();
138
- const matched = invocation.profile === undefined
139
- ? undefined
140
- : Object.entries(profiles).find(([key]) => key.trim() === invocation.profile);
141
- const known = invocation.profile === undefined || matched !== undefined;
142
- const text = !known
143
- ? `T专家 小队「${invocation.profile}」不存在。可用小队:${Object.keys(profiles).join('、') || '(无)'}。不要创建团队。`
144
- : buildActivationDirective(invocation.goal, invocation.profile, resolveProfileTaskPlanning(matched?.[1]));
145
- return { kind: 'enter', messages: [...decision.messages, createUserMessage({ content: [{ type: 'text', text }], source: { kind: 't-team-command', ...invocation.goal === '' ? {} : { goal: invocation.goal }, ...invocation.profile === undefined ? {} : { profile: invocation.profile } } })] };
146
- });
147
- }
@@ -1,12 +0,0 @@
1
- /**
2
- * T Team session event types — pure types only, zero imports.
3
- *
4
- * This file intentionally imports nothing: both the host program (the
5
- * emitter in `events.ts`) and the browser program (the Conversation Node
6
- * definition) must be able to load these types and the `SessionEventMap`
7
- * declaration merge without pulling in host-side `Context` augmentations
8
- * (dsh-session's index declares `Context.sessions: SessionStore`, which
9
- * collides with the browser runtime's `ISessions` under the same name).
10
- * @module t-team/event-types
11
- */
12
- export {};
@@ -1,60 +0,0 @@
1
- /**
2
- * Durable T Team session events and their emitter.
3
- *
4
- * Every team-state mutation appends one event to the captain's Session, so
5
- * the web client's Conversation Node mechanism can fold the tree view from
6
- * the session log deterministically (same mechanism as `tool-workflow`'s
7
- * `tool-workflow/*` record events). Events append to the captain's session
8
- * even when a member agent performed the mutation, so the captain's
9
- * conversation stream stays the single authoritative monitor surface.
10
- *
11
- * Types and the `SessionEventMap` merge live in `event-types.ts` (zero
12
- * imports) so the browser program can load them without host augmentations.
13
- * @module t-team/events
14
- */
15
- import * as dshSession from '@deepseek-ai/dsh-session';
16
- /** Event types already reported as unsupported, to avoid repetitive logs. */
17
- const skippedEventTypes = new Set();
18
- /**
19
- * Append one T Team event to a Session, containing failures (a broken
20
- * durable record must never break team tool execution).
21
- * @param ctx - the plugin context (for logging).
22
- * @param session - the session to record into (the captain's, normally).
23
- * @param type - the event type.
24
- * @param data - the event payload.
25
- */
26
- export function appendTeamEvent(ctx, session, type, data) {
27
- // Out-of-repo events are not in the harness's generated vocabulary today.
28
- // Mutating that ReadonlySet would make readability depend on which plugins
29
- // happen to be loaded. Until Session.append exposes the official
30
- // `ignorable: true` writer surface, omit these informational records unless
31
- // the running harness already recognizes them. Disk state remains the
32
- // authoritative source for the activity panel.
33
- const known = dshSession.KNOWN_SESSION_EVENT_TYPES;
34
- if (known?.has(type) !== true) {
35
- if (!skippedEventTypes.has(type)) {
36
- skippedEventTypes.add(type);
37
- ctx.logger.debug(`t-team: session event "${type}" omitted because this harness does not recognize it`);
38
- }
39
- return;
40
- }
41
- try {
42
- session.append(type, data);
43
- }
44
- catch (error) {
45
- ctx.logger.warn(`t-team: session record failed after ${type}: ${String(error)}`);
46
- }
47
- }
48
- /**
49
- * Resolve the captain's live Session for event recording. The captain agent
50
- * may be offline (its team outlives the session), in which case the caller's
51
- * own session is used as the fallback record target.
52
- * @param ctx - the plugin context (injects `agents`).
53
- * @param captainSessionId - the captain's durable session id.
54
- * @param fallback - the calling agent's session, used when the captain is not live.
55
- * @returns the session to record into.
56
- */
57
- export function captainSessionOf(ctx, captainSessionId, fallback) {
58
- const captain = ctx.agents.get(captainSessionId);
59
- return captain?.session ?? fallback;
60
- }