@sheepbun/yips 0.1.1

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 (99) hide show
  1. package/dist/agent/commands/command-catalog.js +243 -0
  2. package/dist/agent/commands/commands.js +418 -0
  3. package/dist/agent/conductor.js +118 -0
  4. package/dist/agent/context/code-context.js +68 -0
  5. package/dist/agent/context/memory-store.js +159 -0
  6. package/dist/agent/context/session-store.js +211 -0
  7. package/dist/agent/protocol/tool-protocol.js +160 -0
  8. package/dist/agent/skills/skills.js +327 -0
  9. package/dist/agent/tools/tool-executor.js +415 -0
  10. package/dist/agent/tools/tool-safety.js +52 -0
  11. package/dist/app/index.js +35 -0
  12. package/dist/app/repl.js +105 -0
  13. package/dist/app/update-check.js +132 -0
  14. package/dist/app/version.js +51 -0
  15. package/dist/code-context.js +68 -0
  16. package/dist/colors.js +204 -0
  17. package/dist/command-catalog.js +242 -0
  18. package/dist/commands.js +350 -0
  19. package/dist/conductor.js +94 -0
  20. package/dist/config/config.js +335 -0
  21. package/dist/config/hooks.js +187 -0
  22. package/dist/config.js +335 -0
  23. package/dist/downloader-state.js +302 -0
  24. package/dist/downloader-ui.js +289 -0
  25. package/dist/gateway/adapters/discord.js +108 -0
  26. package/dist/gateway/adapters/formatting.js +96 -0
  27. package/dist/gateway/adapters/telegram.js +106 -0
  28. package/dist/gateway/adapters/types.js +2 -0
  29. package/dist/gateway/adapters/whatsapp.js +124 -0
  30. package/dist/gateway/auth-policy.js +66 -0
  31. package/dist/gateway/core.js +87 -0
  32. package/dist/gateway/headless-conductor.js +328 -0
  33. package/dist/gateway/message-router.js +23 -0
  34. package/dist/gateway/rate-limiter.js +48 -0
  35. package/dist/gateway/runtime/backend-policy.js +18 -0
  36. package/dist/gateway/runtime/discord-bot.js +104 -0
  37. package/dist/gateway/runtime/discord-main.js +69 -0
  38. package/dist/gateway/session-manager.js +77 -0
  39. package/dist/gateway/types.js +2 -0
  40. package/dist/hardware.js +92 -0
  41. package/dist/hooks.js +187 -0
  42. package/dist/index.js +34 -0
  43. package/dist/input-engine.js +250 -0
  44. package/dist/llama-client.js +227 -0
  45. package/dist/llama-server.js +620 -0
  46. package/dist/llm/llama-client.js +227 -0
  47. package/dist/llm/llama-server.js +620 -0
  48. package/dist/llm/token-counter.js +47 -0
  49. package/dist/memory-store.js +159 -0
  50. package/dist/messages.js +59 -0
  51. package/dist/model-downloader.js +382 -0
  52. package/dist/model-manager-state.js +118 -0
  53. package/dist/model-manager-ui.js +194 -0
  54. package/dist/model-manager.js +190 -0
  55. package/dist/models/hardware.js +92 -0
  56. package/dist/models/model-downloader.js +382 -0
  57. package/dist/models/model-manager.js +190 -0
  58. package/dist/prompt-box.js +78 -0
  59. package/dist/prompt-composer.js +498 -0
  60. package/dist/repl.js +105 -0
  61. package/dist/session-store.js +211 -0
  62. package/dist/spinner.js +76 -0
  63. package/dist/title-box.js +388 -0
  64. package/dist/token-counter.js +47 -0
  65. package/dist/tool-executor.js +415 -0
  66. package/dist/tool-protocol.js +121 -0
  67. package/dist/tool-safety.js +52 -0
  68. package/dist/tui/app.js +2553 -0
  69. package/dist/tui/startup.js +56 -0
  70. package/dist/tui-input-routing.js +53 -0
  71. package/dist/tui.js +51 -0
  72. package/dist/types/app-types.js +2 -0
  73. package/dist/types.js +2 -0
  74. package/dist/ui/colors.js +204 -0
  75. package/dist/ui/downloader/downloader-state.js +302 -0
  76. package/dist/ui/downloader/downloader-ui.js +289 -0
  77. package/dist/ui/input/input-engine.js +250 -0
  78. package/dist/ui/input/tui-input-routing.js +53 -0
  79. package/dist/ui/input/vt-session.js +168 -0
  80. package/dist/ui/messages.js +59 -0
  81. package/dist/ui/model-manager/model-manager-state.js +118 -0
  82. package/dist/ui/model-manager/model-manager-ui.js +194 -0
  83. package/dist/ui/prompt/prompt-box.js +78 -0
  84. package/dist/ui/prompt/prompt-composer.js +498 -0
  85. package/dist/ui/spinner.js +76 -0
  86. package/dist/ui/title-box.js +388 -0
  87. package/dist/ui/tui/app.js +6 -0
  88. package/dist/ui/tui/autocomplete.js +85 -0
  89. package/dist/ui/tui/constants.js +18 -0
  90. package/dist/ui/tui/history.js +29 -0
  91. package/dist/ui/tui/layout.js +341 -0
  92. package/dist/ui/tui/runtime-core.js +2584 -0
  93. package/dist/ui/tui/runtime-utils.js +53 -0
  94. package/dist/ui/tui/start-tui.js +54 -0
  95. package/dist/ui/tui/startup.js +56 -0
  96. package/dist/version.js +51 -0
  97. package/dist/vt-session.js +168 -0
  98. package/install.sh +457 -0
  99. package/package.json +128 -0
@@ -0,0 +1,124 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.WhatsAppAdapter = void 0;
4
+ const formatting_1 = require("#gateway/adapters/formatting");
5
+ const DEFAULT_API_BASE_URL = "https://graph.facebook.com";
6
+ const DEFAULT_API_VERSION = "v21.0";
7
+ const DEFAULT_MAX_MESSAGE_LENGTH = 4000;
8
+ function isObject(value) {
9
+ return typeof value === "object" && value !== null;
10
+ }
11
+ function parseTimestamp(value) {
12
+ if (!value) {
13
+ return undefined;
14
+ }
15
+ const seconds = Number.parseInt(value, 10);
16
+ if (!Number.isFinite(seconds)) {
17
+ return undefined;
18
+ }
19
+ return new Date(seconds * 1000);
20
+ }
21
+ function findContactByWaId(contacts, waId) {
22
+ if (!contacts || contacts.length === 0) {
23
+ return undefined;
24
+ }
25
+ return contacts.find((contact) => contact.wa_id === waId);
26
+ }
27
+ function toInboundMessage(message, value, entryId) {
28
+ if (message.type !== "text") {
29
+ return null;
30
+ }
31
+ const senderId = message.from;
32
+ const text = message.text?.body;
33
+ if (typeof senderId !== "string" || typeof text !== "string") {
34
+ return null;
35
+ }
36
+ const contact = findContactByWaId(value.contacts, senderId);
37
+ return {
38
+ platform: "whatsapp",
39
+ senderId,
40
+ channelId: value.metadata?.phone_number_id,
41
+ text,
42
+ messageId: message.id,
43
+ timestamp: parseTimestamp(message.timestamp),
44
+ metadata: {
45
+ waId: contact?.wa_id ?? senderId,
46
+ profileName: contact?.profile?.name,
47
+ displayPhoneNumber: value.metadata?.display_phone_number,
48
+ phoneNumberId: value.metadata?.phone_number_id,
49
+ entryId
50
+ }
51
+ };
52
+ }
53
+ function parseInboundMessages(payload) {
54
+ if (!isObject(payload)) {
55
+ return [];
56
+ }
57
+ const envelope = payload;
58
+ if (!Array.isArray(envelope.entry)) {
59
+ return [];
60
+ }
61
+ const messages = [];
62
+ for (const entry of envelope.entry) {
63
+ if (!Array.isArray(entry.changes)) {
64
+ continue;
65
+ }
66
+ for (const change of entry.changes) {
67
+ const value = change.value;
68
+ if (!value || !Array.isArray(value.messages)) {
69
+ continue;
70
+ }
71
+ for (const message of value.messages) {
72
+ const inbound = toInboundMessage(message, value, entry.id);
73
+ if (inbound) {
74
+ messages.push(inbound);
75
+ }
76
+ }
77
+ }
78
+ }
79
+ return messages;
80
+ }
81
+ class WhatsAppAdapter {
82
+ platform = "whatsapp";
83
+ accessToken;
84
+ phoneNumberId;
85
+ apiBaseUrl;
86
+ apiVersion;
87
+ maxMessageLength;
88
+ constructor(options) {
89
+ this.accessToken = options.accessToken.trim();
90
+ this.phoneNumberId = options.phoneNumberId.trim();
91
+ this.apiBaseUrl = (options.apiBaseUrl ?? DEFAULT_API_BASE_URL).trim().replace(/\/+$/, "");
92
+ this.apiVersion = (options.apiVersion ?? DEFAULT_API_VERSION).trim();
93
+ this.maxMessageLength = Math.max(1, Math.trunc(options.maxMessageLength ?? DEFAULT_MAX_MESSAGE_LENGTH));
94
+ }
95
+ parseInbound(payload) {
96
+ return parseInboundMessages(payload);
97
+ }
98
+ formatOutbound(context, response) {
99
+ const normalizedText = (0, formatting_1.normalizeOutboundText)(response.text);
100
+ const chunks = (0, formatting_1.chunkOutboundText)(normalizedText, this.maxMessageLength);
101
+ if (chunks.length === 0) {
102
+ return null;
103
+ }
104
+ const requests = chunks.map((chunk) => ({
105
+ method: "POST",
106
+ endpoint: `${this.apiBaseUrl}/${this.apiVersion}/${this.phoneNumberId}/messages`,
107
+ headers: {
108
+ authorization: `Bearer ${this.accessToken}`,
109
+ "content-type": "application/json"
110
+ },
111
+ body: {
112
+ messaging_product: "whatsapp",
113
+ recipient_type: "individual",
114
+ to: context.message.senderId,
115
+ type: "text",
116
+ text: {
117
+ body: chunk
118
+ }
119
+ }
120
+ }));
121
+ return requests.length === 1 ? (requests[0] ?? null) : requests;
122
+ }
123
+ }
124
+ exports.WhatsAppAdapter = WhatsAppAdapter;
@@ -0,0 +1,66 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.GatewayAuthPolicy = void 0;
4
+ function toSenderKey(message) {
5
+ return `${message.platform}:${message.senderId}`;
6
+ }
7
+ function parseAuthCommand(text) {
8
+ const trimmed = text.trim();
9
+ if (!trimmed.startsWith("/auth")) {
10
+ return null;
11
+ }
12
+ const parts = trimmed.split(/\s+/);
13
+ if (parts[0] !== "/auth") {
14
+ return null;
15
+ }
16
+ return parts[1] ?? "";
17
+ }
18
+ class GatewayAuthPolicy {
19
+ allowedSenderIds;
20
+ passphrase;
21
+ authenticatedSenders = new Set();
22
+ constructor(options) {
23
+ this.allowedSenderIds =
24
+ options.allowedSenderIds && options.allowedSenderIds.length > 0
25
+ ? new Set(options.allowedSenderIds)
26
+ : null;
27
+ this.passphrase = options.passphrase?.trim() ? options.passphrase.trim() : null;
28
+ }
29
+ evaluate(message) {
30
+ if (this.allowedSenderIds && !this.allowedSenderIds.has(message.senderId)) {
31
+ return {
32
+ type: "unauthorized",
33
+ reason: "sender_not_allowed"
34
+ };
35
+ }
36
+ if (!this.passphrase) {
37
+ return {
38
+ type: "authorized"
39
+ };
40
+ }
41
+ const senderKey = toSenderKey(message);
42
+ if (this.authenticatedSenders.has(senderKey)) {
43
+ return {
44
+ type: "authorized"
45
+ };
46
+ }
47
+ const authAttempt = parseAuthCommand(message.text);
48
+ if (authAttempt === null) {
49
+ return {
50
+ type: "unauthorized",
51
+ reason: "passphrase_required"
52
+ };
53
+ }
54
+ if (authAttempt !== this.passphrase) {
55
+ return {
56
+ type: "unauthorized",
57
+ reason: "passphrase_invalid"
58
+ };
59
+ }
60
+ this.authenticatedSenders.add(senderKey);
61
+ return {
62
+ type: "authenticated"
63
+ };
64
+ }
65
+ }
66
+ exports.GatewayAuthPolicy = GatewayAuthPolicy;
@@ -0,0 +1,87 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.GatewayCore = void 0;
4
+ const auth_policy_1 = require("#gateway/auth-policy");
5
+ const message_router_1 = require("#gateway/message-router");
6
+ const rate_limiter_1 = require("#gateway/rate-limiter");
7
+ const session_manager_1 = require("#gateway/session-manager");
8
+ class GatewayCore {
9
+ authPolicy;
10
+ unauthorizedMessage;
11
+ rateLimiter;
12
+ sessionManager;
13
+ handleMessageFn;
14
+ constructor(options) {
15
+ this.authPolicy = new auth_policy_1.GatewayAuthPolicy({
16
+ allowedSenderIds: options.allowedSenderIds,
17
+ passphrase: options.passphrase
18
+ });
19
+ this.unauthorizedMessage =
20
+ options.unauthorizedMessage ??
21
+ "Access denied. Authenticate with /auth <passphrase> or contact the administrator.";
22
+ this.rateLimiter =
23
+ options.rateLimiter ??
24
+ new rate_limiter_1.GatewayRateLimiter({
25
+ maxMessages: 20,
26
+ windowMs: 60_000
27
+ });
28
+ this.sessionManager = options.sessionManager ?? new session_manager_1.GatewaySessionManager();
29
+ this.handleMessageFn = options.handleMessage;
30
+ }
31
+ async dispatch(message) {
32
+ const normalizedMessage = (0, message_router_1.normalizeIncomingMessage)(message);
33
+ const validationError = (0, message_router_1.validateIncomingMessage)(normalizedMessage);
34
+ if (validationError) {
35
+ return {
36
+ status: "invalid",
37
+ reason: validationError
38
+ };
39
+ }
40
+ const authDecision = this.authPolicy.evaluate(normalizedMessage);
41
+ if (authDecision.type === "authenticated") {
42
+ return {
43
+ status: "authenticated",
44
+ response: {
45
+ text: "Authentication successful. You can now send messages."
46
+ }
47
+ };
48
+ }
49
+ if (authDecision.type === "unauthorized") {
50
+ return {
51
+ status: "unauthorized",
52
+ reason: authDecision.reason,
53
+ response: {
54
+ text: this.unauthorizedMessage
55
+ }
56
+ };
57
+ }
58
+ const rate = this.rateLimiter.check(normalizedMessage.senderId);
59
+ if (!rate.allowed) {
60
+ return {
61
+ status: "rate_limited",
62
+ reason: "rate_limit_exceeded",
63
+ retryAfterMs: rate.retryAfterMs
64
+ };
65
+ }
66
+ const session = this.sessionManager.getOrCreateSession(normalizedMessage);
67
+ const response = await this.handleMessageFn({
68
+ message: normalizedMessage,
69
+ session
70
+ });
71
+ return {
72
+ status: "ok",
73
+ sessionId: session.id,
74
+ response
75
+ };
76
+ }
77
+ listSessions() {
78
+ return this.sessionManager.listSessions();
79
+ }
80
+ pruneIdleSessions(maxIdleMs) {
81
+ return this.sessionManager.pruneIdleSessions(maxIdleMs);
82
+ }
83
+ pruneRateLimiterState() {
84
+ return this.rateLimiter.pruneStaleCounters();
85
+ }
86
+ }
87
+ exports.GatewayCore = GatewayCore;
@@ -0,0 +1,328 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.GatewayHeadlessConductor = void 0;
4
+ exports.createGatewayHeadlessMessageHandler = createGatewayHeadlessMessageHandler;
5
+ const conductor_1 = require("#agent/conductor");
6
+ const code_context_1 = require("#agent/context/code-context");
7
+ const session_store_1 = require("#agent/context/session-store");
8
+ const skills_1 = require("#agent/skills/skills");
9
+ const tool_executor_1 = require("#agent/tools/tool-executor");
10
+ const tool_safety_1 = require("#agent/tools/tool-safety");
11
+ const llama_server_1 = require("#llm/llama-server");
12
+ const llama_client_1 = require("#llm/llama-client");
13
+ const token_counter_1 = require("#llm/token-counter");
14
+ const vt_session_1 = require("#ui/input/vt-session");
15
+ const UNSUPPORTED_BACKEND_RESPONSE = "Gateway headless mode currently supports backend 'llamacpp' only.";
16
+ const AUTO_DENY_OUTPUT = "Action denied by gateway safety policy.";
17
+ function composeChatRequestMessages(history, codeContextMessage) {
18
+ if (!codeContextMessage) {
19
+ return history;
20
+ }
21
+ return [{ role: "system", content: codeContextMessage }, ...history];
22
+ }
23
+ function buildSubagentScopeMessage(call) {
24
+ const lines = [
25
+ "Subagent scope:",
26
+ `Task: ${call.task}`,
27
+ call.context ? `Context: ${call.context}` : null,
28
+ call.allowedTools
29
+ ? `Allowed tools: ${call.allowedTools.length > 0 ? call.allowedTools.join(", ") : "(none)"}`
30
+ : "Allowed tools: all available tools",
31
+ "Stay focused on the delegated scope and return concise findings."
32
+ ].filter((line) => line !== null);
33
+ return lines.join("\n");
34
+ }
35
+ function assessToolCallRisk(call, workingZone) {
36
+ if (call.name === "run_command") {
37
+ const command = typeof call.arguments["command"] === "string" ? call.arguments["command"] : "";
38
+ const cwdArg = typeof call.arguments["cwd"] === "string" ? call.arguments["cwd"] : ".";
39
+ const resolvedCwd = (0, tool_safety_1.resolveToolPath)(cwdArg, workingZone);
40
+ return (0, tool_safety_1.assessCommandRisk)(command, resolvedCwd, workingZone);
41
+ }
42
+ const pathArg = typeof call.arguments["path"] === "string" ? call.arguments["path"] : ".";
43
+ return (0, tool_safety_1.assessPathRisk)(pathArg, workingZone);
44
+ }
45
+ function toErrorMessage(error) {
46
+ return error instanceof Error ? error.message : String(error);
47
+ }
48
+ const DEFAULT_DEPS = {
49
+ createLlamaClient: (config) => new llama_client_1.LlamaClient({
50
+ baseUrl: config.llamaBaseUrl,
51
+ model: config.model
52
+ }),
53
+ ensureReady: llama_server_1.ensureLlamaReady,
54
+ formatStartupFailure: llama_server_1.formatLlamaStartupFailure,
55
+ runConductor: conductor_1.runConductorTurn,
56
+ executeTool: tool_executor_1.executeToolCall,
57
+ executeSkill: skills_1.executeSkillCall,
58
+ createVtSession: () => new vt_session_1.VirtualTerminalSession(),
59
+ loadCodeContext: code_context_1.loadCodeContext,
60
+ toCodeContextSystemMessage: code_context_1.toCodeContextSystemMessage,
61
+ createSessionFile: session_store_1.createSessionFileFromHistory,
62
+ writeSessionFile: session_store_1.writeSessionFile,
63
+ estimateCompletionTokens: (text) => (0, token_counter_1.estimateConversationTokens)([{ content: text }]),
64
+ estimateHistoryTokens: (history) => (0, token_counter_1.estimateConversationTokens)(history)
65
+ };
66
+ class GatewayHeadlessConductor {
67
+ config;
68
+ gatewayBackend;
69
+ username;
70
+ workingDirectory;
71
+ maxRounds;
72
+ deps;
73
+ sessionStates = new Map();
74
+ llamaClient;
75
+ vtSession;
76
+ codeContextMessage = null;
77
+ constructor(options, deps = {}) {
78
+ this.config = options.config;
79
+ this.gatewayBackend = options.gatewayBackend ?? this.config.backend;
80
+ this.username = options.username ?? "Gateway User";
81
+ this.workingDirectory = options.workingDirectory ?? process.cwd();
82
+ this.maxRounds = options.maxRounds;
83
+ this.deps = { ...DEFAULT_DEPS, ...deps };
84
+ this.llamaClient = this.deps.createLlamaClient(this.config);
85
+ this.vtSession = this.deps.createVtSession();
86
+ }
87
+ async initialize() {
88
+ const loaded = await this.deps.loadCodeContext(this.workingDirectory);
89
+ if (!loaded) {
90
+ this.codeContextMessage = null;
91
+ return;
92
+ }
93
+ this.codeContextMessage = this.deps.toCodeContextSystemMessage(loaded);
94
+ }
95
+ async handleMessage(context) {
96
+ if (this.gatewayBackend !== "llamacpp") {
97
+ return {
98
+ text: `${UNSUPPORTED_BACKEND_RESPONSE} Configured backend: '${this.gatewayBackend}'.`
99
+ };
100
+ }
101
+ const state = this.getOrCreateSessionState(context);
102
+ state.history.push({ role: "user", content: context.message.text });
103
+ let latestAssistantText = "";
104
+ try {
105
+ await this.deps.runConductor({
106
+ history: state.history,
107
+ requestAssistant: async () => {
108
+ const readiness = await this.deps.ensureReady(this.config);
109
+ if (!readiness.ready) {
110
+ throw new Error(readiness.failure
111
+ ? this.deps.formatStartupFailure(readiness.failure, this.config)
112
+ : "llama.cpp is unavailable.");
113
+ }
114
+ this.llamaClient.setModel(this.config.model);
115
+ const requestMessages = composeChatRequestMessages(state.history, this.codeContextMessage);
116
+ const result = await this.llamaClient.chat(requestMessages, this.config.model);
117
+ return {
118
+ text: result.text,
119
+ rendered: false,
120
+ totalTokens: result.usage?.totalTokens,
121
+ completionTokens: result.usage?.completionTokens ?? this.deps.estimateCompletionTokens(result.text)
122
+ };
123
+ },
124
+ executeToolCalls: async (toolCalls) => {
125
+ const results = [];
126
+ for (const call of toolCalls) {
127
+ const risk = assessToolCallRisk(call, this.workingDirectory);
128
+ if (risk.requiresConfirmation) {
129
+ results.push({
130
+ callId: call.id,
131
+ tool: call.name,
132
+ status: "denied",
133
+ output: AUTO_DENY_OUTPUT
134
+ });
135
+ continue;
136
+ }
137
+ const result = await this.deps.executeTool(call, {
138
+ workingDirectory: this.workingDirectory,
139
+ vtSession: this.vtSession
140
+ });
141
+ results.push(result);
142
+ }
143
+ return results;
144
+ },
145
+ executeSkillCalls: async (skillCalls) => {
146
+ const results = [];
147
+ for (const call of skillCalls) {
148
+ const result = await this.deps.executeSkill(call, {
149
+ workingDirectory: this.workingDirectory,
150
+ vtSession: this.vtSession
151
+ });
152
+ results.push(result);
153
+ }
154
+ return results;
155
+ },
156
+ executeSubagentCalls: async (subagentCalls) => {
157
+ const results = [];
158
+ for (const subagentCall of subagentCalls) {
159
+ const scopedHistory = [
160
+ { role: "system", content: buildSubagentScopeMessage(subagentCall) },
161
+ { role: "user", content: subagentCall.task }
162
+ ];
163
+ const allowedTools = subagentCall.allowedTools !== undefined ? new Set(subagentCall.allowedTools) : null;
164
+ try {
165
+ const turn = await this.deps.runConductor({
166
+ history: scopedHistory,
167
+ requestAssistant: async () => {
168
+ const readiness = await this.deps.ensureReady(this.config);
169
+ if (!readiness.ready) {
170
+ throw new Error(readiness.failure
171
+ ? this.deps.formatStartupFailure(readiness.failure, this.config)
172
+ : "llama.cpp is unavailable.");
173
+ }
174
+ this.llamaClient.setModel(this.config.model);
175
+ const requestMessages = composeChatRequestMessages(scopedHistory, null);
176
+ const result = await this.llamaClient.chat(requestMessages, this.config.model);
177
+ return {
178
+ text: result.text,
179
+ rendered: false,
180
+ totalTokens: result.usage?.totalTokens,
181
+ completionTokens: result.usage?.completionTokens ??
182
+ this.deps.estimateCompletionTokens(result.text)
183
+ };
184
+ },
185
+ executeToolCalls: async (toolCalls) => {
186
+ const denied = [];
187
+ const permitted = [];
188
+ for (const call of toolCalls) {
189
+ if (allowedTools && !allowedTools.has(call.name)) {
190
+ denied.push({
191
+ callId: call.id,
192
+ tool: call.name,
193
+ status: "denied",
194
+ output: `Tool '${call.name}' is not allowed for subagent ${subagentCall.id}.`
195
+ });
196
+ continue;
197
+ }
198
+ const risk = assessToolCallRisk(call, this.workingDirectory);
199
+ if (risk.requiresConfirmation) {
200
+ denied.push({
201
+ callId: call.id,
202
+ tool: call.name,
203
+ status: "denied",
204
+ output: AUTO_DENY_OUTPUT
205
+ });
206
+ continue;
207
+ }
208
+ permitted.push(call);
209
+ }
210
+ const executed = [];
211
+ for (const call of permitted) {
212
+ const result = await this.deps.executeTool(call, {
213
+ workingDirectory: this.workingDirectory,
214
+ vtSession: this.vtSession
215
+ });
216
+ executed.push(result);
217
+ }
218
+ return [...denied, ...executed];
219
+ },
220
+ executeSkillCalls: async (skillCalls) => {
221
+ const skillResults = [];
222
+ for (const call of skillCalls) {
223
+ const result = await this.deps.executeSkill(call, {
224
+ workingDirectory: this.workingDirectory,
225
+ vtSession: this.vtSession
226
+ });
227
+ skillResults.push(result);
228
+ }
229
+ return skillResults;
230
+ },
231
+ onAssistantText: () => {
232
+ // Subagent responses are returned in metadata only.
233
+ },
234
+ onWarning: () => {
235
+ // Warnings are intentionally omitted from outbound gateway text.
236
+ },
237
+ estimateCompletionTokens: this.deps.estimateCompletionTokens,
238
+ estimateHistoryTokens: this.deps.estimateHistoryTokens,
239
+ computeTokensPerSecond: () => null,
240
+ maxRounds: subagentCall.maxRounds ?? 4
241
+ });
242
+ const latest = [...scopedHistory]
243
+ .reverse()
244
+ .find((entry) => entry.role === "assistant")?.content;
245
+ results.push({
246
+ callId: subagentCall.id,
247
+ status: turn.finished ? "ok" : "timeout",
248
+ output: latest ?? "Subagent completed without assistant output.",
249
+ metadata: {
250
+ rounds: turn.rounds
251
+ }
252
+ });
253
+ }
254
+ catch (error) {
255
+ results.push({
256
+ callId: subagentCall.id,
257
+ status: "error",
258
+ output: `Subagent failed: ${toErrorMessage(error)}`
259
+ });
260
+ }
261
+ }
262
+ return results;
263
+ },
264
+ onAssistantText: (assistantText) => {
265
+ latestAssistantText = assistantText;
266
+ },
267
+ onWarning: () => {
268
+ // Warnings are intentionally omitted from outbound gateway text.
269
+ },
270
+ estimateCompletionTokens: this.deps.estimateCompletionTokens,
271
+ estimateHistoryTokens: this.deps.estimateHistoryTokens,
272
+ computeTokensPerSecond: () => null,
273
+ maxRounds: this.maxRounds
274
+ });
275
+ }
276
+ catch (error) {
277
+ const message = `Request failed: ${toErrorMessage(error)}`;
278
+ state.history.push({ role: "assistant", content: message });
279
+ await this.persistSession(state);
280
+ return { text: message };
281
+ }
282
+ const finalText = latestAssistantText.trim().length > 0 ? latestAssistantText : "(no response)";
283
+ await this.persistSession(state);
284
+ return { text: finalText };
285
+ }
286
+ dispose() {
287
+ this.vtSession.dispose();
288
+ this.sessionStates.clear();
289
+ }
290
+ getOrCreateSessionState(context) {
291
+ const key = `${context.session.id}:${context.session.createdAt.toISOString()}`;
292
+ const existing = this.sessionStates.get(key);
293
+ if (existing) {
294
+ return existing;
295
+ }
296
+ const created = {
297
+ history: [],
298
+ sessionFilePath: null
299
+ };
300
+ this.sessionStates.set(key, created);
301
+ return created;
302
+ }
303
+ async persistSession(state) {
304
+ if (state.history.length === 0) {
305
+ return;
306
+ }
307
+ if (!state.sessionFilePath) {
308
+ const created = await this.deps.createSessionFile(state.history);
309
+ state.sessionFilePath = created.path;
310
+ }
311
+ await this.deps.writeSessionFile({
312
+ path: state.sessionFilePath,
313
+ username: this.username,
314
+ history: state.history
315
+ });
316
+ }
317
+ }
318
+ exports.GatewayHeadlessConductor = GatewayHeadlessConductor;
319
+ async function createGatewayHeadlessMessageHandler(options, deps = {}) {
320
+ const runtime = new GatewayHeadlessConductor(options, deps);
321
+ await runtime.initialize();
322
+ return {
323
+ handleMessage: async (context) => await runtime.handleMessage(context),
324
+ dispose: () => {
325
+ runtime.dispose();
326
+ }
327
+ };
328
+ }
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.normalizeIncomingMessage = normalizeIncomingMessage;
4
+ exports.validateIncomingMessage = validateIncomingMessage;
5
+ const MAX_TEXT_LENGTH = 4000;
6
+ function normalizeIncomingMessage(message) {
7
+ return {
8
+ ...message,
9
+ senderId: message.senderId.trim(),
10
+ channelId: message.channelId?.trim(),
11
+ text: message.text.trim().slice(0, MAX_TEXT_LENGTH),
12
+ timestamp: message.timestamp ?? new Date()
13
+ };
14
+ }
15
+ function validateIncomingMessage(message) {
16
+ if (message.senderId.trim().length === 0) {
17
+ return "sender_id_required";
18
+ }
19
+ if (message.text.trim().length === 0) {
20
+ return "message_text_required";
21
+ }
22
+ return null;
23
+ }
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.GatewayRateLimiter = void 0;
4
+ class GatewayRateLimiter {
5
+ maxMessages;
6
+ windowMs;
7
+ counters = new Map();
8
+ now;
9
+ constructor(options, now = () => new Date()) {
10
+ this.maxMessages = Math.max(1, Math.trunc(options.maxMessages));
11
+ this.windowMs = Math.max(1000, Math.trunc(options.windowMs));
12
+ this.now = now;
13
+ }
14
+ check(senderId) {
15
+ const now = this.now().valueOf();
16
+ const existing = this.counters.get(senderId);
17
+ const active = existing && now - existing.startedAt < this.windowMs ? existing : { count: 0, startedAt: now };
18
+ active.count += 1;
19
+ this.counters.set(senderId, active);
20
+ const resetAt = new Date(active.startedAt + this.windowMs);
21
+ if (active.count <= this.maxMessages) {
22
+ return {
23
+ allowed: true,
24
+ remaining: this.maxMessages - active.count,
25
+ retryAfterMs: 0,
26
+ resetAt
27
+ };
28
+ }
29
+ return {
30
+ allowed: false,
31
+ remaining: 0,
32
+ retryAfterMs: Math.max(1, active.startedAt + this.windowMs - now),
33
+ resetAt
34
+ };
35
+ }
36
+ pruneStaleCounters() {
37
+ const now = this.now().valueOf();
38
+ let removed = 0;
39
+ for (const [senderId, window] of this.counters) {
40
+ if (now - window.startedAt >= this.windowMs) {
41
+ this.counters.delete(senderId);
42
+ removed += 1;
43
+ }
44
+ }
45
+ return removed;
46
+ }
47
+ }
48
+ exports.GatewayRateLimiter = GatewayRateLimiter;