handsoff 0.1.2-beta.2 → 0.1.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.
- package/dist/gateway/process.js +193 -69
- package/dist/gateway/process.js.map +1 -1
- package/package.json +1 -1
package/dist/gateway/process.js
CHANGED
|
@@ -167,7 +167,7 @@ function buildPermissionCard(input) {
|
|
|
167
167
|
behaviors: [
|
|
168
168
|
{
|
|
169
169
|
type: "callback",
|
|
170
|
-
value: { action: btn.value }
|
|
170
|
+
value: { action: btn.value, requestId: input.requestId }
|
|
171
171
|
}
|
|
172
172
|
]
|
|
173
173
|
};
|
|
@@ -253,13 +253,14 @@ function buildQuestionCard(input) {
|
|
|
253
253
|
{
|
|
254
254
|
tag: "button",
|
|
255
255
|
text: { tag: "plain_text", content: item.label },
|
|
256
|
+
hover_tips: { tag: "plain_text", content: item.label },
|
|
256
257
|
type: "primary",
|
|
257
258
|
width: "default",
|
|
258
259
|
size: "small",
|
|
259
260
|
behaviors: [
|
|
260
261
|
{
|
|
261
262
|
type: "callback",
|
|
262
|
-
value: { action: "select", optionId: item.id }
|
|
263
|
+
value: { action: "select", optionId: item.id, requestId: input.requestId }
|
|
263
264
|
}
|
|
264
265
|
]
|
|
265
266
|
}
|
|
@@ -289,7 +290,7 @@ function buildQuestionCard(input) {
|
|
|
289
290
|
behaviors: [
|
|
290
291
|
{
|
|
291
292
|
type: "callback",
|
|
292
|
-
value: { action: "check", optionId: item.id }
|
|
293
|
+
value: { action: "check", optionId: item.id, requestId: input.requestId }
|
|
293
294
|
}
|
|
294
295
|
]
|
|
295
296
|
}));
|
|
@@ -325,7 +326,7 @@ function buildQuestionCard(input) {
|
|
|
325
326
|
behaviors: [
|
|
326
327
|
{
|
|
327
328
|
type: "callback",
|
|
328
|
-
value: { action: "submit" }
|
|
329
|
+
value: { action: "submit", requestId: input.requestId }
|
|
329
330
|
}
|
|
330
331
|
]
|
|
331
332
|
}
|
|
@@ -698,7 +699,7 @@ var AgentAdapterRegistry = class _AgentAdapterRegistry {
|
|
|
698
699
|
// src/core/session/Session.ts
|
|
699
700
|
init_esm_shims();
|
|
700
701
|
var Session = class {
|
|
701
|
-
|
|
702
|
+
_id;
|
|
702
703
|
agent;
|
|
703
704
|
token;
|
|
704
705
|
metadata;
|
|
@@ -711,7 +712,7 @@ var Session = class {
|
|
|
711
712
|
permissionMode;
|
|
712
713
|
slashCommands;
|
|
713
714
|
constructor(options) {
|
|
714
|
-
this.
|
|
715
|
+
this._id = options.id;
|
|
715
716
|
this.agent = options.adapter;
|
|
716
717
|
this.token = options.token;
|
|
717
718
|
this.metadata = options.metadata ?? {};
|
|
@@ -720,6 +721,12 @@ var Session = class {
|
|
|
720
721
|
this._updatedAt = this.createdAt;
|
|
721
722
|
this.cwd = options.cwd;
|
|
722
723
|
}
|
|
724
|
+
get id() {
|
|
725
|
+
return this._id;
|
|
726
|
+
}
|
|
727
|
+
setId(newId) {
|
|
728
|
+
this._id = newId;
|
|
729
|
+
}
|
|
723
730
|
get agentType() {
|
|
724
731
|
return this.agent.agentType;
|
|
725
732
|
}
|
|
@@ -841,6 +848,22 @@ var SessionManager = class {
|
|
|
841
848
|
this.logger.debug({ sessionId, ...info }, "[SessionManager] Session info updated");
|
|
842
849
|
return true;
|
|
843
850
|
}
|
|
851
|
+
renameSessionId(oldId, newId) {
|
|
852
|
+
const session = this.sessions.get(oldId);
|
|
853
|
+
if (!session) {
|
|
854
|
+
this.logger.warn({ oldId, newId }, "[SessionManager] renameSessionId: session not found");
|
|
855
|
+
return false;
|
|
856
|
+
}
|
|
857
|
+
if (this.sessions.has(newId)) {
|
|
858
|
+
this.logger.warn({ oldId, newId }, "[SessionManager] renameSessionId: newId already exists");
|
|
859
|
+
return false;
|
|
860
|
+
}
|
|
861
|
+
session.setId(newId);
|
|
862
|
+
this.sessions.delete(oldId);
|
|
863
|
+
this.sessions.set(newId, session);
|
|
864
|
+
this.logger.info({ oldId, newId }, "[SessionManager] Session renamed");
|
|
865
|
+
return true;
|
|
866
|
+
}
|
|
844
867
|
};
|
|
845
868
|
|
|
846
869
|
// src/gateway/events.ts
|
|
@@ -3073,6 +3096,9 @@ var Gateway = class {
|
|
|
3073
3096
|
updateSessionInfo(sessionId, info) {
|
|
3074
3097
|
this.sessionManager.updateSessionInfo(sessionId, info);
|
|
3075
3098
|
}
|
|
3099
|
+
renameSessionId(oldId, newId) {
|
|
3100
|
+
return this.sessionManager.renameSessionId(oldId, newId);
|
|
3101
|
+
}
|
|
3076
3102
|
};
|
|
3077
3103
|
|
|
3078
3104
|
// src/gateway/publisher.ts
|
|
@@ -3336,13 +3362,21 @@ var BaseAgentAdapter = class {
|
|
|
3336
3362
|
this.bus.onInbound((msg) => this.handleInboundMessage(msg))
|
|
3337
3363
|
);
|
|
3338
3364
|
}
|
|
3365
|
+
/**
|
|
3366
|
+
* Resolve session ID from inbound message. Subclasses may override to
|
|
3367
|
+
* return a stable session ID based on transport-level identity (e.g.
|
|
3368
|
+
* Claude SDK session_id).
|
|
3369
|
+
*/
|
|
3370
|
+
resolveSessionId(_message) {
|
|
3371
|
+
return void 0;
|
|
3372
|
+
}
|
|
3339
3373
|
async handleInboundMessage(message) {
|
|
3340
3374
|
const text = message.text.trim();
|
|
3341
3375
|
this.logger.debug({ channel: message.channel, chatId: message.chatId, targetAgent: message.targetAgent }, "Inbound message received");
|
|
3342
3376
|
if (message.systemCommand) return;
|
|
3343
3377
|
if (message.targetAgent !== this.agentType) return;
|
|
3344
3378
|
const parsed = parseInboundCommand(text, this.prefix);
|
|
3345
|
-
const sessionId = `${this.agentType}-${message.
|
|
3379
|
+
const sessionId = this.resolveSessionId(message) ?? `${this.agentType}-${message.sender.id}`;
|
|
3346
3380
|
if (!parsed) {
|
|
3347
3381
|
if (!this.supportsInboundPrompt) {
|
|
3348
3382
|
this.logger.debug({ sessionId, textPreview: text.slice(0, 60) }, "Inbound prompt ignored: adapter does not support user input");
|
|
@@ -3794,6 +3828,19 @@ var ClaudeHookAgentAdapter = class extends BaseAgentAdapter {
|
|
|
3794
3828
|
payload: enrichedPayload
|
|
3795
3829
|
};
|
|
3796
3830
|
}
|
|
3831
|
+
/**
|
|
3832
|
+
* Handle permission/question requests from Claude hook events.
|
|
3833
|
+
*
|
|
3834
|
+
* Called for both PreToolUse (regular tools) and PermissionRequest events.
|
|
3835
|
+
* When toolName === 'AskUserQuestion', the request type is 'question' instead
|
|
3836
|
+
* of 'permission', which causes channels to render question cards (single/multi
|
|
3837
|
+
* select) instead of permission cards (Allow/Deny).
|
|
3838
|
+
*
|
|
3839
|
+
* evaluateInteraction blocks until the user responds via a channel. The response
|
|
3840
|
+
* is returned to Claude as part of the HTTP hook response body.
|
|
3841
|
+
*
|
|
3842
|
+
* See file header for full flow diagrams.
|
|
3843
|
+
*/
|
|
3797
3844
|
async processPermissionRequest(eventType, payload, requestId) {
|
|
3798
3845
|
const sessionId = typeof payload.session_id === "string" ? payload.session_id : void 0;
|
|
3799
3846
|
const toolName = typeof payload.tool_name === "string" ? payload.tool_name : "unknown";
|
|
@@ -4580,6 +4627,8 @@ var ClaudeRemoteAgentAdapter = class _ClaudeRemoteAgentAdapter extends BaseAgent
|
|
|
4580
4627
|
defaultModel;
|
|
4581
4628
|
defaultPermissionMode;
|
|
4582
4629
|
static VALID_PERMISSION_MODES = ["default", "acceptEdits", "bypassPermissions", "plan"];
|
|
4630
|
+
/** Maps sender.id → real Claude session_id for stable multi-turn sessions */
|
|
4631
|
+
senderSessionMap = /* @__PURE__ */ new Map();
|
|
4583
4632
|
constructor(options) {
|
|
4584
4633
|
super({ bus: options.bus, bridge: options.bridge, logger: options.logger });
|
|
4585
4634
|
this.token = options.token;
|
|
@@ -4630,6 +4679,13 @@ var ClaudeRemoteAgentAdapter = class _ClaudeRemoteAgentAdapter extends BaseAgent
|
|
|
4630
4679
|
this.logger.info("Claude session scanner registered");
|
|
4631
4680
|
}
|
|
4632
4681
|
}
|
|
4682
|
+
/**
|
|
4683
|
+
* Return the real Claude session_id if this sender already has an active session.
|
|
4684
|
+
* This makes multi-turn conversations stable across turns.
|
|
4685
|
+
*/
|
|
4686
|
+
resolveSessionId(message) {
|
|
4687
|
+
return this.senderSessionMap.get(message.sender.id);
|
|
4688
|
+
}
|
|
4633
4689
|
async createTransportState(opts) {
|
|
4634
4690
|
const sessionId = opts.sessionId ?? `claude-${randomUUID2().slice(0, 8)}`;
|
|
4635
4691
|
const chatId = opts.chatId ?? sessionId;
|
|
@@ -4664,7 +4720,8 @@ var ClaudeRemoteAgentAdapter = class _ClaudeRemoteAgentAdapter extends BaseAgent
|
|
|
4664
4720
|
chatId,
|
|
4665
4721
|
channel,
|
|
4666
4722
|
cwd,
|
|
4667
|
-
permissionMode: this.defaultPermissionMode
|
|
4723
|
+
permissionMode: this.defaultPermissionMode,
|
|
4724
|
+
senderId: opts.metadata?.senderId
|
|
4668
4725
|
};
|
|
4669
4726
|
this.transports.set(sessionId, state);
|
|
4670
4727
|
this.emitEvent(EventNormalizer.sessionStart(sessionId, this.agentType, this.transportType, {
|
|
@@ -4673,7 +4730,7 @@ var ClaudeRemoteAgentAdapter = class _ClaudeRemoteAgentAdapter extends BaseAgent
|
|
|
4673
4730
|
cwd,
|
|
4674
4731
|
...opts.metadata
|
|
4675
4732
|
}));
|
|
4676
|
-
this.consumeQueryMessages(q,
|
|
4733
|
+
this.consumeQueryMessages(q, state).catch((err) => {
|
|
4677
4734
|
this.logger.error({ sessionId, error: err }, "Query consumer error");
|
|
4678
4735
|
});
|
|
4679
4736
|
this.logger.info({ sessionId }, "Claude remote transport state created");
|
|
@@ -4687,6 +4744,9 @@ var ClaudeRemoteAgentAdapter = class _ClaudeRemoteAgentAdapter extends BaseAgent
|
|
|
4687
4744
|
remoteState.query.kill("SIGTERM");
|
|
4688
4745
|
remoteState.aggregator.reset();
|
|
4689
4746
|
this.transports.delete(sessionId);
|
|
4747
|
+
if (remoteState.senderId) {
|
|
4748
|
+
this.senderSessionMap.delete(remoteState.senderId);
|
|
4749
|
+
}
|
|
4690
4750
|
this.emitEvent(EventNormalizer.sessionEnd(sessionId, this.agentType, this.transportType, "close"));
|
|
4691
4751
|
}
|
|
4692
4752
|
async sendCommand(state, command) {
|
|
@@ -4728,13 +4788,13 @@ var ClaudeRemoteAgentAdapter = class _ClaudeRemoteAgentAdapter extends BaseAgent
|
|
|
4728
4788
|
}
|
|
4729
4789
|
}
|
|
4730
4790
|
// === Internal ===
|
|
4731
|
-
async consumeQueryMessages(q,
|
|
4791
|
+
async consumeQueryMessages(q, state) {
|
|
4732
4792
|
try {
|
|
4733
4793
|
for await (const msg of q) {
|
|
4734
|
-
await this.handleSdkMessage(msg,
|
|
4794
|
+
await this.handleSdkMessage(msg, state);
|
|
4735
4795
|
}
|
|
4736
4796
|
} catch (err) {
|
|
4737
|
-
this.logger.error({ sessionId, error: err }, "Claude remote stream ended with error");
|
|
4797
|
+
this.logger.error({ sessionId: state.sessionId, error: err }, "Claude remote stream ended with error");
|
|
4738
4798
|
this.bus.publishOutbound({
|
|
4739
4799
|
channel: state.channel,
|
|
4740
4800
|
chatId: state.chatId,
|
|
@@ -4743,21 +4803,35 @@ var ClaudeRemoteAgentAdapter = class _ClaudeRemoteAgentAdapter extends BaseAgent
|
|
|
4743
4803
|
} finally {
|
|
4744
4804
|
state.aggregator.reset();
|
|
4745
4805
|
state.turnInProgress = false;
|
|
4746
|
-
this.transports.delete(sessionId);
|
|
4747
|
-
this.emitEvent(EventNormalizer.sessionEnd(sessionId, this.agentType, this.transportType, "stream_ended"));
|
|
4806
|
+
this.transports.delete(state.sessionId);
|
|
4807
|
+
this.emitEvent(EventNormalizer.sessionEnd(state.sessionId, this.agentType, this.transportType, "stream_ended"));
|
|
4748
4808
|
}
|
|
4749
4809
|
}
|
|
4750
|
-
async handleSdkMessage(msg,
|
|
4810
|
+
async handleSdkMessage(msg, state) {
|
|
4811
|
+
const sessionId = state.sessionId;
|
|
4751
4812
|
switch (msg.type) {
|
|
4752
4813
|
case "system": {
|
|
4753
4814
|
const system = msg;
|
|
4754
4815
|
if (system.subtype === "init" && system.session_id) {
|
|
4755
|
-
|
|
4756
|
-
this.
|
|
4816
|
+
const claudeSessionId = system.session_id;
|
|
4817
|
+
this.logger.info({ sessionId, claudeSessionId }, "Claude session initialized");
|
|
4818
|
+
if (claudeSessionId !== sessionId) {
|
|
4819
|
+
const renamed = this.bridge.renameSessionId(sessionId, claudeSessionId);
|
|
4820
|
+
if (renamed) {
|
|
4821
|
+
this.transports.delete(sessionId);
|
|
4822
|
+
state.sessionId = claudeSessionId;
|
|
4823
|
+
this.transports.set(claudeSessionId, state);
|
|
4824
|
+
this.logger.info({ oldId: sessionId, newId: claudeSessionId }, "Session renamed to Claude session_id");
|
|
4825
|
+
}
|
|
4826
|
+
}
|
|
4827
|
+
this.bridge.updateSessionInfo(claudeSessionId, {
|
|
4757
4828
|
model: system.model,
|
|
4758
4829
|
slashCommands: system.slash_commands,
|
|
4759
4830
|
permissionMode: state.permissionMode
|
|
4760
4831
|
});
|
|
4832
|
+
if (state.senderId) {
|
|
4833
|
+
this.senderSessionMap.set(state.senderId, claudeSessionId);
|
|
4834
|
+
}
|
|
4761
4835
|
}
|
|
4762
4836
|
break;
|
|
4763
4837
|
}
|
|
@@ -4810,7 +4884,9 @@ var ClaudeRemoteAgentAdapter = class _ClaudeRemoteAgentAdapter extends BaseAgent
|
|
|
4810
4884
|
durationMs: result.duration_ms,
|
|
4811
4885
|
usage: result.usage ? {
|
|
4812
4886
|
inputTokens: result.usage.input_tokens,
|
|
4813
|
-
outputTokens: result.usage.output_tokens
|
|
4887
|
+
outputTokens: result.usage.output_tokens,
|
|
4888
|
+
cacheReadInputTokens: result.usage.cache_read_input_tokens,
|
|
4889
|
+
cacheCreationInputTokens: result.usage.cache_creation_input_tokens
|
|
4814
4890
|
} : void 0
|
|
4815
4891
|
})
|
|
4816
4892
|
);
|
|
@@ -4829,6 +4905,18 @@ var ClaudeRemoteAgentAdapter = class _ClaudeRemoteAgentAdapter extends BaseAgent
|
|
|
4829
4905
|
}
|
|
4830
4906
|
}
|
|
4831
4907
|
}
|
|
4908
|
+
/**
|
|
4909
|
+
* Handle tool permission requests from the Claude SDK.
|
|
4910
|
+
*
|
|
4911
|
+
* The SDK calls this callback when Claude wants to run a tool. We block
|
|
4912
|
+
* the Promise until the user responds via a channel (Feishu/Telegram).
|
|
4913
|
+
*
|
|
4914
|
+
* Resolution values:
|
|
4915
|
+
* - allow → { behavior: 'allow', updatedInput? }
|
|
4916
|
+
* - deny → { behavior: 'deny', message }
|
|
4917
|
+
*
|
|
4918
|
+
* See file header for full flow diagram.
|
|
4919
|
+
*/
|
|
4832
4920
|
async handleCanCallTool(sessionId, toolName, toolInput, signal) {
|
|
4833
4921
|
const state = this.transports.get(sessionId);
|
|
4834
4922
|
if (!state) {
|
|
@@ -4873,6 +4961,21 @@ var ClaudeRemoteAgentAdapter = class _ClaudeRemoteAgentAdapter extends BaseAgent
|
|
|
4873
4961
|
});
|
|
4874
4962
|
});
|
|
4875
4963
|
}
|
|
4964
|
+
/**
|
|
4965
|
+
* Handle question requests from the Claude SDK (AskUserQuestion tool).
|
|
4966
|
+
*
|
|
4967
|
+
* The SDK calls this callback when Claude runs AskUserQuestion. We block
|
|
4968
|
+
* the Promise until the user selects an answer via a channel.
|
|
4969
|
+
*
|
|
4970
|
+
* For multi-select questions, the interaction-utils wraps the response
|
|
4971
|
+
* with updatedInput containing { answers: { [questionText]: answerValue } }.
|
|
4972
|
+
*
|
|
4973
|
+
* Resolution values:
|
|
4974
|
+
* - answer → { action: 'answer', answer: string }
|
|
4975
|
+
* - deny → { action: 'deny', message }
|
|
4976
|
+
*
|
|
4977
|
+
* See file header for full flow diagram.
|
|
4978
|
+
*/
|
|
4876
4979
|
async handleCanAnswerQuestion(sessionId, question, signal) {
|
|
4877
4980
|
const state = this.transports.get(sessionId);
|
|
4878
4981
|
if (!state) {
|
|
@@ -6259,25 +6362,16 @@ function sendJson(res, statusCode, data) {
|
|
|
6259
6362
|
}
|
|
6260
6363
|
function formatHookResponse(eventType, toolName, result) {
|
|
6261
6364
|
const isAskUserQuestion = toolName === "AskUserQuestion";
|
|
6262
|
-
|
|
6365
|
+
const buildAskUserQuestionUpdatedInput = () => {
|
|
6263
6366
|
const questions = result._toolInput?.questions;
|
|
6264
6367
|
const question = questions?.[0];
|
|
6265
6368
|
const selectedValueStr = result.selectedValues && result.selectedValues.length > 0 ? result.selectedValues.map(String).join(", ") : void 0;
|
|
6266
6369
|
const answerValue = result.customInput ?? selectedValueStr ?? "";
|
|
6267
|
-
|
|
6370
|
+
return {
|
|
6268
6371
|
...result._toolInput,
|
|
6269
6372
|
answers: question?.question ? { [question.question]: answerValue } : {}
|
|
6270
6373
|
};
|
|
6271
|
-
|
|
6272
|
-
hookSpecificOutput: {
|
|
6273
|
-
hookEventName: "PermissionRequest",
|
|
6274
|
-
decision: {
|
|
6275
|
-
behavior: "allow",
|
|
6276
|
-
updatedInput
|
|
6277
|
-
}
|
|
6278
|
-
}
|
|
6279
|
-
});
|
|
6280
|
-
}
|
|
6374
|
+
};
|
|
6281
6375
|
if (eventType === "PermissionRequest") {
|
|
6282
6376
|
let updatedPermissions;
|
|
6283
6377
|
if (result.permissionUpdates && result.permissionUpdates.length > 0) {
|
|
@@ -6290,27 +6384,36 @@ function formatHookResponse(eventType, toolName, result) {
|
|
|
6290
6384
|
...rule.directories && { directories: rule.directories }
|
|
6291
6385
|
}));
|
|
6292
6386
|
}
|
|
6387
|
+
const decision = {
|
|
6388
|
+
behavior: result.action === "select" ? "allow" : result.action,
|
|
6389
|
+
...result.message !== void 0 && { message: result.message },
|
|
6390
|
+
...result.selectedValues !== void 0 && {
|
|
6391
|
+
data: result.selectedValues.map(String).join(", ")
|
|
6392
|
+
},
|
|
6393
|
+
...updatedPermissions !== void 0 && { updatedPermissions }
|
|
6394
|
+
};
|
|
6395
|
+
if (isAskUserQuestion) {
|
|
6396
|
+
decision.updatedInput = buildAskUserQuestionUpdatedInput();
|
|
6397
|
+
}
|
|
6293
6398
|
return JSON.stringify({
|
|
6294
6399
|
hookSpecificOutput: {
|
|
6295
6400
|
hookEventName: "PermissionRequest",
|
|
6296
|
-
decision
|
|
6297
|
-
behavior: result.action === "select" ? "allow" : result.action,
|
|
6298
|
-
...result.message !== void 0 && { message: result.message },
|
|
6299
|
-
...result.selectedValues !== void 0 && {
|
|
6300
|
-
data: result.selectedValues.map(String).join(", ")
|
|
6301
|
-
},
|
|
6302
|
-
...updatedPermissions !== void 0 && { updatedPermissions }
|
|
6303
|
-
}
|
|
6401
|
+
decision
|
|
6304
6402
|
}
|
|
6305
6403
|
});
|
|
6306
6404
|
}
|
|
6307
|
-
|
|
6308
|
-
|
|
6309
|
-
|
|
6310
|
-
|
|
6311
|
-
|
|
6312
|
-
|
|
6313
|
-
|
|
6405
|
+
const permissionDecision = result.action === "allow" ? "allow" : "deny";
|
|
6406
|
+
const hookSpecificOutput = {
|
|
6407
|
+
hookEventName: "PreToolUse",
|
|
6408
|
+
permissionDecision
|
|
6409
|
+
};
|
|
6410
|
+
if (result.message !== void 0) {
|
|
6411
|
+
hookSpecificOutput.permissionDecisionReason = result.message;
|
|
6412
|
+
}
|
|
6413
|
+
if (isAskUserQuestion) {
|
|
6414
|
+
hookSpecificOutput.updatedInput = buildAskUserQuestionUpdatedInput();
|
|
6415
|
+
}
|
|
6416
|
+
return JSON.stringify({ hookSpecificOutput });
|
|
6314
6417
|
}
|
|
6315
6418
|
function setupRoutes(httpServer, gateway, getClientCount) {
|
|
6316
6419
|
httpServer.registerRoute("GET", "/health", (_req, res) => {
|
|
@@ -9799,6 +9902,33 @@ function getEventTitle(eventType) {
|
|
|
9799
9902
|
}
|
|
9800
9903
|
}
|
|
9801
9904
|
|
|
9905
|
+
// src/shared/format.ts
|
|
9906
|
+
init_esm_shims();
|
|
9907
|
+
function formatDuration3(ms) {
|
|
9908
|
+
if (ms === void 0 || ms < 0) return "-";
|
|
9909
|
+
if (ms < 1e3) return `${ms}ms`;
|
|
9910
|
+
if (ms < 6e4) return `${Math.round(ms / 1e3)}s`;
|
|
9911
|
+
if (ms < 36e5) return `${Math.round(ms / 6e4)}m`;
|
|
9912
|
+
const hours = Math.floor(ms / 36e5);
|
|
9913
|
+
const minutes = Math.round(ms % 36e5 / 6e4);
|
|
9914
|
+
if (minutes === 0) return `${hours}h`;
|
|
9915
|
+
return `${hours}h${minutes}m`;
|
|
9916
|
+
}
|
|
9917
|
+
function formatTokenCount(n) {
|
|
9918
|
+
if (n === void 0 || n < 0) return "-";
|
|
9919
|
+
if (n < 1e3) return `${n}`;
|
|
9920
|
+
if (n < 1e6) {
|
|
9921
|
+
const val2 = n / 1e3;
|
|
9922
|
+
return `${stripZeroDecimal(val2)}k`;
|
|
9923
|
+
}
|
|
9924
|
+
const val = n / 1e6;
|
|
9925
|
+
return `${stripZeroDecimal(val)}M`;
|
|
9926
|
+
}
|
|
9927
|
+
function stripZeroDecimal(val) {
|
|
9928
|
+
const fixed = val.toFixed(1);
|
|
9929
|
+
return fixed.endsWith(".0") ? fixed.slice(0, -2) : fixed;
|
|
9930
|
+
}
|
|
9931
|
+
|
|
9802
9932
|
// src/adapters/channels/telegram/formatter.ts
|
|
9803
9933
|
var TelegramFormatter = class {
|
|
9804
9934
|
/**
|
|
@@ -9914,6 +10044,7 @@ var TelegramFormatter = class {
|
|
|
9914
10044
|
case "turn:finished":
|
|
9915
10045
|
return this.formatFinished(
|
|
9916
10046
|
title,
|
|
10047
|
+
context.model || context.agentType,
|
|
9917
10048
|
context.durationMs,
|
|
9918
10049
|
context.usage?.inputTokens,
|
|
9919
10050
|
context.usage?.outputTokens,
|
|
@@ -9949,17 +10080,19 @@ var TelegramFormatter = class {
|
|
|
9949
10080
|
/**
|
|
9950
10081
|
* 格式化结果通知消息
|
|
9951
10082
|
*/
|
|
9952
|
-
formatFinished(title, durationMs, inputTokens, outputTokens, result) {
|
|
10083
|
+
formatFinished(title, model, durationMs, inputTokens, outputTokens, result) {
|
|
9953
10084
|
const lines = [
|
|
9954
10085
|
`${this.escape(title)}`,
|
|
9955
10086
|
""
|
|
9956
10087
|
];
|
|
10088
|
+
if (model) {
|
|
10089
|
+
lines.push(`<b>Model:</b> ${this.escape(model)}`);
|
|
10090
|
+
}
|
|
9957
10091
|
if (durationMs !== void 0) {
|
|
9958
|
-
|
|
9959
|
-
lines.push(`<b>Duration:</b> ${this.escape(seconds)}s`);
|
|
10092
|
+
lines.push(`<b>Duration:</b> ${this.escape(formatDuration3(durationMs))}`);
|
|
9960
10093
|
}
|
|
9961
10094
|
if (inputTokens !== void 0 && outputTokens !== void 0) {
|
|
9962
|
-
lines.push(`<b>Tokens:</b>
|
|
10095
|
+
lines.push(`<b>Tokens:</b> ${formatTokenCount(inputTokens)}\u2191 / ${formatTokenCount(outputTokens)}\u2193`);
|
|
9963
10096
|
}
|
|
9964
10097
|
if (result) {
|
|
9965
10098
|
lines.push("", "<b>Result:</b>");
|
|
@@ -10889,7 +11022,8 @@ var FeishuClient = class {
|
|
|
10889
11022
|
userId: operator?.open_id,
|
|
10890
11023
|
messageId: context?.open_message_id,
|
|
10891
11024
|
chatId: context?.open_chat_id,
|
|
10892
|
-
optionId: value?.optionId
|
|
11025
|
+
optionId: value?.optionId,
|
|
11026
|
+
checked: actionObj?.checked
|
|
10893
11027
|
};
|
|
10894
11028
|
await this.cardActionHandler(cardAction);
|
|
10895
11029
|
} catch (error2) {
|
|
@@ -11335,7 +11469,7 @@ var FeishuChannel = class extends (_a4 = BaseChannel) {
|
|
|
11335
11469
|
{ text: "\u274C Deny", type: "danger_filled", value: "reject", confirm: { title: "\u786E\u8BA4\u62D2\u7EDD", text: "\u786E\u8BA4\u62D2\u7EDD\u8BE5\u5DE5\u5177\u8C03\u7528\uFF1F" } },
|
|
11336
11470
|
{ text: "\u{1F513} Always Allow", type: "default", value: "always_allow", confirm: { title: "\u786E\u8BA4\u603B\u662F\u5141\u8BB8", text: "\u603B\u662F\u5141\u8BB8\u5C06\u653E\u884C\u8BE5\u5DE5\u5177\u7684\u6240\u6709\u8C03\u7528\uFF0C\u8BF7\u786E\u8BA4\u3002" } }
|
|
11337
11471
|
];
|
|
11338
|
-
const cardContent = this.formatter.formatPermissionCard({ title: this.getTitleForContext(message.context), content, buttons });
|
|
11472
|
+
const cardContent = this.formatter.formatPermissionCard({ title: this.getTitleForContext(message.context), content, buttons, requestId });
|
|
11339
11473
|
await this.sendCardRaw(cardContent, message);
|
|
11340
11474
|
} else if (eventType === "question:request") {
|
|
11341
11475
|
const question = interaction?.question;
|
|
@@ -11348,7 +11482,7 @@ var FeishuChannel = class extends (_a4 = BaseChannel) {
|
|
|
11348
11482
|
question.options.map((opt) => ({ id: opt.id, value: opt.value }))
|
|
11349
11483
|
);
|
|
11350
11484
|
}
|
|
11351
|
-
const cardContent = this.formatter.formatQuestionCard({ title: this.getTitleForContext(message.context), content, mode, items });
|
|
11485
|
+
const cardContent = this.formatter.formatQuestionCard({ title: this.getTitleForContext(message.context), content, mode, items, requestId });
|
|
11352
11486
|
await this.sendCardRaw(cardContent, message);
|
|
11353
11487
|
} else {
|
|
11354
11488
|
const content = message.context ? this.formatContextToMarkdown(message.context) : message.text || "";
|
|
@@ -11356,10 +11490,10 @@ var FeishuChannel = class extends (_a4 = BaseChannel) {
|
|
|
11356
11490
|
if (message.context?.eventType === "turn:finished") {
|
|
11357
11491
|
const finishedCtx = message.context;
|
|
11358
11492
|
const model = finishedCtx.model || finishedCtx.agentType || "-";
|
|
11359
|
-
const duration =
|
|
11493
|
+
const duration = formatDuration3(finishedCtx.durationMs);
|
|
11360
11494
|
const outTokens = finishedCtx.usage?.outputTokens;
|
|
11361
11495
|
const inTokens = finishedCtx.usage?.inputTokens;
|
|
11362
|
-
const tokens = outTokens !== void 0 && inTokens !== void 0 ? `${
|
|
11496
|
+
const tokens = outTokens !== void 0 && inTokens !== void 0 ? `${formatTokenCount(inTokens)}\u2191 ${formatTokenCount(outTokens)}\u2193` : "-";
|
|
11363
11497
|
meta = `${model} \xB7 ${duration} \xB7 ${tokens}`;
|
|
11364
11498
|
}
|
|
11365
11499
|
const cardContent = this.formatter.formatMessageCard({ title: this.getTitleForContext(message.context), content, meta });
|
|
@@ -11517,12 +11651,12 @@ var FeishuChannel = class extends (_a4 = BaseChannel) {
|
|
|
11517
11651
|
this.pendingSelections.set(requestId, /* @__PURE__ */ new Set());
|
|
11518
11652
|
}
|
|
11519
11653
|
const selected = this.pendingSelections.get(requestId);
|
|
11520
|
-
if (
|
|
11521
|
-
selected.delete(optionId);
|
|
11522
|
-
} else {
|
|
11654
|
+
if (data.checked) {
|
|
11523
11655
|
selected.add(optionId);
|
|
11656
|
+
} else {
|
|
11657
|
+
selected.delete(optionId);
|
|
11524
11658
|
}
|
|
11525
|
-
this.logger.info({ requestId, userId, optionId, selectedCount: selected.size }, "[Channel] Checker
|
|
11659
|
+
this.logger.info({ requestId, userId, optionId, checked: data.checked, selectedCount: selected.size }, "[Channel] Checker changed");
|
|
11526
11660
|
return;
|
|
11527
11661
|
}
|
|
11528
11662
|
if (action === "submit") {
|
|
@@ -11669,20 +11803,10 @@ ${context.thinking}`;
|
|
|
11669
11803
|
`;
|
|
11670
11804
|
if (context.cwd) {
|
|
11671
11805
|
md += `**WorkDir:** ${context.cwd}
|
|
11672
|
-
`;
|
|
11673
|
-
}
|
|
11674
|
-
md += `**Status:** Turn finished
|
|
11675
|
-
`;
|
|
11676
|
-
if (context.durationMs) {
|
|
11677
|
-
md += `**Duration:** ${(context.durationMs / 1e3).toFixed(1)}s
|
|
11678
|
-
`;
|
|
11679
|
-
}
|
|
11680
|
-
if (context.usage?.inputTokens !== void 0 && context.usage?.outputTokens !== void 0) {
|
|
11681
|
-
md += `**Tokens:** in ${context.usage.inputTokens} / out ${context.usage.outputTokens}
|
|
11682
11806
|
`;
|
|
11683
11807
|
}
|
|
11684
11808
|
if (context.lastAssistantMessage) {
|
|
11685
|
-
md +=
|
|
11809
|
+
md += `${context.lastAssistantMessage}`;
|
|
11686
11810
|
}
|
|
11687
11811
|
break;
|
|
11688
11812
|
case "tool:post":
|