openteam 1.0.6 → 1.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openteam",
3
- "version": "1.0.6",
3
+ "version": "1.0.8",
4
4
  "description": "Agent-centric team collaboration framework",
5
5
  "type": "module",
6
6
  "bin": {
@@ -16,7 +16,7 @@ import {
16
16
  listAllInstances,
17
17
  } from '../foundation/state.js';
18
18
  import { detectMultiplexer, getSessionState, hasSession, findSessionsByPrefix, attachSession, startSession, killSession, isInsideMux } from '../foundation/terminal.js';
19
- import { buildWrapperCmd } from './daemon/panes.js';
19
+ import { buildWrapperCmd, buildWrapperOptions } from './daemon/panes.js';
20
20
 
21
21
  // ── 输出辅助 ──
22
22
 
@@ -156,13 +156,10 @@ export async function cmdStart(teamName, options) {
156
156
  const daemonCmd = `openteam daemon ${teamName} --port ${port} --dir "${projectDir}" --mux ${mux} --cli ${cliType}`;
157
157
 
158
158
  // zellij: 构建 wrapper 命令,由 team layout 的 stacked panes 自动调用
159
- const cliArgs = teamConfig.cli_config?.[cliType]?.args || [];
160
- const wrapperOptions = {
159
+ const wrapperOptions = buildWrapperOptions(teamConfig, {
161
160
  serverUrl: `http://127.0.0.1:${port}`,
162
161
  teamName, projectDir, cliType,
163
- agents: teamConfig.agents,
164
- cliArgs,
165
- };
162
+ });
166
163
  const agentCmds = mux === 'zellij' ? teamConfig.agents.map(agent => ({
167
164
  name: agent,
168
165
  cmd: buildWrapperCmd(agent, wrapperOptions, { mux, sessionName }),
@@ -14,7 +14,7 @@ import { DEFAULTS, getSessionName } from '../../foundation/constants.js';
14
14
  import { createLogger } from '../../foundation/logger.js';
15
15
  import { cleanMuxEnv } from '../../foundation/terminal.js';
16
16
  import { startServe, stopServe } from './serve.js';
17
- import { createAllAgentPanes, checkAndRespawn } from './panes.js';
17
+ import { createAllAgentPanes, checkAndRespawn, buildWrapperOptions } from './panes.js';
18
18
  import { createEmbeddedDashboard } from '../dashboard/index.js';
19
19
 
20
20
  const log = createLogger('daemon');
@@ -70,7 +70,10 @@ export async function runDaemon(teamName, projectDir, options = {}) {
70
70
  started: new Date().toISOString(),
71
71
  });
72
72
 
73
- // ── 2. 确保 agent/skill 软链接 ──
73
+ // ── 2. 读取 CLI 配置 ──
74
+ const keepDefaultSP = !!teamConfig.cli_config?.[cliType]?.keep_default_system_prompt;
75
+
76
+ // ── 3. 确保 agent/skill 软链接 ──
74
77
  const { ensureLinks } = await import('./links.js');
75
78
  const linkResult = ensureLinks({ teamName, projectDir, cliType, agents, skipAgentLinks: keepDefaultSP });
76
79
  if (!linkResult.ok) {
@@ -86,19 +89,10 @@ export async function runDaemon(teamName, projectDir, options = {}) {
86
89
  console.log(`已创建 ${linkResult.linked.length} 个链接`);
87
90
  }
88
91
 
89
- // ── 3. wrapper 环境配置 ──
90
- const cliConf = teamConfig.cli_config?.[cliType] || {};
91
- const cliArgs = cliConf.args || [];
92
- const keepDefaultSP = !!cliConf.keep_default_system_prompt;
93
- const wrapperOptions = {
94
- serverUrl: serve.url,
95
- teamName,
96
- projectDir,
97
- cliType,
98
- agents,
99
- cliArgs,
100
- keepDefaultSP,
101
- };
92
+ // ── 4. wrapper 环境配置 ──
93
+ const wrapperOptions = buildWrapperOptions(teamConfig, {
94
+ serverUrl: serve.url, teamName, projectDir, cliType,
95
+ });
102
96
 
103
97
  // ── 4. 创建 agent panes ──
104
98
  // zellij: layout 已在 startSession 时创建好 stacked agents
@@ -152,3 +152,27 @@ export function buildWrapperCmd(agent, wrapperOptions, muxInfo) {
152
152
 
153
153
  return `env ${envVars.join(' ')} ${wrapperBin}`;
154
154
  }
155
+
156
+ /**
157
+ * 从 teamConfig 构建 wrapperOptions — 唯一入口,消除重复构建
158
+ *
159
+ * @param {object} teamConfig - 团队配置
160
+ * @param {object} runtime - 运行时信息
161
+ * @param {string} runtime.serverUrl
162
+ * @param {string} runtime.teamName
163
+ * @param {string} runtime.projectDir
164
+ * @param {string} runtime.cliType
165
+ * @returns {object} wrapperOptions
166
+ */
167
+ export function buildWrapperOptions(teamConfig, { serverUrl, teamName, projectDir, cliType }) {
168
+ const cliConf = teamConfig.cli_config?.[cliType] || {};
169
+ return {
170
+ serverUrl,
171
+ teamName,
172
+ projectDir,
173
+ cliType,
174
+ agents: teamConfig.agents,
175
+ cliArgs: cliConf.args || [],
176
+ keepDefaultSP: !!cliConf.keep_default_system_prompt,
177
+ };
178
+ }
@@ -115,18 +115,24 @@ async function main() {
115
115
 
116
116
  // ── 5. 启动消息轮询 ──
117
117
  let polling = true;
118
+ let injecting = false; // 注入锁,防止轮询重入导致消息交叉
118
119
  const pollTimer = setInterval(async () => {
119
- if (!polling) return;
120
+ if (!polling || injecting) return;
120
121
  try {
121
122
  // 上报活动状态
122
123
  const active = (Date.now() - lastPtyOutput) < ACTIVE_THRESHOLD;
123
124
  heartbeat(serverUrl, agent, active).catch(() => {});
124
125
 
125
126
  const messages = await pullMessages(serverUrl, agent);
126
- for (const msg of messages) {
127
- injectMessage(ptyProcess, msg.message);
127
+ if (messages.length > 0) {
128
+ injecting = true;
129
+ for (const msg of messages) {
130
+ await injectMessage(ptyProcess, msg.message);
131
+ }
132
+ injecting = false;
128
133
  }
129
134
  } catch (err) {
135
+ injecting = false;
130
136
  log.warn('wrapper.poll.error', { error: err.message });
131
137
  }
132
138
  }, POLL_INTERVAL);
@@ -160,20 +166,30 @@ async function main() {
160
166
 
161
167
  // ── 消息注入 ──
162
168
 
169
+ const INJECT_ENTER_DELAY = 200; // 写入文本后等待 TUI 渲染再发 Enter
170
+ const INJECT_BETWEEN_DELAY = 300; // 两条消息之间的间隔,确保前一条已提交
171
+
163
172
  /**
164
173
  * 通过 PTY master 注入消息到 CLI
165
- * 使用 bracketed paste 模式,防止多行消息被逐行执行
174
+ * 返回 Promise,resolve 后才能注入下一条
166
175
  */
167
176
  function injectMessage(ptyProcess, message) {
168
- try {
169
- // 消息中的 \n 保持原样 — claude code 中 \n(0x0a) = 换行,\r(0x0d) = 提交
170
- ptyProcess.write(message);
171
- // 延迟发送 Enter 提交 — 确保 TUI 完成文本渲染
172
- setTimeout(() => ptyProcess.write('\r'), 100);
173
- log.info('wrapper.inject.ok', { preview: message.slice(0, 50) });
174
- } catch (err) {
175
- log.warn('wrapper.inject.failed', { error: err.message, preview: message.slice(0, 50) });
176
- }
177
+ return new Promise((resolve) => {
178
+ try {
179
+ // 消息中的 \n 保持原样 — claude code 中 \n(0x0a) = 换行,\r(0x0d) = 提交
180
+ ptyProcess.write(message);
181
+ // 延迟发送 Enter 提交 — 确保 TUI 完成文本渲染
182
+ setTimeout(() => {
183
+ ptyProcess.write('\r');
184
+ log.info('wrapper.inject.ok', { preview: message.slice(0, 50) });
185
+ // 再等一段时间让 TUI 处理提交,然后才允许下一条
186
+ setTimeout(resolve, INJECT_BETWEEN_DELAY);
187
+ }, INJECT_ENTER_DELAY);
188
+ } catch (err) {
189
+ log.warn('wrapper.inject.failed', { error: err.message, preview: message.slice(0, 50) });
190
+ resolve();
191
+ }
192
+ });
177
193
  }
178
194
 
179
195
  // ── HTTP 辅助函数 ──