nikou-cli 0.1.7 → 0.1.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 (3) hide show
  1. package/README.md +2 -0
  2. package/dist/index.js +85 -23
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -162,6 +162,8 @@ Hook 支持三种启动模式:
162
162
 
163
163
  主节点负责连接聊天平台、接收消息、维护 Worker 列表并分发任务。从节点负责连接主节点,收到任务后调用 Codex、Claude 或 Gemini 执行。
164
164
 
165
+ 群聊发送 `@机器人 绑定: <目录>` 时,主节点会先检查当前用户是否已经绑定该目录;已有绑定会直接返回结果,未绑定则只把路径校验任务发给该用户自己的在线 Worker。一个群可以保留多个用户、多个目录的 Agent 绑定,绑定指令不会广播给其他 Worker 抢占。
166
+
165
167
  ### 初始化配置
166
168
 
167
169
  ```bash
package/dist/index.js CHANGED
@@ -3041,7 +3041,10 @@ var TaskManager = class {
3041
3041
  }
3042
3042
  if (task.status !== "pending") {
3043
3043
  this.logger.info(
3044
- formatTraceLog(task.traceId, `\u8BA4\u9886\u88AB\u62D2\u7EDD\uFF1A\u4EFB\u52A1\u5DF2\u88AB\u8BA4\u9886 worker=${workerMeta.workerId}`)
3044
+ formatTraceLog(
3045
+ task.traceId,
3046
+ `\u8BA4\u9886\u88AB\u62D2\u7EDD\uFF1A\u4EFB\u52A1\u5DF2\u88AB\u8BA4\u9886 claimed_worker=${task.claimedWorkerId || "-"}, rejected_worker=${workerMeta.workerId}`
3047
+ )
3045
3048
  );
3046
3049
  return false;
3047
3050
  }
@@ -4028,6 +4031,15 @@ var ChatBindingRegistry = class {
4028
4031
  getByKey(chatId, bindingKey) {
4029
4032
  return this.bindings.get(normalize2(chatId))?.get(normalize2(bindingKey)) || null;
4030
4033
  }
4034
+ /** 按群、绑定用户和目录查找普通绑定,供绑定指令做幂等校验。 */
4035
+ getNormalBinding(chatId, ownerUserId, dirPath) {
4036
+ const owner = normalize2(ownerUserId);
4037
+ const dir = normalize2(dirPath);
4038
+ if (!owner || !dir) {
4039
+ return null;
4040
+ }
4041
+ return this.listByChat(chatId).find((binding) => binding.kind === "normal" && binding.ownerUserId === owner && binding.dirPath === dir) || null;
4042
+ }
4031
4043
  /** 返回全部绑定记录(扁平) */
4032
4044
  list() {
4033
4045
  const all = [];
@@ -5356,9 +5368,17 @@ var HookMasterCommand = class {
5356
5368
  const bindCmd = parseBindingCommand(text);
5357
5369
  if (bindCmd.matched) {
5358
5370
  this.logger.info(formatMasterTraceLog(messageId, `\u4E3B\u8282\u70B9\u547D\u4E2D\u7ED1\u5B9A\u6307\u4EE4: chat_id=${chatId}, target=${bindCmd.target}`));
5359
- if (!this.handleBindCommand(chatId, messageId, senderId, bindCmd.target)) {
5360
- return;
5361
- }
5371
+ await this.handleBindCommand({
5372
+ data,
5373
+ message,
5374
+ chatId,
5375
+ chatType,
5376
+ messageId,
5377
+ senderId,
5378
+ senderOpenId,
5379
+ target: bindCmd.target
5380
+ });
5381
+ return;
5362
5382
  }
5363
5383
  const stopCmd = parseStopCommand(text);
5364
5384
  if (stopCmd.matched) {
@@ -5378,8 +5398,7 @@ var HookMasterCommand = class {
5378
5398
  chatType,
5379
5399
  senderId,
5380
5400
  senderOpenId,
5381
- text,
5382
- isBindingCommand: bindCmd.matched
5401
+ text
5383
5402
  });
5384
5403
  }
5385
5404
  async dispatchFeishuTask(input) {
@@ -5409,10 +5428,6 @@ var HookMasterCommand = class {
5409
5428
  } else {
5410
5429
  this.logger.warn(formatMasterTraceLog(input.messageId, `\u5355\u804A\u4EFB\u52A1\u672A\u5339\u914D\u5230\u4ECE\u8282\u70B9: task_id=${task.taskId}, requester=${input.senderId || "-"}, chat_id=${input.chatId || "-"}`));
5411
5430
  }
5412
- } else if (input.isBindingCommand) {
5413
- this.wsServer.broadcast(buildTaskOffer(task, eventKey, input.data));
5414
- delivered = this.wsServer.getWorkerCount();
5415
- this.logger.info(formatMasterTraceLog(input.messageId, `\u7ED1\u5B9A\u6307\u4EE4\u5DF2\u5E7F\u64AD\u7ED9\u4ECE\u8282\u70B9\u5339\u914D\u76EE\u5F55: task_id=${task.taskId}, chat_id=${input.chatId || "-"}, delivered=${delivered}, message_id=${input.messageId}`));
5416
5431
  } else {
5417
5432
  await this.groupRouteDispatcher?.dispatch(task, input.text);
5418
5433
  return;
@@ -5433,16 +5448,54 @@ var HookMasterCommand = class {
5433
5448
  () => this.messageResponder?.replyNoWorker(task)
5434
5449
  );
5435
5450
  }
5436
- handleBindCommand(chatId, messageId, senderId, target) {
5451
+ async handleBindCommand(input) {
5452
+ const { chatId, messageId, senderId, target } = input;
5453
+ const replyInThread = resolveReplyInThread(input.message);
5437
5454
  if (this.bindAllowedUserIds.length > 0 && !this.bindAllowedUserIds.includes(senderId)) {
5438
- void this.messageResponder?.replyText(chatId, "\u7ED1\u5B9A\u5931\u8D25\uFF1A\u5F53\u524D\u7528\u6237\u4E0D\u5728\u7ED1\u5B9A\u767D\u540D\u5355", messageId, resolveReplyInThread({
5439
- chat_id: chatId,
5440
- message_id: messageId
5441
- }));
5442
- return false;
5455
+ await this.messageResponder?.replyText(chatId, "\u7ED1\u5B9A\u5931\u8D25\uFF1A\u5F53\u524D\u7528\u6237\u4E0D\u5728\u7ED1\u5B9A\u767D\u540D\u5355", messageId, replyInThread);
5456
+ return;
5443
5457
  }
5444
- this.logger.info(formatMasterTraceLog(messageId, `\u7ED1\u5B9A\u6307\u4EE4\u6821\u9A8C\u901A\u8FC7\uFF0C\u7EE7\u7EED\u4E0B\u53D1\u5230\u4ECE\u8282\u70B9: chat_id=${chatId}, target=${target}`));
5445
- return true;
5458
+ if (!target) {
5459
+ await this.messageResponder?.replyText(chatId, "\u7ED1\u5B9A\u5931\u8D25\uFF1A\u8BF7\u6307\u5B9A\u7ED1\u5B9A\u8DEF\u5F84", messageId, replyInThread);
5460
+ return;
5461
+ }
5462
+ const existing = this.chatBindingRegistry.getNormalBinding(chatId, senderId, target);
5463
+ if (existing) {
5464
+ this.logger.info(formatMasterTraceLog(messageId, `\u4E3B\u8282\u70B9\u547D\u4E2D\u5DF2\u6709\u7FA4\u804A\u7ED1\u5B9A: chat_id=${chatId}, worker=${existing.workerId}, owner_user_id=${senderId}, dir=${target}`));
5465
+ await this.messageResponder?.replyText(chatId, `\u5DF2\u7ED1\u5B9A\u5230: ${target}`, messageId, replyInThread);
5466
+ return;
5467
+ }
5468
+ if (!this.taskManager || !this.wsServer) {
5469
+ return;
5470
+ }
5471
+ const workerId = this.workerRegistry.getWorkerIdByOwner(senderId);
5472
+ if (!workerId) {
5473
+ this.logger.warn(formatMasterTraceLog(messageId, `\u7ED1\u5B9A\u5931\u8D25\uFF0C\u672A\u627E\u5230\u53D1\u8D77\u4EBA\u7684\u5728\u7EBF\u4ECE\u8282\u70B9: chat_id=${chatId}, owner_user_id=${senderId}, dir=${target}`));
5474
+ await this.messageResponder?.replyText(chatId, "\u7ED1\u5B9A\u5931\u8D25\uFF1A\u672A\u627E\u5230\u4F60\u7684\u5728\u7EBF\u4ECE\u8282\u70B9\uFF0C\u8BF7\u5148\u542F\u52A8 ai-hook", messageId, replyInThread);
5475
+ return;
5476
+ }
5477
+ const eventKey = "im.message.receive_v1";
5478
+ const task = this.taskManager.createTaskRecord(input.data, messageId, eventKey, {
5479
+ chatId,
5480
+ chatType: input.chatType,
5481
+ requesterUserId: senderId,
5482
+ requesterOpenId: input.senderOpenId,
5483
+ responseMode: "card",
5484
+ routeMode: "direct_group",
5485
+ replyInThread
5486
+ });
5487
+ task.targetWorkerId = workerId;
5488
+ const delivered = this.wsServer.sendTo(workerId, buildTaskOffer(task, eventKey, input.data));
5489
+ if (!delivered) {
5490
+ this.taskManager.cleanupTask(task.taskId);
5491
+ await this.messageResponder?.replyText(chatId, "\u7ED1\u5B9A\u5931\u8D25\uFF1A\u4F60\u7684\u4ECE\u8282\u70B9\u5F53\u524D\u4E0D\u53EF\u7528\uFF0C\u8BF7\u91CD\u65B0\u542F\u52A8 ai-hook", messageId, replyInThread);
5492
+ return;
5493
+ }
5494
+ this.logger.info(formatMasterTraceLog(messageId, `\u7ED1\u5B9A\u6307\u4EE4\u5DF2\u5B9A\u5411\u4E0B\u53D1: task_id=${task.taskId}, chat_id=${chatId}, owner_user_id=${senderId}, worker=${workerId}, dir=${target}`));
5495
+ this.taskManager.scheduleClaimTimeout(
5496
+ task.taskId,
5497
+ () => this.messageResponder?.replyText(chatId, "\u7ED1\u5B9A\u5931\u8D25\uFF1A\u4ECE\u8282\u70B9\u54CD\u5E94\u8D85\u65F6\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5", messageId, replyInThread)
5498
+ );
5446
5499
  }
5447
5500
  async handleBotMenuEvent(data) {
5448
5501
  const event = data?.event || data;
@@ -8329,7 +8382,6 @@ ${question}` : question;
8329
8382
  if (!fs14.existsSync(bindTarget)) {
8330
8383
  return { ...baseTask, canHandle: true, type: "quick_result", quickResult: `\u8DEF\u5F84\u4E0D\u5B58\u5728: ${bindTarget}` };
8331
8384
  }
8332
- this.stateManager.rebindChatToDir(message.chatId, bindTarget);
8333
8385
  const bindAt = (/* @__PURE__ */ new Date()).toISOString();
8334
8386
  const pathDisplay = resolveCardPathDisplay(bindTarget);
8335
8387
  this.logger.info(formatTraceLog(baseTask.traceId, `\u7ED1\u5B9A\u6210\u529F: chatId=${message.chatId} -> ${bindTarget}`));
@@ -9956,6 +10008,7 @@ var SlaveRelayClient = class {
9956
10008
  if (!chatId || !dirPath) {
9957
10009
  return;
9958
10010
  }
10011
+ this.stateManager.rebindChatToDir(chatId, dirPath);
9959
10012
  this.send({
9960
10013
  type: "binding_update",
9961
10014
  worker_id: this.workerId,
@@ -14011,6 +14064,7 @@ import { execFile, spawn as spawn4 } from "child_process";
14011
14064
  import { createRequire } from "module";
14012
14065
  import { promisify } from "util";
14013
14066
  var execFileAsync = promisify(execFile);
14067
+ var MISSING_CLI_AUTH_SECRET_ERROR = "\u672A\u914D\u7F6E CLI_AUTH_SECRET\uFF0C\u65E0\u6CD5\u62C9\u53D6 Nacos \u540C\u6B65\u8BA1\u5212";
14014
14068
  var NacosSubscriptionSyncCoordinator = class {
14015
14069
  logger;
14016
14070
  workerId;
@@ -14019,12 +14073,15 @@ var NacosSubscriptionSyncCoordinator = class {
14019
14073
  daemon = null;
14020
14074
  running = false;
14021
14075
  stopped = false;
14076
+ missingCliAuthSecretWarned = false;
14022
14077
  status;
14078
+ resolveAuthRuntimeConfig;
14023
14079
  statePath = path26.join(os16.homedir(), ".nikou-block", "worker", "nacos-subscriptions.json");
14024
14080
  constructor(options) {
14025
14081
  this.logger = options.logger;
14026
14082
  this.workerId = options.workerId;
14027
14083
  this.config = options.config;
14084
+ this.resolveAuthRuntimeConfig = options.resolveAuthRuntimeConfig || resolveCliAuthRuntimeConfig;
14028
14085
  this.status = {
14029
14086
  enabled: this.config.enabled,
14030
14087
  configured: false,
@@ -14092,15 +14149,20 @@ var NacosSubscriptionSyncCoordinator = class {
14092
14149
  } catch (error) {
14093
14150
  const message = error instanceof Error ? error.message : String(error);
14094
14151
  this.status.lastError = message;
14095
- this.logger.warn(`Nacos Skill \u8BA2\u9605\u540C\u6B65\u5931\u8D25: ${message}`);
14152
+ if (message !== MISSING_CLI_AUTH_SECRET_ERROR || !this.missingCliAuthSecretWarned) {
14153
+ this.logger.warn(`Nacos Skill \u8BA2\u9605\u540C\u6B65\u5931\u8D25: ${message}`);
14154
+ }
14155
+ if (message === MISSING_CLI_AUTH_SECRET_ERROR) {
14156
+ this.missingCliAuthSecretWarned = true;
14157
+ }
14096
14158
  } finally {
14097
14159
  this.running = false;
14098
14160
  }
14099
14161
  }
14100
14162
  async fetchPlan() {
14101
- const runtime = resolveCliAuthRuntimeConfig();
14163
+ const runtime = this.resolveAuthRuntimeConfig();
14102
14164
  if (!runtime.cliAuthSecret) {
14103
- throw new Error("\u672A\u914D\u7F6E CLI_AUTH_SECRET\uFF0C\u65E0\u6CD5\u62C9\u53D6 Nacos \u540C\u6B65\u8BA1\u5212");
14165
+ throw new Error(MISSING_CLI_AUTH_SECRET_ERROR);
14104
14166
  }
14105
14167
  const response = await fetch(`${runtime.apiUrl}/worker-skills/nacos-sync/plan`, {
14106
14168
  method: "POST",
@@ -14115,7 +14177,7 @@ var NacosSubscriptionSyncCoordinator = class {
14115
14177
  }
14116
14178
  async report(results) {
14117
14179
  if (!results.length) return;
14118
- const runtime = resolveCliAuthRuntimeConfig();
14180
+ const runtime = this.resolveAuthRuntimeConfig();
14119
14181
  const response = await fetch(`${runtime.apiUrl}/worker-skills/nacos-sync/report`, {
14120
14182
  method: "POST",
14121
14183
  headers: { "Content-Type": "application/json" },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nikou-cli",
3
- "version": "0.1.7",
3
+ "version": "0.1.8",
4
4
  "description": "Install Agent Skills and MCP servers, and run distributed AI hook workers.",
5
5
  "type": "module",
6
6
  "bin": {