@kafca/agentdock 0.1.7

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 (79) hide show
  1. package/README.md +70 -0
  2. package/bin/agentdock.mjs +275 -0
  3. package/build/icon.png +0 -0
  4. package/dist/renderer/assets/index-BaNpzC6d.js +482 -0
  5. package/dist/renderer/assets/index-CLWJyO0E.css +11 -0
  6. package/dist/renderer/favicon.svg +19 -0
  7. package/dist/renderer/index.html +14 -0
  8. package/dist/renderer/logo-options.svg +153 -0
  9. package/dist-electron/electron/knowledge-skill-script.test.js +125 -0
  10. package/dist-electron/electron/lac-cli.test.js +221 -0
  11. package/dist-electron/electron/local-core-refactor.test.js +1075 -0
  12. package/dist-electron/electron/main.js +163 -0
  13. package/dist-electron/electron/plugin-kernel.test.js +534 -0
  14. package/dist-electron/electron/preload.js +2 -0
  15. package/dist-electron/package.json +3 -0
  16. package/dist-electron/packages/contracts/src/index.js +18 -0
  17. package/dist-electron/packages/contracts/src/local-core.js +2 -0
  18. package/dist-electron/packages/core-sdk/src/index.js +406 -0
  19. package/dist-electron/packages/knowledge-api/src/ai-vector-provider.js +388 -0
  20. package/dist-electron/packages/knowledge-api/src/index.js +146 -0
  21. package/dist-electron/packages/knowledge-api/src/sqlite-store.js +467 -0
  22. package/dist-electron/packages/knowledge-api/src/thread-knowledge-store.js +21 -0
  23. package/dist-electron/packages/knowledge-api/test/ai-vector-provider.test.js +412 -0
  24. package/dist-electron/packages/plugin-sdk/src/index.js +2 -0
  25. package/dist-electron/services/local-ai-core/src/acp/local-core-acp-backend.js +369 -0
  26. package/dist-electron/services/local-ai-core/src/acp/local-core-acp-response-processor.js +110 -0
  27. package/dist-electron/services/local-ai-core/src/acp/local-core-acp-session-coordinator.js +171 -0
  28. package/dist-electron/services/local-ai-core/src/acp/local-core-acp-store.js +635 -0
  29. package/dist-electron/services/local-ai-core/src/acp/local-core-acp-transport.js +190 -0
  30. package/dist-electron/services/local-ai-core/src/acp/local-core-acp-turn-coordinator.js +244 -0
  31. package/dist-electron/services/local-ai-core/src/acp/workspace-acp-permissions.js +52 -0
  32. package/dist-electron/services/local-ai-core/src/cli/lac.js +290 -0
  33. package/dist-electron/services/local-ai-core/src/gateway/local-core-lark-gateway.js +976 -0
  34. package/dist-electron/services/local-ai-core/src/gateway/local-core-weixin-gateway.js +1143 -0
  35. package/dist-electron/services/local-ai-core/src/kernel/bootstrap.js +291 -0
  36. package/dist-electron/services/local-ai-core/src/kernel/capability-registry.js +67 -0
  37. package/dist-electron/services/local-ai-core/src/kernel/diagnostics.js +27 -0
  38. package/dist-electron/services/local-ai-core/src/kernel/event-bus.js +27 -0
  39. package/dist-electron/services/local-ai-core/src/kernel/lifecycle-manager.js +85 -0
  40. package/dist-electron/services/local-ai-core/src/kernel/plugin-registry.js +60 -0
  41. package/dist-electron/services/local-ai-core/src/plugins/builtin/agent-localcore-acp-plugin.js +114 -0
  42. package/dist-electron/services/local-ai-core/src/plugins/builtin/channel-lark-plugin.js +59 -0
  43. package/dist-electron/services/local-ai-core/src/plugins/builtin/channel-weixin-plugin.js +56 -0
  44. package/dist-electron/services/local-ai-core/src/plugins/builtin/knowledge-ai-vector-plugin.js +7 -0
  45. package/dist-electron/services/local-ai-core/src/plugins/builtin/knowledge-noop-plugin.js +7 -0
  46. package/dist-electron/services/local-ai-core/src/plugins/builtin/runtime-capabilities-plugin.js +62 -0
  47. package/dist-electron/services/local-ai-core/src/plugins/builtin/scheduler-cron-plugin.js +49 -0
  48. package/dist-electron/services/local-ai-core/src/plugins/builtin/scheduler-lark-plugin.js +38 -0
  49. package/dist-electron/services/local-ai-core/src/plugins/builtin/scheduler-weixin-plugin.js +38 -0
  50. package/dist-electron/services/local-ai-core/src/router/workspace-route-config.js +230 -0
  51. package/dist-electron/services/local-ai-core/src/router/workspace-router-types.js +2 -0
  52. package/dist-electron/services/local-ai-core/src/router/workspace-router.js +389 -0
  53. package/dist-electron/services/local-ai-core/src/runtime/local-core-controller.js +315 -0
  54. package/dist-electron/services/local-ai-core/src/runtime/local-core-runtime-state.js +282 -0
  55. package/dist-electron/services/local-ai-core/src/runtime/server.js +465 -0
  56. package/dist-electron/services/local-ai-core/src/runtime/standalone.js +38 -0
  57. package/dist-electron/services/local-ai-core/src/scheduler/adapters.js +2 -0
  58. package/dist-electron/services/local-ai-core/src/scheduler/cron-command-detector.js +70 -0
  59. package/dist-electron/services/local-ai-core/src/scheduler/cron.js +53 -0
  60. package/dist-electron/services/local-ai-core/src/scheduler/execution-policy.js +2 -0
  61. package/dist-electron/services/local-ai-core/src/scheduler/lark-execution-policies.js +66 -0
  62. package/dist-electron/services/local-ai-core/src/scheduler/lark-schedule-adapter.js +77 -0
  63. package/dist-electron/services/local-ai-core/src/scheduler/scheduled-conversation-executor.js +46 -0
  64. package/dist-electron/services/local-ai-core/src/scheduler/scheduler-run-lifecycle.js +64 -0
  65. package/dist-electron/services/local-ai-core/src/scheduler/scheduler-service.js +133 -0
  66. package/dist-electron/services/local-ai-core/src/scheduler/weixin-execution-policies.js +66 -0
  67. package/dist-electron/services/local-ai-core/src/scheduler/weixin-schedule-adapter.js +78 -0
  68. package/dist-electron/services/local-ai-core/src/thread/workspace-thread-id.js +17 -0
  69. package/dist-electron/services/local-ai-core/src/thread/workspace-thread-mappers.js +43 -0
  70. package/dist-electron/shared/desktop.js +195 -0
  71. package/dist-electron/src/api/client.js +73 -0
  72. package/dist-electron/src/api/sessions.js +21 -0
  73. package/dist-electron/src/lib/session-utils.js +51 -0
  74. package/dist-electron/src/pages/Threads/thread-chat-model.js +246 -0
  75. package/dist-electron/src/pages/Threads/thread-chat-model.test.js +76 -0
  76. package/dist-electron/src/pages/Threads/thread-chat-permission.js +26 -0
  77. package/dist-electron/src/pages/Threads/thread-chat-permission.test.js +102 -0
  78. package/dist-electron/src/pages/Threads/thread-chat-task-state.js +65 -0
  79. package/package.json +146 -0
@@ -0,0 +1,190 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.LocalCoreAcpTransport = void 0;
4
+ const node_child_process_1 = require("node:child_process");
5
+ class LocalCoreAcpTransport {
6
+ options;
7
+ constructor(options) {
8
+ this.options = options;
9
+ }
10
+ spawnSession(input) {
11
+ const baseEnv = {
12
+ ...process.env,
13
+ ...input.config.env,
14
+ };
15
+ const child = (0, node_child_process_1.spawn)(input.config.command, input.config.args, {
16
+ cwd: input.config.workDir,
17
+ env: {
18
+ ...baseEnv,
19
+ ...input.runtimeEnv,
20
+ },
21
+ stdio: ['pipe', 'pipe', 'pipe'],
22
+ });
23
+ const session = {
24
+ child,
25
+ requestId: 0,
26
+ stdoutBuffer: '',
27
+ pending: new Map(),
28
+ sessionId: '',
29
+ supportsLoad: false,
30
+ workspaceId: input.config.workspaceId,
31
+ threadId: input.threadId,
32
+ bridgeSessionKey: input.bridgeSessionKey,
33
+ currentRunId: null,
34
+ currentTurn: null,
35
+ loadReplayMode: false,
36
+ pendingPermissionByRun: new Map(),
37
+ schedulerJobCreatedByRun: new Map(),
38
+ closed: false,
39
+ closeReason: null,
40
+ promptPromise: null,
41
+ };
42
+ child.stdout.setEncoding('utf8');
43
+ child.stdout.on('data', (chunk) => this.handleStdout(session, chunk));
44
+ child.stderr.setEncoding('utf8');
45
+ child.stderr.on('data', (chunk) => {
46
+ this.options.log?.(`[localcore-acp:${input.threadId}] ${chunk.trimEnd()}`);
47
+ });
48
+ child.stdin.on('error', (error) => {
49
+ this.handlePipeFailure(session, error);
50
+ });
51
+ child.on('exit', (code, signal) => {
52
+ this.options.onSessionClosed(session, new Error(`ACP agent exited with code ${code ?? 'unknown'}${signal ? ` (${signal})` : ''}`));
53
+ });
54
+ return session;
55
+ }
56
+ async initializeSession(session) {
57
+ const initResult = await this.request(session, 'initialize', {
58
+ protocolVersion: 1,
59
+ clientCapabilities: {
60
+ fs: {
61
+ readTextFile: false,
62
+ writeTextFile: false,
63
+ },
64
+ terminal: false,
65
+ },
66
+ clientInfo: {
67
+ name: 'agentdock',
68
+ title: 'AgentDock',
69
+ version: '0.1.0',
70
+ },
71
+ }, 30000);
72
+ session.supportsLoad = Boolean(initResult?.agentCapabilities?.loadSession);
73
+ }
74
+ request(session, method, params, timeoutMs = 30000) {
75
+ session.requestId += 1;
76
+ const id = session.requestId;
77
+ return new Promise((resolve, reject) => {
78
+ const timeout = setTimeout(() => {
79
+ session.pending.delete(id);
80
+ reject(new Error(`Timed out waiting for ACP ${method} after ${timeoutMs}ms`));
81
+ }, timeoutMs);
82
+ session.pending.set(id, {
83
+ resolve: (value) => {
84
+ clearTimeout(timeout);
85
+ resolve(value);
86
+ },
87
+ reject: (error) => {
88
+ clearTimeout(timeout);
89
+ reject(error);
90
+ },
91
+ });
92
+ if (!this.sendRaw(session, {
93
+ jsonrpc: '2.0',
94
+ id,
95
+ method,
96
+ params,
97
+ })) {
98
+ session.pending.delete(id);
99
+ reject(new Error(session.closeReason || 'ACP session is not writable'));
100
+ }
101
+ });
102
+ }
103
+ sendRaw(session, payload) {
104
+ if (session.closed || !session.child.stdin.writable) {
105
+ return false;
106
+ }
107
+ try {
108
+ session.child.stdin.write(`${JSON.stringify(payload)}\n`, (error) => {
109
+ if (error) {
110
+ this.handlePipeFailure(session, error);
111
+ }
112
+ });
113
+ return true;
114
+ }
115
+ catch (error) {
116
+ this.handlePipeFailure(session, error);
117
+ return false;
118
+ }
119
+ }
120
+ closeSession(session, reason = 'ACP session closed') {
121
+ if (session.closed) {
122
+ return;
123
+ }
124
+ session.closed = true;
125
+ session.closeReason = reason;
126
+ if (!session.child.killed) {
127
+ session.child.kill('SIGTERM');
128
+ }
129
+ }
130
+ closeSessionWithError(session, error) {
131
+ if (session.closed) {
132
+ return;
133
+ }
134
+ session.closed = true;
135
+ session.closeReason = error.message;
136
+ for (const pending of session.pending.values()) {
137
+ pending.reject(error);
138
+ }
139
+ session.pending.clear();
140
+ if (!session.child.killed) {
141
+ session.child.kill('SIGTERM');
142
+ }
143
+ }
144
+ handleStdout(session, chunk) {
145
+ session.stdoutBuffer += chunk;
146
+ while (session.stdoutBuffer.includes('\n')) {
147
+ const newlineIndex = session.stdoutBuffer.indexOf('\n');
148
+ const line = session.stdoutBuffer.slice(0, newlineIndex).trim();
149
+ session.stdoutBuffer = session.stdoutBuffer.slice(newlineIndex + 1);
150
+ if (!line) {
151
+ continue;
152
+ }
153
+ let payload;
154
+ try {
155
+ payload = JSON.parse(line);
156
+ }
157
+ catch (error) {
158
+ this.options.log?.(`ACP stdout parse failed: ${error instanceof Error ? error.message : String(error)}`);
159
+ continue;
160
+ }
161
+ if (payload.method && payload.id !== undefined) {
162
+ this.options.onAgentRequest(session, payload);
163
+ continue;
164
+ }
165
+ if (payload.method) {
166
+ this.options.onAgentNotification(session, payload);
167
+ continue;
168
+ }
169
+ if (payload.id !== undefined) {
170
+ const pending = session.pending.get(payload.id);
171
+ if (!pending) {
172
+ continue;
173
+ }
174
+ session.pending.delete(payload.id);
175
+ if (payload.error) {
176
+ pending.reject(new Error(payload.error.message || `ACP request failed: ${payload.id}`));
177
+ }
178
+ else {
179
+ pending.resolve(payload.result);
180
+ }
181
+ }
182
+ }
183
+ }
184
+ handlePipeFailure(session, error) {
185
+ const message = error instanceof Error ? error.message : String(error);
186
+ this.options.log?.(`[localcore-acp:${session.threadId}] stdin failure: ${message}`);
187
+ this.options.onSessionClosed(session, new Error(message));
188
+ }
189
+ }
190
+ exports.LocalCoreAcpTransport = LocalCoreAcpTransport;
@@ -0,0 +1,244 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.LocalCoreAcpTurnCoordinator = void 0;
4
+ const desktop_js_1 = require("../../../../shared/desktop.js");
5
+ const workspace_acp_permissions_js_1 = require("./workspace-acp-permissions.js");
6
+ class LocalCoreAcpTurnCoordinator {
7
+ options;
8
+ constructor(options) {
9
+ this.options = options;
10
+ }
11
+ flushPendingToolCall(session) {
12
+ const currentTurn = session.currentTurn;
13
+ const currentRunId = session.currentRunId;
14
+ const title = currentTurn?.pendingToolCallTitle?.trim();
15
+ if (!currentTurn || !currentRunId || !title) {
16
+ return;
17
+ }
18
+ currentTurn.pendingToolCallTitle = undefined;
19
+ this.emitProgress(session, currentRunId, `🔧 ${title}`);
20
+ }
21
+ getPendingPermissionRequest(session, detail) {
22
+ const runId = session?.currentRunId;
23
+ if (!session || !runId) {
24
+ return null;
25
+ }
26
+ const pendingPermission = session.pendingPermissionByRun.get(runId);
27
+ if (!pendingPermission) {
28
+ return null;
29
+ }
30
+ const latestAssistantMessage = [...detail.messages].reverse().find((message) => message.role === 'assistant');
31
+ return {
32
+ id: latestAssistantMessage?.id || `${runId}-buttons`,
33
+ content: latestAssistantMessage?.content || 'Permission required before continuing.',
34
+ actions: (0, workspace_acp_permissions_js_1.toPermissionButtonRows)(pendingPermission.options, desktop_js_1.normalizeDesktopBridgeButtonOption),
35
+ actionReplyCtx: runId,
36
+ actionPending: false,
37
+ actionStatus: undefined,
38
+ actionMode: 'permission',
39
+ actionInteractive: true,
40
+ };
41
+ }
42
+ handleAgentRequest(session, payload) {
43
+ if (payload.method !== 'session/request_permission') {
44
+ this.options.sendRaw(session, {
45
+ jsonrpc: '2.0',
46
+ id: payload.id,
47
+ error: {
48
+ code: -32601,
49
+ message: `Unsupported ACP client method: ${String(payload.method || '')}`,
50
+ },
51
+ });
52
+ return;
53
+ }
54
+ const currentRunId = session.currentRunId;
55
+ if (!currentRunId) {
56
+ this.options.sendRaw(session, {
57
+ jsonrpc: '2.0',
58
+ id: payload.id,
59
+ result: {
60
+ outcome: {
61
+ outcome: 'cancelled',
62
+ },
63
+ },
64
+ });
65
+ return;
66
+ }
67
+ const options = Array.isArray(payload.params?.options)
68
+ ? payload.params.options
69
+ .map((option) => ({
70
+ optionId: String(option?.optionId || '').trim(),
71
+ name: String(option?.name || option?.optionId || '').trim(),
72
+ kind: String(option?.kind || '').trim(),
73
+ normalizedAction: (0, workspace_acp_permissions_js_1.normalizePermissionAction)(option?.kind),
74
+ }))
75
+ .filter((option) => option.optionId)
76
+ : [];
77
+ const toolTitle = (0, workspace_acp_permissions_js_1.formatToolCallContent)(payload.params?.toolCall);
78
+ const isSchedulerAdd = isSchedulerAddCommand(toolTitle);
79
+ if (isSchedulerAdd && session.schedulerJobCreatedByRun.get(currentRunId)) {
80
+ this.options.sendRaw(session, {
81
+ jsonrpc: '2.0',
82
+ id: payload.id,
83
+ result: {
84
+ outcome: {
85
+ outcome: 'cancelled',
86
+ },
87
+ },
88
+ });
89
+ const content = '已限制本次对话只创建一个定时任务,额外的 scheduler add 请求已自动取消。';
90
+ this.options.appendMessage(session.threadId, 'assistant', content, 'progress');
91
+ this.options.emitBridge({
92
+ type: 'reply',
93
+ sessionKey: session.bridgeSessionKey,
94
+ replyCtx: currentRunId,
95
+ content,
96
+ });
97
+ return;
98
+ }
99
+ const buttonRows = (0, workspace_acp_permissions_js_1.toPermissionButtonRows)(options, desktop_js_1.normalizeDesktopBridgeButtonOption);
100
+ const permissionRequest = {
101
+ requestId: payload.id,
102
+ toolTitle,
103
+ isSchedulerAdd,
104
+ options,
105
+ };
106
+ session.pendingPermissionByRun.set(currentRunId, permissionRequest);
107
+ if (session.currentTurn) {
108
+ session.currentTurn.permission = permissionRequest;
109
+ }
110
+ this.options.updateRunStatus(currentRunId, session.threadId, 'awaiting_input');
111
+ const permissionPrompt = [
112
+ '等待工具确认',
113
+ '',
114
+ toolTitle,
115
+ '',
116
+ '请选择一个选项继续执行。',
117
+ '',
118
+ '若按钮没有显示,请直接回复:allow all / allow / deny',
119
+ ].join('\n');
120
+ this.options.appendMessage(session.threadId, 'assistant', permissionPrompt, 'progress');
121
+ this.options.emitBridge({
122
+ type: 'buttons',
123
+ sessionKey: session.bridgeSessionKey,
124
+ replyCtx: currentRunId,
125
+ content: permissionPrompt,
126
+ buttonRows,
127
+ });
128
+ }
129
+ handleAgentNotification(session, payload) {
130
+ if (session.loadReplayMode || payload.method !== 'session/update') {
131
+ return;
132
+ }
133
+ const update = payload.params?.update;
134
+ const currentTurn = session.currentTurn;
135
+ const currentRunId = session.currentRunId;
136
+ if (!update || !currentTurn || !currentRunId) {
137
+ return;
138
+ }
139
+ switch (String(update.sessionUpdate || '')) {
140
+ case 'agent_message_chunk': {
141
+ this.flushPendingToolCall(session);
142
+ if (update.content?.type !== 'text') {
143
+ return;
144
+ }
145
+ currentTurn.assistantText += String(update.content.text || '');
146
+ if (!currentTurn.previewStarted) {
147
+ currentTurn.previewStarted = true;
148
+ this.options.emitBridge({
149
+ type: 'preview_start',
150
+ sessionKey: session.bridgeSessionKey,
151
+ replyCtx: currentRunId,
152
+ previewHandle: currentTurn.previewHandle,
153
+ content: currentTurn.assistantText,
154
+ });
155
+ return;
156
+ }
157
+ this.options.emitBridge({
158
+ type: 'update_message',
159
+ sessionKey: session.bridgeSessionKey,
160
+ replyCtx: currentRunId,
161
+ previewHandle: currentTurn.previewHandle,
162
+ content: currentTurn.assistantText,
163
+ });
164
+ return;
165
+ }
166
+ case 'tool_call': {
167
+ const title = String(update.title || 'Running tool').trim();
168
+ this.flushPendingToolCall(session);
169
+ currentTurn.pendingToolCallTitle = title;
170
+ return;
171
+ }
172
+ case 'tool_call_update': {
173
+ const title = String(update.title || 'Tool update').trim();
174
+ const status = String(update.status || '').trim();
175
+ const content = this.extractToolUpdateContent(update.content);
176
+ if (isSchedulerAddCommand(title) && /Created scheduler job\b/.test(content)) {
177
+ session.schedulerJobCreatedByRun.set(currentRunId, true);
178
+ }
179
+ const toolName = currentTurn.pendingToolCallTitle?.trim();
180
+ if (this.isEmptyRunningToolUpdate(title, status, content)) {
181
+ currentTurn.pendingToolCallTitle = undefined;
182
+ return;
183
+ }
184
+ currentTurn.pendingToolCallTitle = undefined;
185
+ this.emitProgress(session, currentRunId, this.formatToolProgressMessage(toolName, title, status, content));
186
+ return;
187
+ }
188
+ case 'plan': {
189
+ this.flushPendingToolCall(session);
190
+ const entries = Array.isArray(update.entries) ? update.entries : [];
191
+ if (entries.length === 0) {
192
+ return;
193
+ }
194
+ const summary = entries
195
+ .map((entry) => String(entry?.content || '').trim())
196
+ .filter(Boolean)
197
+ .join(' | ');
198
+ const content = `💭 ${summary}`;
199
+ this.options.appendMessage(session.threadId, 'assistant', content, 'progress');
200
+ this.options.emitBridge({
201
+ type: 'reply',
202
+ sessionKey: session.bridgeSessionKey,
203
+ replyCtx: currentRunId,
204
+ content,
205
+ });
206
+ return;
207
+ }
208
+ default:
209
+ return;
210
+ }
211
+ }
212
+ emitProgress(session, currentRunId, content) {
213
+ this.options.appendMessage(session.threadId, 'assistant', content, 'progress');
214
+ this.options.emitBridge({
215
+ type: 'reply',
216
+ sessionKey: session.bridgeSessionKey,
217
+ replyCtx: currentRunId,
218
+ content,
219
+ });
220
+ }
221
+ extractToolUpdateContent(content) {
222
+ return Array.isArray(content)
223
+ ? content
224
+ .map((entry) => entry?.type === 'content' && entry?.content?.type === 'text'
225
+ ? String(entry.content.text || '')
226
+ : '')
227
+ .filter(Boolean)
228
+ .join('\n')
229
+ : '';
230
+ }
231
+ formatToolProgressMessage(toolName, title, status, content) {
232
+ const detail = [title, status, content].filter(Boolean).join(' - ');
233
+ return toolName ? `🔧 ${toolName}: ${detail || 'Tool update'}` : `🔧 ${detail || 'Tool update'}`;
234
+ }
235
+ isEmptyRunningToolUpdate(title, status, content) {
236
+ return !content.trim() &&
237
+ /^running$/i.test(status) &&
238
+ (!title.trim() || /^tool update$/i.test(title));
239
+ }
240
+ }
241
+ exports.LocalCoreAcpTurnCoordinator = LocalCoreAcpTurnCoordinator;
242
+ function isSchedulerAddCommand(value) {
243
+ return /\blac\s+scheduler\s+add\b/.test(String(value || ''));
244
+ }
@@ -0,0 +1,52 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.normalizePermissionAction = normalizePermissionAction;
4
+ exports.formatToolCallContent = formatToolCallContent;
5
+ exports.toPermissionButtonRows = toPermissionButtonRows;
6
+ function normalizePermissionAction(kind) {
7
+ const normalized = String(kind || '').trim().toLowerCase();
8
+ if (normalized === 'allow_always') {
9
+ return 'allow all';
10
+ }
11
+ if (normalized.startsWith('allow')) {
12
+ return 'allow';
13
+ }
14
+ if (normalized.startsWith('reject')) {
15
+ return 'deny';
16
+ }
17
+ return '';
18
+ }
19
+ function formatToolCallContent(toolCall) {
20
+ if (!toolCall || typeof toolCall !== 'object') {
21
+ return 'Permission required before continuing.';
22
+ }
23
+ const title = typeof toolCall.title === 'string' ? toolCall.title.trim() : '';
24
+ const content = Array.isArray(toolCall.content)
25
+ ? toolCall.content
26
+ .map((entry) => {
27
+ if (!entry || typeof entry !== 'object') {
28
+ return '';
29
+ }
30
+ if (typeof entry.text === 'string') {
31
+ return String(entry.text).trim();
32
+ }
33
+ const nested = entry.content;
34
+ return nested?.type === 'text' ? String(nested.text || '').trim() : '';
35
+ })
36
+ .filter(Boolean)
37
+ .join('\n')
38
+ : '';
39
+ return [title, content].filter(Boolean).join('\n\n') || 'Permission required before continuing.';
40
+ }
41
+ function toPermissionButtonRows(options, normalizeButton) {
42
+ return [options.map((option) => {
43
+ const data = option.normalizedAction
44
+ ? `perm:${option.normalizedAction.replace(/\s+/g, '_')}`
45
+ : option.optionId;
46
+ const normalized = normalizeButton({
47
+ text: option.normalizedAction || option.name,
48
+ data,
49
+ });
50
+ return normalized || { text: option.name, data: option.optionId };
51
+ })];
52
+ }