blun-king-cli 9.1.463 → 9.1.464

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.
@@ -0,0 +1,252 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { createRequire } from 'node:module';
3
+
4
+ const require = createRequire(import.meta.url);
5
+ const { createAgentApiToolBroker } = require('./agent-api-tool-broker.cjs');
6
+
7
+ const CLIENT_TOOL_NAME_PATTERN = /^[A-Za-z0-9_-]{1,64}$/u;
8
+ const INTERNAL_TOOL_PREFIX = 'client__';
9
+ const NO_CLIENT_TOOLS_PREFIX = '__blun_agent_api_no_client_tools__';
10
+
11
+ class AgentApiRuntimeError extends Error {
12
+ constructor(code, message) {
13
+ super(message);
14
+ this.name = 'AgentApiRuntimeError';
15
+ this.code = code;
16
+ }
17
+ }
18
+
19
+ function runtimeError(code, message) {
20
+ return new AgentApiRuntimeError(code, message);
21
+ }
22
+
23
+ function requireId(value, label) {
24
+ if (typeof value !== 'string' || value.trim().length === 0) {
25
+ throw runtimeError('AGENT_API_INVALID_SCOPE', `${label} must be a non-empty string`);
26
+ }
27
+ return value.trim();
28
+ }
29
+
30
+ function cloneStructured(value, label) {
31
+ try {
32
+ return structuredClone(value);
33
+ } catch {
34
+ throw runtimeError(
35
+ 'AGENT_API_INVALID_TOOL_DEFINITION',
36
+ `${label} must be structured-cloneable`,
37
+ );
38
+ }
39
+ }
40
+
41
+ function normalizeParameters(value, name) {
42
+ if (value === undefined) {
43
+ return { type: 'object', properties: {} };
44
+ }
45
+ if (value === null || typeof value !== 'object' || Array.isArray(value)) {
46
+ throw runtimeError(
47
+ 'AGENT_API_INVALID_TOOL_DEFINITION',
48
+ `tool "${name}" parameters must be an object`,
49
+ );
50
+ }
51
+ return cloneStructured(value, `tool "${name}" parameters`);
52
+ }
53
+
54
+ function normalizeClientToolDefinitions(tools) {
55
+ if (!Array.isArray(tools)) {
56
+ throw runtimeError('AGENT_API_INVALID_TOOLS', 'tools must be an array');
57
+ }
58
+
59
+ const names = new Set();
60
+ return tools.map((tool, index) => {
61
+ if (tool === null || typeof tool !== 'object' || Array.isArray(tool)
62
+ || tool.type !== 'function'
63
+ || tool.function === null
64
+ || typeof tool.function !== 'object'
65
+ || Array.isArray(tool.function)) {
66
+ throw runtimeError(
67
+ 'AGENT_API_INVALID_TOOL_DEFINITION',
68
+ `tools[${index}] must be an OpenAI function tool`,
69
+ );
70
+ }
71
+
72
+ const name = tool.function.name;
73
+ if (typeof name !== 'string' || !CLIENT_TOOL_NAME_PATTERN.test(name)) {
74
+ throw runtimeError(
75
+ 'AGENT_API_INVALID_TOOL_DEFINITION',
76
+ `tools[${index}].function.name must match ${CLIENT_TOOL_NAME_PATTERN}`,
77
+ );
78
+ }
79
+ if (names.has(name)) {
80
+ throw runtimeError(
81
+ 'AGENT_API_DUPLICATE_TOOL_NAME',
82
+ `tool name "${name}" is declared more than once`,
83
+ );
84
+ }
85
+ names.add(name);
86
+
87
+ const description = tool.function.description;
88
+ if (description !== undefined && typeof description !== 'string') {
89
+ throw runtimeError(
90
+ 'AGENT_API_INVALID_TOOL_DEFINITION',
91
+ `tool "${name}" description must be a string`,
92
+ );
93
+ }
94
+
95
+ return Object.freeze({
96
+ name,
97
+ description: description ?? '',
98
+ parameters: normalizeParameters(tool.function.parameters, name),
99
+ });
100
+ });
101
+ }
102
+
103
+ function shortHash(value, length = 10) {
104
+ return createHash('sha256').update(value).digest('hex').slice(0, length);
105
+ }
106
+
107
+ function internalToolName(publicName, index) {
108
+ const ordinal = index.toString(36).padStart(2, '0');
109
+ const prefix = `${INTERNAL_TOOL_PREFIX}${ordinal}__`;
110
+ const suffix = `__${shortHash(publicName, 8)}`;
111
+ const available = 64 - prefix.length - suffix.length;
112
+ return `${prefix}${publicName.slice(0, available)}${suffix}`;
113
+ }
114
+
115
+ function noClientToolsSentinel(sessionId, requestId) {
116
+ return `${NO_CLIENT_TOOLS_PREFIX}${shortHash(`${sessionId}\0${requestId}`, 16)}`;
117
+ }
118
+
119
+ function validateSession(session) {
120
+ if (session === null || typeof session !== 'object') {
121
+ throw runtimeError('AGENT_API_INVALID_SESSION', 'session must be an SDK session');
122
+ }
123
+ const requiredMethods = [
124
+ 'registerToolAndWait',
125
+ 'unregisterToolAndWait',
126
+ 'setAllowedTools',
127
+ ];
128
+ for (const method of requiredMethods) {
129
+ if (typeof session[method] !== 'function') {
130
+ throw runtimeError(
131
+ 'AGENT_API_INVALID_SESSION',
132
+ `session.${method} must be a function`,
133
+ );
134
+ }
135
+ }
136
+ return requireId(session.id, 'session.id');
137
+ }
138
+
139
+ async function unregisterTools(session, names) {
140
+ let firstError;
141
+ for (const name of [...names].reverse()) {
142
+ try {
143
+ await session.unregisterToolAndWait(name);
144
+ } catch (error) {
145
+ firstError ??= error;
146
+ }
147
+ }
148
+ if (firstError !== undefined) throw firstError;
149
+ }
150
+
151
+ function createAgentApiRuntime(options = {}) {
152
+ const broker = options.broker ?? createAgentApiToolBroker(options.brokerOptions);
153
+
154
+ async function bindClientTools(input) {
155
+ if (input === null || typeof input !== 'object') {
156
+ throw runtimeError('AGENT_API_INVALID_REQUEST', 'binding input must be an object');
157
+ }
158
+
159
+ const sessionId = validateSession(input.session);
160
+ const requestId = requireId(input.requestId, 'requestId');
161
+ const tools = normalizeClientToolDefinitions(input.tools ?? []);
162
+ const mappings = tools.map((tool, index) => Object.freeze({
163
+ ...tool,
164
+ internalName: internalToolName(tool.name, index),
165
+ }));
166
+ const registeredNames = [];
167
+
168
+ broker.beginRequest({
169
+ sessionId,
170
+ requestId,
171
+ toolNames: mappings.map((tool) => tool.name),
172
+ });
173
+
174
+ try {
175
+ for (const tool of mappings) {
176
+ await input.session.registerToolAndWait(
177
+ tool.internalName,
178
+ tool.description,
179
+ tool.parameters,
180
+ (request) => broker.handleToolCall({
181
+ sessionId,
182
+ turnId: request.turnId,
183
+ toolCallId: request.toolCallId,
184
+ toolName: tool.name,
185
+ args: request.args,
186
+ }),
187
+ );
188
+ registeredNames.push(tool.internalName);
189
+ }
190
+
191
+ await input.session.setAllowedTools(
192
+ registeredNames.length > 0
193
+ ? registeredNames
194
+ : [noClientToolsSentinel(sessionId, requestId)],
195
+ );
196
+ } catch (error) {
197
+ broker.abortRequest({
198
+ sessionId,
199
+ requestId,
200
+ reason: 'Agent API SDK binding failed',
201
+ });
202
+ try {
203
+ await unregisterTools(input.session, registeredNames);
204
+ } catch {
205
+ // Preserve the binding error; cleanup was best-effort and all handlers are fail-closed.
206
+ }
207
+ throw error;
208
+ }
209
+
210
+ let closed = false;
211
+ function ensureOpen() {
212
+ if (closed) {
213
+ throw runtimeError('AGENT_API_BINDING_CLOSED', 'Agent API SDK binding is closed');
214
+ }
215
+ }
216
+
217
+ return Object.freeze({
218
+ sessionId,
219
+ requestId,
220
+ nextToolCall() {
221
+ ensureOpen();
222
+ return broker.nextToolCall({ sessionId, requestId });
223
+ },
224
+ submitToolResult(result) {
225
+ ensureOpen();
226
+ return broker.submitToolResult({ sessionId, requestId, ...result });
227
+ },
228
+ async finish() {
229
+ ensureOpen();
230
+ broker.finishRequest({ sessionId, requestId });
231
+ closed = true;
232
+ await unregisterTools(input.session, registeredNames);
233
+ },
234
+ async abort(reason = 'Agent API request aborted') {
235
+ ensureOpen();
236
+ broker.abortRequest({ sessionId, requestId, reason });
237
+ closed = true;
238
+ await unregisterTools(input.session, registeredNames);
239
+ },
240
+ });
241
+ }
242
+
243
+ return Object.freeze({ bindClientTools });
244
+ }
245
+
246
+ export {
247
+ AgentApiRuntimeError,
248
+ CLIENT_TOOL_NAME_PATTERN,
249
+ NO_CLIENT_TOOLS_PREFIX,
250
+ createAgentApiRuntime,
251
+ normalizeClientToolDefinitions,
252
+ };
@@ -0,0 +1,248 @@
1
+ 'use strict';
2
+
3
+ const DEFAULT_MAX_PENDING_TOOL_CALLS = 32;
4
+
5
+ class AgentApiToolBrokerError extends Error {
6
+ constructor(code, message) {
7
+ super(message);
8
+ this.name = 'AgentApiToolBrokerError';
9
+ this.code = code;
10
+ }
11
+ }
12
+
13
+ function brokerError(code, message) {
14
+ return new AgentApiToolBrokerError(code, message);
15
+ }
16
+
17
+ function requireId(value, label) {
18
+ if (typeof value !== 'string' || value.trim().length === 0) {
19
+ throw brokerError('AGENT_API_INVALID_SCOPE', `${label} must be a non-empty string`);
20
+ }
21
+ return value.trim();
22
+ }
23
+
24
+ function normalizeToolNames(value) {
25
+ if (!Array.isArray(value)) {
26
+ throw brokerError('AGENT_API_INVALID_TOOLS', 'toolNames must be an array');
27
+ }
28
+ const names = value.map((name) => requireId(name, 'tool name'));
29
+ if (new Set(names).size !== names.length) {
30
+ throw brokerError('AGENT_API_INVALID_TOOLS', 'toolNames must not contain duplicates');
31
+ }
32
+ return new Set(names);
33
+ }
34
+
35
+ function normalizeMaxPendingToolCalls(value) {
36
+ const normalized = value ?? DEFAULT_MAX_PENDING_TOOL_CALLS;
37
+ if (!Number.isSafeInteger(normalized) || normalized < 1) {
38
+ throw brokerError(
39
+ 'AGENT_API_INVALID_LIMIT',
40
+ 'maxPendingToolCalls must be a positive safe integer',
41
+ );
42
+ }
43
+ return normalized;
44
+ }
45
+
46
+ function normalizeToolResult(input) {
47
+ if (typeof input.output !== 'string') {
48
+ throw brokerError('AGENT_API_INVALID_TOOL_RESULT', 'tool result output must be a string');
49
+ }
50
+ if (input.isError !== undefined && typeof input.isError !== 'boolean') {
51
+ throw brokerError('AGENT_API_INVALID_TOOL_RESULT', 'tool result isError must be a boolean');
52
+ }
53
+ return {
54
+ output: input.output,
55
+ isError: input.isError === true,
56
+ };
57
+ }
58
+
59
+ function cloneArgs(value) {
60
+ try {
61
+ return structuredClone(value);
62
+ } catch {
63
+ throw brokerError('AGENT_API_INVALID_TOOL_ARGS', 'tool arguments must be structured-cloneable');
64
+ }
65
+ }
66
+
67
+ function createDeferred() {
68
+ let resolve;
69
+ let reject;
70
+ const promise = new Promise((resolvePromise, rejectPromise) => {
71
+ resolve = resolvePromise;
72
+ reject = rejectPromise;
73
+ });
74
+ return { promise, reject, resolve };
75
+ }
76
+
77
+ function createAgentApiToolBroker(options = {}) {
78
+ const maxPendingToolCalls = normalizeMaxPendingToolCalls(options.maxPendingToolCalls);
79
+ const activeRequests = new Map();
80
+
81
+ function requestForScope(input) {
82
+ const sessionId = requireId(input.sessionId, 'sessionId');
83
+ const requestId = requireId(input.requestId, 'requestId');
84
+ const request = activeRequests.get(sessionId);
85
+ if (request === undefined) {
86
+ throw brokerError('AGENT_API_REQUEST_NOT_ACTIVE', 'no active Agent API request for session');
87
+ }
88
+ if (request.requestId !== requestId) {
89
+ throw brokerError(
90
+ 'AGENT_API_TOOL_RESULT_SCOPE_MISMATCH',
91
+ 'Agent API request does not match the active session request',
92
+ );
93
+ }
94
+ return request;
95
+ }
96
+
97
+ function pendingToolCallCount() {
98
+ let count = 0;
99
+ for (const request of activeRequests.values()) count += request.pending.size;
100
+ return count;
101
+ }
102
+
103
+ function deliverEvent(request, event) {
104
+ const waiter = request.eventWaiters.shift();
105
+ if (waiter !== undefined) {
106
+ waiter.resolve(event);
107
+ return;
108
+ }
109
+ request.events.push(event);
110
+ }
111
+
112
+ function beginRequest(input) {
113
+ const sessionId = requireId(input.sessionId, 'sessionId');
114
+ const requestId = requireId(input.requestId, 'requestId');
115
+ if (activeRequests.has(sessionId)) {
116
+ throw brokerError(
117
+ 'AGENT_API_SESSION_BUSY',
118
+ 'session already has an active Agent API request',
119
+ );
120
+ }
121
+ const request = {
122
+ sessionId,
123
+ requestId,
124
+ toolNames: normalizeToolNames(input.toolNames),
125
+ seenToolCallIds: new Set(),
126
+ pending: new Map(),
127
+ events: [],
128
+ eventWaiters: [],
129
+ };
130
+ activeRequests.set(sessionId, request);
131
+ return { sessionId, requestId };
132
+ }
133
+
134
+ function handleToolCall(input) {
135
+ const sessionId = requireId(input.sessionId, 'sessionId');
136
+ const toolCallId = requireId(input.toolCallId, 'toolCallId');
137
+ const toolName = requireId(input.toolName, 'toolName');
138
+ const request = activeRequests.get(sessionId);
139
+ if (request === undefined) {
140
+ return Promise.reject(brokerError(
141
+ 'AGENT_API_REQUEST_NOT_ACTIVE',
142
+ 'no active Agent API request for SDK tool call',
143
+ ));
144
+ }
145
+ if (!request.toolNames.has(toolName)) {
146
+ return Promise.reject(brokerError(
147
+ 'AGENT_API_TOOL_NOT_DECLARED',
148
+ `tool "${toolName}" was not declared by the API client`,
149
+ ));
150
+ }
151
+ if (request.seenToolCallIds.has(toolCallId)) {
152
+ return Promise.reject(brokerError(
153
+ 'AGENT_API_TOOL_CALL_DUPLICATE',
154
+ `tool call "${toolCallId}" has already been accepted`,
155
+ ));
156
+ }
157
+ if (pendingToolCallCount() >= maxPendingToolCalls) {
158
+ return Promise.reject(brokerError(
159
+ 'AGENT_API_TOOL_CALL_LIMIT',
160
+ 'too many client tool calls are waiting for results',
161
+ ));
162
+ }
163
+
164
+ const deferred = createDeferred();
165
+ request.seenToolCallIds.add(toolCallId);
166
+ request.pending.set(toolCallId, deferred);
167
+ deliverEvent(request, Object.freeze({
168
+ type: 'client_tool_call',
169
+ sessionId,
170
+ requestId: request.requestId,
171
+ turnId: input.turnId,
172
+ toolCallId,
173
+ toolName,
174
+ args: cloneArgs(input.args),
175
+ }));
176
+ return deferred.promise;
177
+ }
178
+
179
+ function nextToolCall(input) {
180
+ const request = requestForScope(input);
181
+ const event = request.events.shift();
182
+ if (event !== undefined) return Promise.resolve(event);
183
+ const deferred = createDeferred();
184
+ request.eventWaiters.push(deferred);
185
+ return deferred.promise;
186
+ }
187
+
188
+ function submitToolResult(input) {
189
+ const request = requestForScope(input);
190
+ const toolCallId = requireId(input.toolCallId, 'toolCallId');
191
+ const pending = request.pending.get(toolCallId);
192
+ if (pending === undefined) {
193
+ throw brokerError(
194
+ 'AGENT_API_TOOL_CALL_NOT_PENDING',
195
+ `tool call "${toolCallId}" is not waiting for a result`,
196
+ );
197
+ }
198
+ const result = normalizeToolResult(input);
199
+ request.pending.delete(toolCallId);
200
+ pending.resolve(result);
201
+ return true;
202
+ }
203
+
204
+ function abortRequest(input) {
205
+ const request = requestForScope(input);
206
+ const reason = typeof input.reason === 'string' && input.reason.trim().length > 0
207
+ ? input.reason.trim()
208
+ : 'request aborted';
209
+ const error = brokerError('AGENT_API_REQUEST_ABORTED', reason);
210
+ activeRequests.delete(request.sessionId);
211
+ request.events.length = 0;
212
+ for (const waiter of request.eventWaiters.splice(0)) waiter.reject(error);
213
+ for (const pending of request.pending.values()) pending.reject(error);
214
+ request.pending.clear();
215
+ return true;
216
+ }
217
+
218
+ function finishRequest(input) {
219
+ const request = requestForScope(input);
220
+ if (request.pending.size > 0) {
221
+ throw brokerError(
222
+ 'AGENT_API_REQUEST_HAS_PENDING_TOOLS',
223
+ 'cannot finish a request while client tool results are pending',
224
+ );
225
+ }
226
+ const error = brokerError('AGENT_API_REQUEST_FINISHED', 'Agent API request finished');
227
+ activeRequests.delete(request.sessionId);
228
+ request.events.length = 0;
229
+ for (const waiter of request.eventWaiters.splice(0)) waiter.reject(error);
230
+ return true;
231
+ }
232
+
233
+ return Object.freeze({
234
+ abortRequest,
235
+ beginRequest,
236
+ finishRequest,
237
+ handleToolCall,
238
+ nextToolCall,
239
+ pendingToolCallCount,
240
+ submitToolResult,
241
+ });
242
+ }
243
+
244
+ module.exports = {
245
+ AgentApiToolBrokerError,
246
+ DEFAULT_MAX_PENDING_TOOL_CALLS,
247
+ createAgentApiToolBroker,
248
+ };
@@ -28,6 +28,8 @@ const LEGACY_PROFILE_ENTRIES = Object.freeze([
28
28
  const MIGRATION_MARKER = '.legacy-profile-migrated-v1';
29
29
  const SESSION_PATH_MIGRATION_MARKER = '.legacy-profile-session-paths-v2';
30
30
  const SESSION_PATH_BACKUP_DIR = '.legacy-profile-session-paths-v2-backup';
31
+ const PERSONA_LANGUAGE_MIGRATION_MARKER = '.legacy-persona-language-v1';
32
+ const PERSONA_LANGUAGE_BACKUP = '.legacy-persona-language-v1-backup.json';
31
33
 
32
34
  function normalizeProfileName(value) {
33
35
  const normalized = String(value || '').trim().toLowerCase();
@@ -134,6 +136,56 @@ function backupFile(source, target, fsImpl) {
134
136
  fsImpl.copyFileSync(source, target);
135
137
  }
136
138
 
139
+ function readJsonObject(target, fsImpl) {
140
+ if (!fsImpl.existsSync(target)) return null;
141
+ try {
142
+ const text = fsImpl.readFileSync(target, 'utf8');
143
+ const parsed = JSON.parse(text.charCodeAt(0) === 0xFEFF ? text.slice(1) : text);
144
+ return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : null;
145
+ } catch {
146
+ return null;
147
+ }
148
+ }
149
+
150
+ function writeMarkerOnce(target, text, fsImpl) {
151
+ try {
152
+ fsImpl.writeFileSync(target, text, { encoding: 'utf8', flag: 'wx' });
153
+ } catch (error) {
154
+ if (error?.code !== 'EEXIST') throw error;
155
+ }
156
+ }
157
+
158
+ function repairDefaultProfilePersonaLanguage(sharedHome, fsImpl = fs) {
159
+ const paths = resolveProfilePaths(sharedHome, DEFAULT_PROFILE);
160
+ const marker = path.join(paths.home, PERSONA_LANGUAGE_MIGRATION_MARKER);
161
+ const profilePersonaPath = path.join(paths.home, 'persona.json');
162
+ const legacyPersonaPath = path.join(sharedHome, 'persona.json');
163
+ if (fsImpl.existsSync(marker) || !fsImpl.existsSync(profilePersonaPath)) return paths;
164
+
165
+ const profilePersona = readJsonObject(profilePersonaPath, fsImpl);
166
+ const legacyPersona = readJsonObject(legacyPersonaPath, fsImpl);
167
+ if (profilePersona === null || legacyPersona === null) return paths;
168
+
169
+ if (!Object.hasOwn(profilePersona, 'language')) {
170
+ const legacyLanguage = typeof legacyPersona.language === 'string'
171
+ ? legacyPersona.language.trim()
172
+ : '';
173
+ if (legacyLanguage) {
174
+ const original = fsImpl.readFileSync(profilePersonaPath, 'utf8');
175
+ backupFile(profilePersonaPath, path.join(paths.home, PERSONA_LANGUAGE_BACKUP), fsImpl);
176
+ replaceTextFileIfUnchanged(
177
+ profilePersonaPath,
178
+ original,
179
+ `${JSON.stringify({ ...profilePersona, language: legacyLanguage }, null, 2)}\n`,
180
+ fsImpl,
181
+ );
182
+ }
183
+ }
184
+
185
+ writeMarkerOnce(marker, 'legacy persona language repaired once\n', fsImpl);
186
+ return paths;
187
+ }
188
+
137
189
  function rewriteSessionState(statePath, legacySessionDir, profileSessionDir, backupRoot, relativeSessionDir, fsImpl) {
138
190
  if (!fsImpl.existsSync(statePath)) return false;
139
191
  const original = fsImpl.readFileSync(statePath, 'utf8');
@@ -251,6 +303,7 @@ function seedDefaultProfileFromLegacy(sharedHome, fsImpl = fs) {
251
303
  if (error?.code !== 'EEXIST') throw error;
252
304
  }
253
305
  }
306
+ repairDefaultProfilePersonaLanguage(sharedHome, fsImpl);
254
307
  return repairDefaultProfileSessionPaths(sharedHome, fsImpl);
255
308
  }
256
309
 
@@ -258,6 +311,7 @@ module.exports = {
258
311
  DEFAULT_PROFILE,
259
312
  normalizeProfileName,
260
313
  parseProfileLaunchArgs,
314
+ repairDefaultProfilePersonaLanguage,
261
315
  repairDefaultProfileSessionPaths,
262
316
  resolveProfilePaths,
263
317
  seedDefaultProfileFromLegacy,
@@ -0,0 +1,19 @@
1
+ 'use strict';
2
+
3
+ function resolveSubagentRunMode({ allowBackground, requested }) {
4
+ if (allowBackground !== true) {
5
+ return {
6
+ runInBackground: false,
7
+ forcedForParentResponsiveness: false,
8
+ };
9
+ }
10
+
11
+ return {
12
+ runInBackground: true,
13
+ forcedForParentResponsiveness: requested !== true,
14
+ };
15
+ }
16
+
17
+ module.exports = {
18
+ resolveSubagentRunMode,
19
+ };
@@ -0,0 +1,45 @@
1
+ 'use strict';
2
+
3
+ function createAddressedChannelFocusStore(options = {}) {
4
+ const maxItems = Math.max(1, Number(options.maxItems) || 3);
5
+ const onChange = typeof options.onChange === 'function' ? options.onChange : () => {};
6
+ let sequence = 0;
7
+ let items = [];
8
+
9
+ function snapshot() {
10
+ return items.map((item) => ({ ...item }));
11
+ }
12
+
13
+ function publish() {
14
+ onChange(snapshot());
15
+ }
16
+
17
+ return {
18
+ pin(input) {
19
+ const item = {
20
+ id: `channel-focus-${++sequence}`,
21
+ chatId: String(input.chatId),
22
+ origin: String(input.origin ?? ''),
23
+ text: String(input.text ?? '').trim(),
24
+ };
25
+ items = [...items, item].slice(-maxItems);
26
+ publish();
27
+ return item.id;
28
+ },
29
+
30
+ clearChat(chatId) {
31
+ const expected = String(chatId);
32
+ const next = items.filter((item) => item.chatId !== expected);
33
+ if (next.length === items.length) return false;
34
+ items = next;
35
+ publish();
36
+ return true;
37
+ },
38
+
39
+ snapshot,
40
+ };
41
+ }
42
+
43
+ module.exports = {
44
+ createAddressedChannelFocusStore,
45
+ };
package/blun.mjs CHANGED
@@ -28244,6 +28244,16 @@ function readPersona$1(env = process.env) {
28244
28244
  const resolved = readProfilePersona(env);
28245
28245
  return resolved.persona;
28246
28246
  }
28247
+ function defaultAnswerLanguageSystemBlock() {
28248
+ return [
28249
+ "## Default answer language — HARD RULE",
28250
+ "",
28251
+ "English is the initial language for new or unconfigured accounts, profiles, and sessions.",
28252
+ "An explicit language saved in the account, profile, or current session overrides this default.",
28253
+ "Detect the language of the user's latest conversational message and answer in that language, including languages such as Bengali.",
28254
+ "Tool output does not change the conversational language. This rule overrides the generic # Language section when no explicit language is active."
28255
+ ].join("\n");
28256
+ }
28247
28257
  /**
28248
28258
  * Build the system-prompt block that teaches the agent its persona name, so it
28249
28259
  * warmly adopts the name the user gave it. Empty string when no name is set.
@@ -28410,6 +28420,7 @@ function buildTemplateVars(context, promptVars, tools) {
28410
28420
  const now = context.now instanceof Date ? context.now.toISOString() : context.now ?? (/* @__PURE__ */ new Date()).toISOString();
28411
28421
  const roleBase = context.roleAdditional ?? promptVars["ROLE_ADDITIONAL"] ?? promptVars["roleAdditional"] ?? "";
28412
28422
  const roleAdditional = [
28423
+ defaultAnswerLanguageSystemBlock(),
28413
28424
  personaSystemBlock(),
28414
28425
  soulSystemBlock(),
28415
28426
  identitySystemBlock(),
@@ -252787,7 +252798,7 @@ var init_agent_background_disabled = __esmMin((() => {
252787
252798
  //#region ../../packages/agent-core/src/tools/builtin/collaboration/agent-background-enabled.md?raw
252788
252799
  var agent_background_enabled_default;
252789
252800
  var init_agent_background_enabled = __esmMin((() => {
252790
- agent_background_enabled_default = "When `run_in_background=true`, the subagent runs detached from this turn. The completion arrives in a later turn as a synthetic user-role message containing its result — you do not need to poll, sleep, or check on its progress. Continue with other work or respond to the user. Never fabricate or predict what the result will say.\n\nDefault to a foreground subagent (omit `run_in_background`) when your next step needs its result — foreground hands the result straight back. Reach for `run_in_background=true` only when you have other work to do while it runs and do not need its result to proceed. Never launch in the background and then immediately wait on it (with `TaskOutput block=true`, sleeping, or otherwise): that just blocks the turn for no benefit — run it in the foreground instead.\n";
252801
+ agent_background_enabled_default = "Subagents run detached whenever task management is available, so the parent remains responsive while delegated work continues. Completion arrives automatically in a later turn. Do not poll, sleep, or block on TaskOutput. Continue with other work or respond to the user. Use TaskUpdate to steer the running subagent and TaskStop only when it must be cancelled. Never fabricate or predict its result.";
252791
252802
  }));
252792
252803
  //#endregion
252793
252804
  //#region ../../packages/agent-core/src/tools/builtin/collaboration/agent.md?raw
@@ -252807,7 +252818,7 @@ function formatBackgroundAgentResult(taskId, handle, description, allowBackgroun
252807
252818
  "",
252808
252819
  `description: ${description}`,
252809
252820
  "",
252810
- allowBackground ? `next_step: The completion arrives automatically in a later turn — do NOT wait, poll, or call TaskOutput on it; continue with other work or hand back to the user. (If you have nothing to do until it finishes, run such tasks in the foreground next time.)` : "next_step: The completion arrives automatically in a later turn.",
252821
+ allowBackground ? "next_step: The subagent continues independently. Do not wait or poll; remain responsive to the user. Use TaskUpdate to steer it and TaskStop only if it must be cancelled. Its completion arrives automatically in a later turn." : "next_step: The completion arrives automatically in a later turn.",
252811
252822
  `resume_hint: To continue or recover this same subagent later, call Agent(resume="${handle.agentId}", prompt="..."). The parameter is agent_id ("${handle.agentId}"), NOT task_id ("${taskId}") or source_id from a later <notification>. Recovery cases: a later <notification type="task.lost" | "task.failed" | "task.killed"> for this subagent — its conversation history is preserved across session restarts and resume will pick it up.`
252812
252823
  ].join("\n");
252813
252824
  }
@@ -252846,7 +252857,7 @@ function buildSubagentDescriptions(subagents) {
252846
252857
  return `${header}\n Tools: ${subagent.tools.join(", ")}`;
252847
252858
  }).join("\n");
252848
252859
  }
252849
- var AgentToolInputSchema, BACKGROUND_AGENT_UNAVAILABLE, AgentTool, USER_INTERRUPTED_SUBAGENT_MESSAGE;
252860
+ var AgentToolInputSchema, BACKGROUND_AGENT_UNAVAILABLE, AgentTool, USER_INTERRUPTED_SUBAGENT_MESSAGE, resolveSubagentRunMode;
252850
252861
  var init_agent$1 = __esmMin((() => {
252851
252862
  init_zod$1();
252852
252863
  init_tool_access();
@@ -252859,6 +252870,7 @@ var init_agent$1 = __esmMin((() => {
252859
252870
  init_agent_background_disabled();
252860
252871
  init_agent_background_enabled();
252861
252872
  init_agent$2();
252873
+ ({ resolveSubagentRunMode } = createRequire(import.meta.url)("./bin/subagent-parent-responsiveness.cjs"));
252862
252874
  AgentToolInputSchema = preprocess((input) => {
252863
252875
  if (typeof input !== "object" || input === null || Array.isArray(input)) return input;
252864
252876
  const normalized = { ...input };
@@ -252872,7 +252884,7 @@ var init_agent$1 = __esmMin((() => {
252872
252884
  description: string().describe("Short task description (3-5 words) for UI display"),
252873
252885
  subagent_type: string().optional().describe("One of the available agent types (see \"Available agent types\" in this tool description). Defaults to \"coder\" when omitted."),
252874
252886
  resume: string().optional().describe("Optional agent ID to resume instead of creating a new instance. When set, do not also pass subagent_type — the resumed agent keeps its own type, and supplying both is rejected."),
252875
- run_in_background: boolean$1().optional().describe("If true, return immediately without waiting for completion. Prefer false unless the task can run independently and there is a clear benefit to not waiting."),
252887
+ run_in_background: boolean$1().optional().describe("Compatibility option. When task management is available, subagents always run detached so the parent remains responsive."),
252876
252888
  timeout_minutes: number$1().int().min(0).max(10080).optional().describe("Wall-clock interval before the same agent is continued automatically. Defaults to 240 minutes. Set 0 to disable the interval for this run.")
252877
252889
  }));
252878
252890
  object({
@@ -252908,14 +252920,18 @@ var init_agent$1 = __esmMin((() => {
252908
252920
  let profileName = args.subagent_type?.length ? args.subagent_type : "coder";
252909
252921
  const resumeAgentId = args.resume?.trim();
252910
252922
  if (resumeAgentId !== void 0 && resumeAgentId.length > 0) profileName = await this.subagentHost.getProfileName?.(resumeAgentId) ?? "subagent";
252923
+ const { runInBackground } = resolveSubagentRunMode({
252924
+ allowBackground: this.allowBackground,
252925
+ requested: args.run_in_background
252926
+ });
252911
252927
  return {
252912
- description: `${args.run_in_background === true ? "Launching background" : "Launching"} ${profileName} agent: ${args.description}`,
252928
+ description: `${runInBackground ? "Launching background" : "Launching"} ${profileName} agent: ${args.description}`,
252913
252929
  accesses: ToolAccesses.none(),
252914
252930
  display: {
252915
252931
  kind: "agent_call",
252916
252932
  agent_name: profileName,
252917
252933
  prompt: args.prompt,
252918
- background: args.run_in_background
252934
+ background: runInBackground
252919
252935
  },
252920
252936
  approvalRule: this.name,
252921
252937
  matchesRule: (ruleArgs) => matchesGlobRuleSubject(ruleArgs, profileName),
@@ -252925,14 +252941,17 @@ var init_agent$1 = __esmMin((() => {
252925
252941
  async execution(args, { toolCallId, signal }) {
252926
252942
  try {
252927
252943
  signal.throwIfAborted();
252928
- const runInBackground = args.run_in_background === true;
252944
+ const { runInBackground } = resolveSubagentRunMode({
252945
+ allowBackground: this.allowBackground,
252946
+ requested: args.run_in_background
252947
+ });
252929
252948
  const requestedProfileName = args.subagent_type?.length ? args.subagent_type : void 0;
252930
252949
  const resumeAgentId = args.resume?.trim();
252931
252950
  if (resumeAgentId !== void 0 && resumeAgentId.length > 0 && requestedProfileName !== void 0) return {
252932
252951
  output: "Cannot set subagent_type when resuming an existing agent. Resume by agent id only.",
252933
252952
  isError: true
252934
252953
  };
252935
- if (runInBackground && !this.allowBackground) return {
252954
+ if (args.run_in_background === true && !this.allowBackground) return {
252936
252955
  output: BACKGROUND_AGENT_UNAVAILABLE,
252937
252956
  isError: true
252938
252957
  };
@@ -265752,10 +265771,10 @@ var init_agent = __esmMin((() => {
265752
265771
  return this.resolveEffectiveSystemPrompt(this.config.systemPrompt, this.runtimeSystemPromptAppend);
265753
265772
  }
265754
265773
  get fastConversationSystemPrompt() {
265755
- return [personaSystemBlock(), soulSystemBlock(), identitySystemBlock(), naturalPresenceSystemBlock(), conductSystemBlock(), `## Fast conversation mode
265774
+ return [defaultAnswerLanguageSystemBlock(), personaSystemBlock(), soulSystemBlock(), identitySystemBlock(), naturalPresenceSystemBlock(), conductSystemBlock(), `## Fast conversation mode
265756
265775
 
265757
265776
  - Answer the user's current conversational message directly and concisely.
265758
- - Use the language of the user's latest message unless the persona specifies a language override.
265777
+ - Use the language of the user's latest conversational message unless the persona or current session specifies a language override.
265759
265778
  - Do not claim to have inspected files, run commands, used tools, or checked current external state.
265760
265779
  - If the message actually requires work or live information, say that plainly instead of inventing a result.`].filter((block) => block.length > 0).join("\n\n");
265761
265780
  }
@@ -342506,7 +342525,7 @@ const DEFAULT_INLINE_SUGGEST_LANGUAGES = {
342506
342525
  };
342507
342526
  const DEFAULT_TUI_CONFIG = TuiConfigSchema.parse({
342508
342527
  theme: "auto",
342509
- uiLocale: "auto",
342528
+ uiLocale: "en",
342510
342529
  disablePasteBurst: false,
342511
342530
  editorCommand: null,
342512
342531
  notifications: DEFAULT_NOTIFICATIONS_CONFIG,
@@ -419351,6 +419370,7 @@ var { projectUnaddressedTelegramContext } = createRequire(import.meta.url)("./bi
419351
419370
  var { enqueueTelegramUrgent, rewriteTelegramUrgentEnvelope, telegramUrgentMessage } = createRequire(import.meta.url)("./bin/telegram-urgent-policy.cjs");
419352
419371
  var { createDirectFocusController, createDirectReplyTurnStop, enqueueTelegramDirect, readDirectFocusCheckpoint, rewriteTelegramDirectEnvelope, telegramDirectMessage, writeDirectFocusCheckpoint } = createRequire(import.meta.url)("./bin/telegram-direct-focus-policy.cjs");
419353
419372
  var { enqueueTelegramAddressed } = createRequire(import.meta.url)("./bin/telegram-addressed-priority.cjs");
419373
+ var { createAddressedChannelFocusStore } = createRequire(import.meta.url)("./bin/telegram-addressed-focus.cjs");
419354
419374
  var { removeQueuedReloadCommands, removeQueuedSlashCommands } = createRequire(import.meta.url)("./bin/reload-queue-policy.cjs");
419355
419375
  const MAX_BUFFERED_CONTEXT_MESSAGES = 20;
419356
419376
  const MAX_BUFFERED_CONTEXT_CHARS = 24e3;
@@ -501856,6 +501876,23 @@ var QueuePaneComponent = class extends Container {
501856
501876
  return lines;
501857
501877
  }
501858
501878
  };
501879
+ var AddressedChannelFocusPaneComponent = class extends Container {
501880
+ items;
501881
+ constructor(items) {
501882
+ super();
501883
+ this.items = items;
501884
+ }
501885
+ render(width) {
501886
+ const bar = chalk.hex(ACCENT)(LEFT_BAR$1);
501887
+ return this.items.slice(-3).map((item) => {
501888
+ const origin = item.origin.trim().length > 0 ? `${item.origin.trim()} ` : "Telegram ";
501889
+ const body = item.text.replaceAll(/\s+/g, " ").trim();
501890
+ const prefix = `${bar} ${currentTheme.fg("primary", "!")} ${currentTheme.fg("textStrong", origin)}`;
501891
+ const available = Math.max(1, width - visibleWidth(prefix));
501892
+ return truncateToWidth(`${prefix}${truncateToWidth(body, available, ELLIPSIS$3)}`, width, ELLIPSIS$3);
501893
+ });
501894
+ }
501895
+ };
501859
501896
  //#endregion
501860
501897
  //#region src/tui/utils/refresh-providers.ts
501861
501898
  init_src$3();
@@ -515205,6 +515242,7 @@ function createTUIState(options) {
515205
515242
  ui.setSurfaceEnabled(initialAppState.appearance?.surface !== void 0);
515206
515243
  const transcriptContainer = new GutterContainer(1, 1);
515207
515244
  const activityContainer = new GutterContainer(1, 1);
515245
+ const channelFocusContainer = new GutterContainer(1, 1);
515208
515246
  const todoPanelContainer = new GutterContainer(1, 1);
515209
515247
  const todoPanel = new TodoPanelComponent();
515210
515248
  const queueContainer = new GutterContainer(1, 1);
@@ -515222,6 +515260,7 @@ function createTUIState(options) {
515222
515260
  terminal,
515223
515261
  transcriptContainer,
515224
515262
  activityContainer,
515263
+ channelFocusContainer,
515225
515264
  todoPanelContainer,
515226
515265
  todoPanel,
515227
515266
  queueContainer,
@@ -516735,6 +516774,9 @@ var BlunTUI = class {
516735
516774
  this.startupTranscriptNotice = startupInput.startupTranscriptNotice;
516736
516775
  this.onStartupNoticeShown = startupInput.onStartupNoticeShown;
516737
516776
  this.state = createTUIState(tuiOptions);
516777
+ this.addressedChannelFocusStore = createAddressedChannelFocusStore({
516778
+ onChange: () => this.updateAddressedChannelFocus()
516779
+ });
516738
516780
  this.state.loopIndicator.setRefreshHandler(() => {
516739
516781
  void this.refreshLoopState();
516740
516782
  });
@@ -517405,6 +517447,7 @@ var BlunTUI = class {
517405
517447
  ui.clear();
517406
517448
  ui.addChild(this.state.transcriptContainer);
517407
517449
  ui.addChild(this.state.activityContainer);
517450
+ ui.addChild(this.state.channelFocusContainer);
517408
517451
  ui.addChild(this.state.todoPanelContainer);
517409
517452
  ui.addChild(this.state.queueContainer);
517410
517453
  ui.addChild(this.state.quotaWarningContainer);
@@ -517413,7 +517456,7 @@ var BlunTUI = class {
517413
517456
  ui.addChild(this.state.editorContainer);
517414
517457
  if (ui instanceof BottomPinnedTUI) {
517415
517458
  ui.pinFromIndex = ui.children.indexOf(this.state.quotaWarningContainer);
517416
- ui.chromeFromIndex = ui.children.indexOf(this.state.todoPanelContainer);
517459
+ ui.chromeFromIndex = ui.children.indexOf(this.state.channelFocusContainer);
517417
517460
  ui.overlayRowsProvider = () => this.state.editor.getAutocompleteRows();
517418
517461
  for (const delay of [
517419
517462
  0,
@@ -517625,7 +517668,8 @@ var BlunTUI = class {
517625
517668
  outboxMarker: outboxMarker(),
517626
517669
  transcriptStart: this.state.transcriptEntries.length,
517627
517670
  contextOnly: false,
517628
- directFocus: queued.channelDirect === true
517671
+ directFocus: queued.channelDirect === true,
517672
+ channelFocusId: queued.channelFocusId
517629
517673
  }]);
517630
517674
  this.pendingChannelReplyGuards.push(...installedGuards);
517631
517675
  const inFlight = {
@@ -517679,12 +517723,13 @@ var BlunTUI = class {
517679
517723
  }
517680
517724
  return true;
517681
517725
  }
517682
- installChannelReplyGuard(chatId, contextOnly) {
517726
+ installChannelReplyGuard(chatId, contextOnly, channelFocusId) {
517683
517727
  const guard = {
517684
517728
  chatId,
517685
517729
  outboxMarker: outboxMarker(),
517686
517730
  transcriptStart: this.state.transcriptEntries.length,
517687
- contextOnly
517731
+ contextOnly,
517732
+ channelFocusId
517688
517733
  };
517689
517734
  this.pendingChannelReplyGuards.push(guard);
517690
517735
  return guard;
@@ -517693,6 +517738,18 @@ var BlunTUI = class {
517693
517738
  const index = this.pendingChannelReplyGuards.indexOf(guard);
517694
517739
  if (index >= 0) this.pendingChannelReplyGuards.splice(index, 1);
517695
517740
  }
517741
+ pinAddressedChannelFocus(chatId, origin, text) {
517742
+ return this.addressedChannelFocusStore.pin({ chatId, origin, text });
517743
+ }
517744
+ clearAddressedChannelFocus(chatId) {
517745
+ return this.addressedChannelFocusStore.clearChat(chatId);
517746
+ }
517747
+ updateAddressedChannelFocus() {
517748
+ this.state.channelFocusContainer.clear();
517749
+ const items = this.addressedChannelFocusStore.snapshot();
517750
+ if (items.length > 0) this.state.channelFocusContainer.addChild(new AddressedChannelFocusPaneComponent(items));
517751
+ this.state.ui.requestRender();
517752
+ }
517696
517753
  commitQueuedSteerAtStepStart(turnId) {
517697
517754
  const inFlight = this.queueSteerInFlight;
517698
517755
  if (inFlight === void 0 || inFlight.turnId !== String(turnId)) return;
@@ -517975,11 +518032,12 @@ var BlunTUI = class {
517975
518032
  this.injectTelegramRemoteCommand(envelope, remoteCommand, acknowledge);
517976
518033
  return;
517977
518034
  }
518035
+ const channelFocusId = addressed && urgentEnvelope === void 0 && directEnvelope === void 0 ? this.pinAddressedChannelFocus(routedEnvelope.meta.chat_id, channelOrigin(routedEnvelope), envelope.text) : void 0;
517978
518036
  injectChannelEnvelope({
517979
518037
  canDeliver: () => this.session !== void 0 && this.state.appState.model.trim().length > 0,
517980
518038
  isBusy: () => this.state.queuedMessages.length > 0 || this.queueSteerInFlight !== void 0 || this.deferUserMessages || this.streamingUI.hasActiveTurn() || this.state.appState.streamingPhase !== "idle" || this.state.appState.isCompacting,
517981
518039
  deliverNow: (modelInput, displayText, origin, contextOnly, channelReportSources) => {
517982
- this.sendChannelMessageInternal(this.requireSession(), modelInput, displayText, origin, routedEnvelope.meta.chat_id, routedEnvelope.meta["image_path"], contextOnly, false, acknowledge, false, void 0, directEnvelope !== void 0, directFocus.resumeGranted, channelReportSources);
518040
+ this.sendChannelMessageInternal(this.requireSession(), modelInput, displayText, origin, routedEnvelope.meta.chat_id, routedEnvelope.meta["image_path"], contextOnly, false, acknowledge, false, void 0, directEnvelope !== void 0, directFocus.resumeGranted, channelReportSources, channelFocusId);
517983
518041
  },
517984
518042
  enqueue: (modelInput, displayText, origin, contextOnly, channelReportSources) => {
517985
518043
  const item = {
@@ -517992,6 +518050,7 @@ var BlunTUI = class {
517992
518050
  channelReportSources: channelReportSources,
517993
518051
  channelContextOnly: contextOnly,
517994
518052
  channelAcknowledge: acknowledge,
518053
+ ...channelFocusId === void 0 ? {} : { channelFocusId },
517995
518054
  ...urgentEnvelope !== void 0 ? { channelUrgent: true } : {},
517996
518055
  ...directEnvelope !== void 0 ? { channelDirect: true, channelDirectResume: directFocus.resumeGranted } : {},
517997
518056
  ...addressed && urgentEnvelope === void 0 && directEnvelope === void 0 ? { channelAddressed: true } : {},
@@ -518149,7 +518208,7 @@ var BlunTUI = class {
518149
518208
  this.failSessionRequest(uiText("blunTui.session.sendFailed", { error: formatErrorMessage$2(error) }));
518150
518209
  });
518151
518210
  }
518152
- sendChannelMessageInternal(session, modelInput, displayText, origin, channelChatId, channelImagePath, contextOnly = false, transcriptRendered = false, acknowledge, channelAttention = false, queueKey, channelDirect = false, channelDirectResume = false, channelReportSources = []) {
518211
+ sendChannelMessageInternal(session, modelInput, displayText, origin, channelChatId, channelImagePath, contextOnly = false, transcriptRendered = false, acknowledge, channelAttention = false, queueKey, channelDirect = false, channelDirectResume = false, channelReportSources = [], channelFocusId) {
518153
518212
  armPersonalMemoryRememberIntent(session.id, displayText, {
518154
518213
  permissionMode: this.state.appState.permissionMode,
518155
518214
  channel: true
@@ -518169,7 +518228,8 @@ var BlunTUI = class {
518169
518228
  outboxMarker: outboxMarker(),
518170
518229
  transcriptStart: this.state.transcriptEntries.length,
518171
518230
  contextOnly,
518172
- directFocus: channelDirect
518231
+ directFocus: channelDirect,
518232
+ channelFocusId
518173
518233
  };
518174
518234
  if (installedGuard !== void 0) this.pendingChannelReplyGuard = installedGuard;
518175
518235
  const imagePart = channelImagePath !== void 0 ? buildChannelImagePart(channelImagePath) : void 0;
@@ -518200,6 +518260,7 @@ var BlunTUI = class {
518200
518260
  channelContextOnly: contextOnly,
518201
518261
  channelTranscriptRendered: true,
518202
518262
  channelAcknowledge: acknowledge,
518263
+ ...channelFocusId === void 0 ? {} : { channelFocusId, channelAddressed: true },
518203
518264
  ...channelAttention === true ? { channelAttention: true } : {},
518204
518265
  ...channelDirect === true ? { channelDirect: true, channelDirectResume } : {},
518205
518266
  ...queueKey === void 0 ? {} : { queueKey },
@@ -518292,6 +518353,7 @@ var BlunTUI = class {
518292
518353
  const args = entry.toolCallData?.args;
518293
518354
  return String(args?.["chat_id"] ?? args?.["chatId"] ?? "") === guard.chatId;
518294
518355
  }) || outboxGrewForChat(guard.outboxMarker, guard.chatId)) {
518356
+ this.clearAddressedChannelFocus?.(guard.chatId);
518295
518357
  if (guard.directFocus === true) this.directFocusController.noteReplyDelivered(guard.chatId);
518296
518358
  continue;
518297
518359
  }
@@ -518317,6 +518379,7 @@ var BlunTUI = class {
518317
518379
  if (finalText.length === 0) continue;
518318
518380
  sendReplyFallback(guard.chatId, finalText, guard.contextOnly).then((sent) => {
518319
518381
  if (!sent) return;
518382
+ this.clearAddressedChannelFocus?.(guard.chatId);
518320
518383
  if (guard.directFocus === true) this.directFocusController.noteReplyDelivered(guard.chatId);
518321
518384
  this.showStatus(uiText("blunTui.telegram.fallbackDelivered"));
518322
518385
  });
@@ -518560,7 +518623,7 @@ var BlunTUI = class {
518560
518623
  const activeSession = this.session ?? session;
518561
518624
  if (item.mode === "channel") {
518562
518625
  this.harness.withInteractiveAgent(item.agentId ?? "main", () => {
518563
- this.sendChannelMessageInternal(activeSession, item.text, item.displayText ?? "", item.origin, item.channelChatId, item.channelImagePath, item.channelContextOnly, item.channelTranscriptRendered, item.channelAcknowledge, item.channelAttention, item.queueKey, item.channelDirect, item.channelDirectResume, item.channelReportSources);
518626
+ this.sendChannelMessageInternal(activeSession, item.text, item.displayText ?? "", item.origin, item.channelChatId, item.channelImagePath, item.channelContextOnly, item.channelTranscriptRendered, item.channelAcknowledge, item.channelAttention, item.queueKey, item.channelDirect, item.channelDirectResume, item.channelReportSources, item.channelFocusId);
518564
518627
  });
518565
518628
  return;
518566
518629
  }
@@ -520746,7 +520809,10 @@ function main() {
520746
520809
  });
520747
520810
  }).parse(process.argv);
520748
520811
  }
520749
- main();
520812
+ function isDirectCliEntry(argvEntry = process.argv[1], moduleFilename = __filename) {
520813
+ return typeof argvEntry === "string" && argvEntry.length > 0 && path.resolve(argvEntry) === path.resolve(moduleFilename);
520814
+ }
520815
+ if (isDirectCliEntry()) main();
520750
520816
  async function logStartupFailure(operation, error) {
520751
520817
  log.error("startup failed", {
520752
520818
  operation,
@@ -520757,4 +520823,4 @@ async function logStartupFailure(operation, error) {
520757
520823
  } catch {}
520758
520824
  }
520759
520825
  //#endregion
520760
- export { configureTelegramPluginLaunchMode, handleMainCommand, main };
520826
+ export { configureTelegramPluginLaunchMode, createBlunHarness, createBlunHostIdentity, handleMainCommand, isDirectCliEntry, main };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blun-king-cli",
3
- "version": "9.1.463",
3
+ "version": "9.1.464",
4
4
  "description": "BLUN CLI - your own AI agent with a Telegram channel. Get it done. With BLUN.",
5
5
  "license": "MIT",
6
6
  "bin": {