@tencent-ai/agent-harness-linux-arm64-musl 0.1.5 → 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/agent-harness +0 -0
- package/bin/runtime/codebuddy/package.json +1 -1
- package/bin/runtime/codebuddy/product.cloudhosted.json +11 -9
- package/bin/runtime/codebuddy/product.internal.json +11 -6
- package/bin/runtime/codebuddy/product.ioa.json +9 -7
- package/bin/runtime/codebuddy/product.json +42 -8
- package/bin/runtime/codebuddy/product.selfhosted.json +8 -6
- package/bin/vendor/shim/broker-ipc-client.cjs +195 -46
- package/bin/vendor/shim/brokered-bin/cat +32 -4
- package/bin/vendor/shim/brokered-bin/chmod +32 -4
- package/bin/vendor/shim/brokered-bin/codebuddy-toybox-dispatch +32 -4
- package/bin/vendor/shim/brokered-bin/cp +32 -4
- package/bin/vendor/shim/brokered-bin/dd +32 -4
- package/bin/vendor/shim/brokered-bin/find +32 -4
- package/bin/vendor/shim/brokered-bin/grep +32 -4
- package/bin/vendor/shim/brokered-bin/head +32 -4
- package/bin/vendor/shim/brokered-bin/ln +32 -4
- package/bin/vendor/shim/brokered-bin/ls +32 -4
- package/bin/vendor/shim/brokered-bin/mkdir +32 -4
- package/bin/vendor/shim/brokered-bin/mv +32 -4
- package/bin/vendor/shim/brokered-bin/readlink +32 -4
- package/bin/vendor/shim/brokered-bin/realpath +32 -4
- package/bin/vendor/shim/brokered-bin/rm +32 -4
- package/bin/vendor/shim/brokered-bin/rmdir +32 -4
- package/bin/vendor/shim/brokered-bin/sed +32 -4
- package/bin/vendor/shim/brokered-bin/tail +32 -4
- package/bin/vendor/shim/brokered-bin/tee +32 -4
- package/bin/vendor/shim/brokered-bin/touch +32 -4
- package/bin/vendor/shim/brokered-bin/truncate +32 -4
- package/bin/vendor/shim/brokered-bin/unlink +32 -4
- package/bin/vendor/shim/brokered-bin/wc +32 -4
- package/bin/vendor/shim/node-brokered-fs-shim.cjs +133 -32
- package/bin/vendor/shim/node-safe-delete-shim.cjs +7 -1
- package/package.json +1 -1
package/bin/agent-harness
CHANGED
|
Binary file
|
|
@@ -71,7 +71,11 @@
|
|
|
71
71
|
"WaitForMcpServers",
|
|
72
72
|
"Workflow",
|
|
73
73
|
"Monitor",
|
|
74
|
-
"REPL"
|
|
74
|
+
"REPL",
|
|
75
|
+
"A2AGetAgentCard",
|
|
76
|
+
"A2ASendMessage",
|
|
77
|
+
"A2AGetTask",
|
|
78
|
+
"A2ACancelTask"
|
|
75
79
|
],
|
|
76
80
|
"tags": [
|
|
77
81
|
"cli",
|
|
@@ -299,11 +303,9 @@
|
|
|
299
303
|
{
|
|
300
304
|
"name": "Plan",
|
|
301
305
|
"instructions": "agent-plan-instructions",
|
|
302
|
-
"description": "
|
|
306
|
+
"description": "Software architect agent for designing implementation plans. Use this when you need to plan the implementation strategy for a multi-step task before writing code. Returns step-by-step plans, identifies critical files, and considers architectural trade-offs. Read-only: it designs but does not modify files or exit plan mode; the caller reviews the plan and implements it.",
|
|
303
307
|
"tools": [
|
|
304
308
|
"Read",
|
|
305
|
-
"Write",
|
|
306
|
-
"Edit",
|
|
307
309
|
"Bash",
|
|
308
310
|
"PowerShell",
|
|
309
311
|
"Glob",
|
|
@@ -474,7 +476,7 @@
|
|
|
474
476
|
}
|
|
475
477
|
},
|
|
476
478
|
{
|
|
477
|
-
"credits": "x0.
|
|
479
|
+
"credits": "x0.51 credits",
|
|
478
480
|
"id": "deepseek-v4-pro",
|
|
479
481
|
"name": "Deepseek-V4-Pro",
|
|
480
482
|
"vendor": "f",
|
|
@@ -498,7 +500,7 @@
|
|
|
498
500
|
}
|
|
499
501
|
},
|
|
500
502
|
{
|
|
501
|
-
"credits": "x0.
|
|
503
|
+
"credits": "x0.17 credits",
|
|
502
504
|
"id": "deepseek-v4-flash",
|
|
503
505
|
"name": "Deepseek-V4-Flash",
|
|
504
506
|
"vendor": "f",
|
|
@@ -738,7 +740,7 @@
|
|
|
738
740
|
}
|
|
739
741
|
},
|
|
740
742
|
{
|
|
741
|
-
"credits": "x0.
|
|
743
|
+
"credits": "x0.51 credits",
|
|
742
744
|
"id": "deepseek-v4-pro-exclusive",
|
|
743
745
|
"name": "Deepseek-V4-Pro",
|
|
744
746
|
"vendor": "f",
|
|
@@ -820,6 +822,6 @@
|
|
|
820
822
|
"SelectImage": true,
|
|
821
823
|
"SkipToolCallSupportCheck": true
|
|
822
824
|
},
|
|
823
|
-
"commit": "
|
|
824
|
-
"date": "2026-
|
|
825
|
+
"commit": "89709f860396a62d75e8cc01e9514cf221817e79",
|
|
826
|
+
"date": "2026-09-04T10:20:16.694Z"
|
|
825
827
|
}
|
|
@@ -85,7 +85,11 @@
|
|
|
85
85
|
"WaitForMcpServers",
|
|
86
86
|
"Workflow",
|
|
87
87
|
"Monitor",
|
|
88
|
-
"REPL"
|
|
88
|
+
"REPL",
|
|
89
|
+
"A2AGetAgentCard",
|
|
90
|
+
"A2ASendMessage",
|
|
91
|
+
"A2AGetTask",
|
|
92
|
+
"A2ACancelTask"
|
|
89
93
|
],
|
|
90
94
|
"tags": [
|
|
91
95
|
"cli",
|
|
@@ -178,6 +182,9 @@
|
|
|
178
182
|
"name": "terminalTitleGenerator",
|
|
179
183
|
"instructions": "terminal-title-generator-instructions",
|
|
180
184
|
"description": "Generate terminal window titles from conversation topics.",
|
|
185
|
+
"models": [
|
|
186
|
+
"builtin-lite"
|
|
187
|
+
],
|
|
181
188
|
"tags": [
|
|
182
189
|
"cli",
|
|
183
190
|
"terminal-title-generator"
|
|
@@ -314,11 +321,9 @@
|
|
|
314
321
|
{
|
|
315
322
|
"name": "Plan",
|
|
316
323
|
"instructions": "agent-plan-instructions",
|
|
317
|
-
"description": "
|
|
324
|
+
"description": "Software architect agent for designing implementation plans. Use this when you need to plan the implementation strategy for a multi-step task before writing code. Returns step-by-step plans, identifies critical files, and considers architectural trade-offs. Read-only: it designs but does not modify files or exit plan mode; the caller reviews the plan and implements it.",
|
|
318
325
|
"tools": [
|
|
319
326
|
"Read",
|
|
320
|
-
"Write",
|
|
321
|
-
"Edit",
|
|
322
327
|
"Bash",
|
|
323
328
|
"Glob",
|
|
324
329
|
"Grep",
|
|
@@ -827,6 +832,6 @@
|
|
|
827
832
|
}
|
|
828
833
|
}
|
|
829
834
|
},
|
|
830
|
-
"commit": "
|
|
831
|
-
"date": "2026-
|
|
835
|
+
"commit": "89709f860396a62d75e8cc01e9514cf221817e79",
|
|
836
|
+
"date": "2026-09-04T10:20:16.659Z"
|
|
832
837
|
}
|
|
@@ -107,7 +107,11 @@
|
|
|
107
107
|
"WaitForMcpServers",
|
|
108
108
|
"Workflow",
|
|
109
109
|
"Monitor",
|
|
110
|
-
"REPL"
|
|
110
|
+
"REPL",
|
|
111
|
+
"A2AGetAgentCard",
|
|
112
|
+
"A2ASendMessage",
|
|
113
|
+
"A2AGetTask",
|
|
114
|
+
"A2ACancelTask"
|
|
111
115
|
],
|
|
112
116
|
"tags": [
|
|
113
117
|
"cli",
|
|
@@ -205,7 +209,7 @@
|
|
|
205
209
|
"instructions": "terminal-title-generator-instructions",
|
|
206
210
|
"description": "Generate terminal window titles from conversation topics.",
|
|
207
211
|
"models": [
|
|
208
|
-
"lite"
|
|
212
|
+
"builtin-lite"
|
|
209
213
|
],
|
|
210
214
|
"tags": [
|
|
211
215
|
"cli",
|
|
@@ -349,11 +353,9 @@
|
|
|
349
353
|
{
|
|
350
354
|
"name": "Plan",
|
|
351
355
|
"instructions": "agent-plan-instructions",
|
|
352
|
-
"description": "
|
|
356
|
+
"description": "Software architect agent for designing implementation plans. Use this when you need to plan the implementation strategy for a multi-step task before writing code. Returns step-by-step plans, identifies critical files, and considers architectural trade-offs. Read-only: it designs but does not modify files or exit plan mode; the caller reviews the plan and implements it.",
|
|
353
357
|
"tools": [
|
|
354
358
|
"Read",
|
|
355
|
-
"Write",
|
|
356
|
-
"Edit",
|
|
357
359
|
"Bash",
|
|
358
360
|
"PowerShell",
|
|
359
361
|
"Glob",
|
|
@@ -1323,6 +1325,6 @@
|
|
|
1323
1325
|
}
|
|
1324
1326
|
}
|
|
1325
1327
|
},
|
|
1326
|
-
"commit": "
|
|
1327
|
-
"date": "2026-
|
|
1328
|
+
"commit": "89709f860396a62d75e8cc01e9514cf221817e79",
|
|
1329
|
+
"date": "2026-09-04T10:20:16.660Z"
|
|
1328
1330
|
}
|
|
@@ -1041,7 +1041,7 @@
|
|
|
1041
1041
|
},
|
|
1042
1042
|
{
|
|
1043
1043
|
"name": "tool-webfetch-description",
|
|
1044
|
-
"template": "\n- Fetches content from a specified URL and processes it using an AI model\n- Takes a URL and a prompt as input\n- Fetches the URL content, converts HTML to markdown\n- Processes the content with the prompt using a small, fast model\n- Returns the model's response about the content\n- Use this tool when you need to retrieve and analyze web content\n\nUsage notes:\n - IMPORTANT: If an MCP-provided web fetch tool is available, prefer using that tool instead of this one, as it may have fewer restrictions. All MCP-provided tools start with \"mcp__\".\n - The URL must be a fully-formed valid URL\n - HTTP URLs will be automatically upgraded to HTTPS\n - The prompt should describe what information you want to extract from the page\n - This tool is read-only and does not modify any files\n - Results may be summarized if the content is very large\n - Includes a self-cleaning 15-minute cache for faster responses when repeatedly accessing the same URL\n - When a URL redirects to a different host, the tool will inform you and provide the redirect URL in a special format. You should then make a new WebFetch request with the redirect URL to fetch the content.\n"
|
|
1044
|
+
"template": "\n- Fetches content from a specified URL and processes it using an AI model\n- Takes a URL and a prompt as input\n- Fetches the URL content, converts HTML to markdown\n- Processes the content with the prompt using a small, fast model\n- Returns the model's response about the content\n- Use this tool when you need to retrieve and analyze web content\n\nUsage notes:\n - IMPORTANT: If an MCP-provided web fetch tool is available, prefer using that tool instead of this one, as it may have fewer restrictions. All MCP-provided tools start with \"mcp__\".\n - The URL must be a fully-formed valid URL\n - HTTP URLs will be automatically upgraded to HTTPS\n - The prompt should describe what information you want to extract from the page\n - This tool is read-only and does not modify any files\n - Results may be summarized if the content is very large\n - Includes a self-cleaning 15-minute cache for faster responses when repeatedly accessing the same URL\n - When a URL redirects to a different host, the tool will inform you and provide the redirect URL in a special format. You should then make a new WebFetch request with the redirect URL to fetch the content.\n - IMPORTANT: WebFetch is for text-based web content. For direct media or binary URLs, download with Bash, preserve the real extension (infer it from curl's `%{content_type}` if needed), then open with Read.\n"
|
|
1045
1045
|
},
|
|
1046
1046
|
{
|
|
1047
1047
|
"name": "tool-listmcpresources-description",
|
|
@@ -1239,6 +1239,22 @@
|
|
|
1239
1239
|
"name": "tool-deferexecutetool-description",
|
|
1240
1240
|
"template": "Execute a deferred tool by name. Use this to invoke tools discovered via ToolSearch without needing them in the active tools list.\n\nUsage:\n- First use ToolSearch to discover a tool and learn its parameter schema\n- Then call this tool with the exact tool name and parameters\n- Parameters are validated against the tool's schema before execution\n- The target tool's permission checks and hooks are applied normally\n\nExample:\nDeferExecuteTool({ toolName: \"ImageGen\", params: { prompt: \"a sunset over mountains\" } })\n\nNotes:\n- If parameter validation fails, a detailed error with the expected schema is returned\n- You can skip ToolSearch if you already know the tool name and parameters from a previous turn\n- This tool follows standard permission checks (may require approval depending on permissions configuration)\n"
|
|
1241
1241
|
},
|
|
1242
|
+
{
|
|
1243
|
+
"name": "tool-a2agetagentcard-description",
|
|
1244
|
+
"template": "Get the Agent Card of an A2A agent available to this conversation by agent name.\nUse this before A2ASendMessage. Pass the returned Agent Card `name` as `agent_name` to A2ASendMessage / A2AGetTask / A2ACancelTask.\n\n<available_A2A_agents>\n{%- if a2aAgents and a2aAgents.length > 0 -%}\n{%- for agent in a2aAgents %}\n{{agent.name}}{% if agent.description %}:{{agent.description}}{% endif %}\n{%- endfor -%}\n{%- endif %}\n</available_A2A_agents>\n"
|
|
1245
|
+
},
|
|
1246
|
+
{
|
|
1247
|
+
"name": "tool-a2asendmessage-description",
|
|
1248
|
+
"template": "Send a message to one outbound A2A (Agent2Agent) agent authorized for this conversation.\n\nParameters:\n- `agent_name` (required): the target agent's name, exactly as returned by A2AGetAgentCard (the Agent Card `name`), or from an explicit @Agent mention.\n- `message` (required): a concise, self-contained task or question. Do not paste the whole conversation — state the goal and only the context the agent actually needs (the agent has no visibility into this conversation otherwise).\n- `skill_id` (optional): route directly to one declared skill, for @Agent.Skill-style precise dispatch. Only effective when A2AGetAgentCard reported `supports_skill_routing: true` for that agent; otherwise this is ignored.\n- `attachments` (optional): a list of files to attach to the message, each referenced by a fetchable `url` (plus optional `filename` and `media_type`). Each is sent to the agent as a standard A2A file part. The `url` must already be reachable by the remote agent — this tool sends it as-is and does NOT upload, host, or transform the file. Use this when you already have a URL for a file you want the agent to work on; obtain that URL through whatever mechanism your environment provides.\n\nThis call is NOT synchronous: it returns immediately with whatever single reply the agent gave, and does not wait for the task to finish. Check the returned status: only completed/failed/canceled/rejected are final. For ANY other status — working, submitted, input-required, or auth-required — you MUST immediately call A2AGetTask with the returned task_id, in the same turn, before doing anything else with this task. Do not wait, and do not ask the user for permission first.\n\nauth-required and input-required are NOT a signal to stop and hand off to the user by themselves — calling A2AGetTask to check status is just reading state, not performing a login or filling credentials on the user's behalf, so it does not conflict with any policy against automating logins. A2AGetTask polls automatically with backoff, relays the agent's own request (e.g. \"please sign in\", a clarifying question) to the user exactly once, and keeps waiting for the remote task to advance — it will surface the eventual outcome once the user (or the agent) resolves whatever it was waiting on.\n\nBehavior notes:\n- Calling the same `agent_id` (and `skill_id`, if used) again in a later turn continues the same conversation with that agent automatically — you don't need to restate prior context.\n- To message multiple agents in one turn, call this tool once per agent as parallel tool calls — one agent failing does not affect the others.\n- Any files or links the agent produced are returned as part of the result text.\n- Not available inside a sub-agent or team-member session — only the main conversation can call outbound A2A agents.\n"
|
|
1249
|
+
},
|
|
1250
|
+
{
|
|
1251
|
+
"name": "tool-a2agettask-description",
|
|
1252
|
+
"template": "Wait for a previously created outbound A2A task to finish, polling automatically.\n\nParameters:\n- `agent_name` (required): the same agent name passed to the original A2ASendMessage call.\n- `task_id` (required): the taskId from a prior A2ASendMessage or A2AGetTask result.\n\nThis call polls the remote task's real, current state with backoff, showing live progress as it goes, up to a bounded time/attempt limit. It returns once the task reaches a final state (completed/failed/canceled/rejected), or a \"still running\" result if the bound is hit first — if you get \"still running\", immediately call A2AGetTask again with the same task_id to keep waiting; do not give up after one bounded call.\n\nIf the task is waiting on external input or auth (input-required/auth-required), that request is relayed to the user once and polling continues with backoff — the next call/round of polling reflects the task's real state at that later point in time, not a repeat of what you already saw. Checking status this way is just reading state, not performing a login or filling credentials on the user's behalf, so calling this while the task is waiting on the user to complete a login/approval step out of band does not conflict with any policy against automating logins.\n\nNot available inside a sub-agent or team-member session — only the main conversation can query outbound A2A tasks.\n"
|
|
1253
|
+
},
|
|
1254
|
+
{
|
|
1255
|
+
"name": "tool-a2acanceltask-description",
|
|
1256
|
+
"template": "Request cancellation of a previously created outbound A2A task.\n\nParameters:\n- `agent_name` (required): the same agent name passed to the original A2ASendMessage call.\n- `task_id` (required): the taskId from a prior A2ASendMessage or A2AGetTask result.\n\nCancellation is a best-effort request, not a guarantee. If the task has already reached a terminal state (completed/failed/canceled/rejected), the call fails and the tool result will say so — that is expected, not an error to retry. Use A2AGetTask if you need to check the task's status before deciding whether to cancel it.\n\nNot available inside a sub-agent or team-member session — only the main conversation can cancel outbound A2A tasks.\n"
|
|
1257
|
+
},
|
|
1242
1258
|
{
|
|
1243
1259
|
"name": "tool-describetool-description",
|
|
1244
1260
|
"template": "Get detailed information about a specific tool by name.\n\nUse this when you already know the tool name and need its full definition (description and input schema). This is more efficient than searching when the tool name is known.\n\nUsage:\n- Provide the exact tool name (case-sensitive)\n- Returns the tool's full description and parameter schema\n- The tool is automatically activated for use in subsequent messages\n\nIf the tool is not found, consider using ToolSearch to discover available tools by description.\n"
|
|
@@ -1442,7 +1458,11 @@
|
|
|
1442
1458
|
"WaitForMcpServers",
|
|
1443
1459
|
"Workflow",
|
|
1444
1460
|
"Monitor",
|
|
1445
|
-
"REPL"
|
|
1461
|
+
"REPL",
|
|
1462
|
+
"A2AGetAgentCard",
|
|
1463
|
+
"A2ASendMessage",
|
|
1464
|
+
"A2AGetTask",
|
|
1465
|
+
"A2ACancelTask"
|
|
1446
1466
|
],
|
|
1447
1467
|
"tags": [
|
|
1448
1468
|
"cli",
|
|
@@ -1538,7 +1558,7 @@
|
|
|
1538
1558
|
"instructions": "terminal-title-generator-instructions",
|
|
1539
1559
|
"description": "Generate terminal window titles from conversation topics.",
|
|
1540
1560
|
"models": [
|
|
1541
|
-
"lite"
|
|
1561
|
+
"builtin-lite"
|
|
1542
1562
|
],
|
|
1543
1563
|
"tags": [
|
|
1544
1564
|
"cli",
|
|
@@ -1682,11 +1702,9 @@
|
|
|
1682
1702
|
{
|
|
1683
1703
|
"name": "Plan",
|
|
1684
1704
|
"instructions": "agent-plan-instructions",
|
|
1685
|
-
"description": "
|
|
1705
|
+
"description": "Software architect agent for designing implementation plans. Use this when you need to plan the implementation strategy for a multi-step task before writing code. Returns step-by-step plans, identifies critical files, and considers architectural trade-offs. Read-only: it designs but does not modify files or exit plan mode; the caller reviews the plan and implements it.",
|
|
1686
1706
|
"tools": [
|
|
1687
1707
|
"Read",
|
|
1688
|
-
"Write",
|
|
1689
|
-
"Edit",
|
|
1690
1708
|
"Bash",
|
|
1691
1709
|
"PowerShell",
|
|
1692
1710
|
"Glob",
|
|
@@ -2190,8 +2208,24 @@
|
|
|
2190
2208
|
{
|
|
2191
2209
|
"name": "REPL",
|
|
2192
2210
|
"description": "tool-repl-description"
|
|
2211
|
+
},
|
|
2212
|
+
{
|
|
2213
|
+
"name": "A2AGetAgentCard",
|
|
2214
|
+
"description": "tool-a2agetagentcard-description"
|
|
2215
|
+
},
|
|
2216
|
+
{
|
|
2217
|
+
"name": "A2ASendMessage",
|
|
2218
|
+
"description": "tool-a2asendmessage-description"
|
|
2219
|
+
},
|
|
2220
|
+
{
|
|
2221
|
+
"name": "A2AGetTask",
|
|
2222
|
+
"description": "tool-a2agettask-description"
|
|
2223
|
+
},
|
|
2224
|
+
{
|
|
2225
|
+
"name": "A2ACancelTask",
|
|
2226
|
+
"description": "tool-a2acanceltask-description"
|
|
2193
2227
|
}
|
|
2194
2228
|
],
|
|
2195
|
-
"commit": "
|
|
2196
|
-
"date": "2026-
|
|
2229
|
+
"commit": "89709f860396a62d75e8cc01e9514cf221817e79",
|
|
2230
|
+
"date": "2026-09-04T10:20:16.699Z"
|
|
2197
2231
|
}
|
|
@@ -63,7 +63,11 @@
|
|
|
63
63
|
"WaitForMcpServers",
|
|
64
64
|
"Workflow",
|
|
65
65
|
"Monitor",
|
|
66
|
-
"REPL"
|
|
66
|
+
"REPL",
|
|
67
|
+
"A2AGetAgentCard",
|
|
68
|
+
"A2ASendMessage",
|
|
69
|
+
"A2AGetTask",
|
|
70
|
+
"A2ACancelTask"
|
|
67
71
|
],
|
|
68
72
|
"tags": [
|
|
69
73
|
"cli",
|
|
@@ -291,11 +295,9 @@
|
|
|
291
295
|
{
|
|
292
296
|
"name": "Plan",
|
|
293
297
|
"instructions": "agent-plan-instructions",
|
|
294
|
-
"description": "
|
|
298
|
+
"description": "Software architect agent for designing implementation plans. Use this when you need to plan the implementation strategy for a multi-step task before writing code. Returns step-by-step plans, identifies critical files, and considers architectural trade-offs. Read-only: it designs but does not modify files or exit plan mode; the caller reviews the plan and implements it.",
|
|
295
299
|
"tools": [
|
|
296
300
|
"Read",
|
|
297
|
-
"Write",
|
|
298
|
-
"Edit",
|
|
299
301
|
"Bash",
|
|
300
302
|
"PowerShell",
|
|
301
303
|
"Glob",
|
|
@@ -357,6 +359,6 @@
|
|
|
357
359
|
"ScheduledTasks": true,
|
|
358
360
|
"SkipToolCallSupportCheck": true
|
|
359
361
|
},
|
|
360
|
-
"commit": "
|
|
361
|
-
"date": "2026-
|
|
362
|
+
"commit": "89709f860396a62d75e8cc01e9514cf221817e79",
|
|
363
|
+
"date": "2026-09-04T10:20:16.672Z"
|
|
362
364
|
}
|
|
@@ -10,7 +10,109 @@
|
|
|
10
10
|
const net = require('net');
|
|
11
11
|
const { spawnSync } = require('child_process');
|
|
12
12
|
|
|
13
|
-
const DEFAULT_TIMEOUT_MS =
|
|
13
|
+
const DEFAULT_TIMEOUT_MS = 20_000;
|
|
14
|
+
const MAX_CONCURRENT_REQUESTS = 128;
|
|
15
|
+
const MAX_CONCURRENT_ASYNC_REQUESTS = MAX_CONCURRENT_REQUESTS - 1;
|
|
16
|
+
const MAX_QUEUED_REQUESTS = 8192;
|
|
17
|
+
const REQUEST_QUEUE_PRIORITIES = Object.freeze({ low: 0, normal: 1, high: 2 });
|
|
18
|
+
let activeAsyncRequests = 0;
|
|
19
|
+
let queuedRequestCount = 0;
|
|
20
|
+
const requestWaiters = [new Set(), new Set(), new Set()];
|
|
21
|
+
|
|
22
|
+
function requestTimeoutError() {
|
|
23
|
+
const error = new Error('Broker request timed out');
|
|
24
|
+
error.code = 'CODEBUDDY_BROKER_TIMEOUT';
|
|
25
|
+
return error;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function requestQueueTimeoutError() {
|
|
29
|
+
const error = new Error('Broker request timed out while waiting for an IPC slot');
|
|
30
|
+
error.code = 'CODEBUDDY_BROKER_QUEUE_TIMEOUT';
|
|
31
|
+
error.brokerRequestSent = false;
|
|
32
|
+
return error;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function requestQueueFullError() {
|
|
36
|
+
const error = new Error('Broker request queue is full');
|
|
37
|
+
error.code = 'CODEBUDDY_BROKER_QUEUE_FULL';
|
|
38
|
+
error.brokerRequestSent = false;
|
|
39
|
+
return error;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function requestQueueEvictedError() {
|
|
43
|
+
const error = new Error('Broker request was evicted from the queue by a higher-priority request');
|
|
44
|
+
error.code = 'CODEBUDDY_BROKER_QUEUE_EVICTED';
|
|
45
|
+
error.brokerRequestSent = false;
|
|
46
|
+
return error;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function requestQueuePriority(priority) {
|
|
50
|
+
return Object.hasOwn(REQUEST_QUEUE_PRIORITIES, priority)
|
|
51
|
+
? REQUEST_QUEUE_PRIORITIES[priority]
|
|
52
|
+
: REQUEST_QUEUE_PRIORITIES.normal;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function removeRequestWaiter(waiter) {
|
|
56
|
+
if (!requestWaiters[waiter.priority].delete(waiter)) {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
queuedRequestCount--;
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function evictLowerPriorityRequest(priority) {
|
|
64
|
+
for (let candidatePriority = REQUEST_QUEUE_PRIORITIES.low; candidatePriority < priority; candidatePriority++) {
|
|
65
|
+
for (const waiter of requestWaiters[candidatePriority]) {
|
|
66
|
+
if (!waiter.evictable || !removeRequestWaiter(waiter)) { continue; }
|
|
67
|
+
waiter.reject(requestQueueEvictedError());
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function acquireRequestSlot(timeoutMs, options) {
|
|
75
|
+
if (activeAsyncRequests < MAX_CONCURRENT_ASYNC_REQUESTS) {
|
|
76
|
+
activeAsyncRequests++;
|
|
77
|
+
return Promise.resolve();
|
|
78
|
+
}
|
|
79
|
+
const priority = requestQueuePriority(options.queuePriority);
|
|
80
|
+
if (queuedRequestCount >= MAX_QUEUED_REQUESTS
|
|
81
|
+
&& (!options.queueEvictLowerPriority || !evictLowerPriorityRequest(priority))) {
|
|
82
|
+
return Promise.reject(requestQueueFullError());
|
|
83
|
+
}
|
|
84
|
+
return new Promise((resolve, reject) => {
|
|
85
|
+
const waiter = {
|
|
86
|
+
priority,
|
|
87
|
+
evictable: options.queueEvictable === true,
|
|
88
|
+
resolve: () => {
|
|
89
|
+
clearTimeout(waiter.timer);
|
|
90
|
+
resolve();
|
|
91
|
+
},
|
|
92
|
+
reject: error => {
|
|
93
|
+
clearTimeout(waiter.timer);
|
|
94
|
+
reject(error);
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
waiter.timer = setTimeout(() => {
|
|
98
|
+
if (!removeRequestWaiter(waiter)) { return; }
|
|
99
|
+
waiter.reject(requestQueueTimeoutError());
|
|
100
|
+
}, timeoutMs);
|
|
101
|
+
requestWaiters[priority].add(waiter);
|
|
102
|
+
queuedRequestCount++;
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function releaseRequestSlot() {
|
|
107
|
+
for (let priority = REQUEST_QUEUE_PRIORITIES.high; priority >= REQUEST_QUEUE_PRIORITIES.low; priority--) {
|
|
108
|
+
const next = requestWaiters[priority].values().next().value;
|
|
109
|
+
if (next && removeRequestWaiter(next)) {
|
|
110
|
+
next.resolve();
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
activeAsyncRequests--;
|
|
115
|
+
}
|
|
14
116
|
|
|
15
117
|
function brokerTimeoutMs() {
|
|
16
118
|
const parsed = Number.parseInt(process.env.CODEBUDDY_SANDBOX_BROKER_TIMEOUT_MS || '', 10);
|
|
@@ -29,61 +131,96 @@ function parseBrokerResponse(line) {
|
|
|
29
131
|
}
|
|
30
132
|
}
|
|
31
133
|
|
|
32
|
-
function
|
|
134
|
+
function markBrokerRequestState(error, requestSent) {
|
|
135
|
+
const brokerError = error instanceof Error ? error : new Error(String(error));
|
|
136
|
+
brokerError.brokerRequestSent = requestSent;
|
|
137
|
+
return brokerError;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function nativeSyncErrorMayHaveBeenSent(error) {
|
|
141
|
+
const message = error && error.message ? error.message : String(error);
|
|
142
|
+
return ![
|
|
143
|
+
'requestBrokerSync requires socketPath and line',
|
|
144
|
+
'socket failed:',
|
|
145
|
+
'setsockopt timeout failed:',
|
|
146
|
+
'broker socket path is too long',
|
|
147
|
+
'connect failed:',
|
|
148
|
+
].some(prefix => message.startsWith(prefix));
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
async function sendBrokerRequest(request, options = {}) {
|
|
33
152
|
const socketPath = options.socketPath || brokerSocketPath();
|
|
34
153
|
const timeoutMs = options.timeoutMs || brokerTimeoutMs();
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
154
|
+
if (!socketPath) {
|
|
155
|
+
throw markBrokerRequestState(new Error('Broker socket unavailable'), false);
|
|
156
|
+
}
|
|
40
157
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
158
|
+
const startedAt = Date.now();
|
|
159
|
+
await acquireRequestSlot(timeoutMs, options);
|
|
160
|
+
try {
|
|
161
|
+
const remainingTimeoutMs = timeoutMs - (Date.now() - startedAt);
|
|
162
|
+
if (remainingTimeoutMs <= 0) {
|
|
163
|
+
throw requestQueueTimeoutError();
|
|
164
|
+
}
|
|
165
|
+
const line = JSON.stringify(request);
|
|
166
|
+
return await new Promise((resolve, reject) => {
|
|
167
|
+
let settled = false;
|
|
168
|
+
let requestSent = false;
|
|
169
|
+
let buffer = '';
|
|
170
|
+
const socket = net.createConnection(socketPath);
|
|
171
|
+
const finish = (error, response) => {
|
|
172
|
+
if (settled) { return; }
|
|
173
|
+
settled = true;
|
|
174
|
+
clearTimeout(timer);
|
|
175
|
+
socket.once('close', () => {
|
|
176
|
+
if (error) { reject(markBrokerRequestState(error, requestSent)); } else { resolve(response); }
|
|
177
|
+
});
|
|
178
|
+
socket.destroy();
|
|
179
|
+
};
|
|
180
|
+
const timer = setTimeout(() => finish(requestTimeoutError()), remainingTimeoutMs);
|
|
52
181
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
182
|
+
socket.setEncoding('utf8');
|
|
183
|
+
socket.on('connect', () => {
|
|
184
|
+
requestSent = true;
|
|
185
|
+
socket.write(`${line}\n`);
|
|
186
|
+
});
|
|
187
|
+
socket.on('data', chunk => {
|
|
188
|
+
buffer += chunk;
|
|
189
|
+
const newlineIndex = buffer.indexOf('\n');
|
|
190
|
+
if (newlineIndex === -1) { return; }
|
|
191
|
+
try {
|
|
192
|
+
finish(undefined, parseBrokerResponse(buffer.slice(0, newlineIndex)));
|
|
193
|
+
} catch (error) {
|
|
194
|
+
finish(error);
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
socket.on('error', error => finish(error));
|
|
198
|
+
socket.on('end', () => {
|
|
199
|
+
if (!settled) {
|
|
200
|
+
finish(new Error('Broker closed without a response'));
|
|
201
|
+
}
|
|
202
|
+
});
|
|
56
203
|
});
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
if (newlineIndex === -1) { return; }
|
|
61
|
-
try {
|
|
62
|
-
finish(undefined, parseBrokerResponse(buffer.slice(0, newlineIndex)));
|
|
63
|
-
} catch (error) {
|
|
64
|
-
finish(error);
|
|
65
|
-
}
|
|
66
|
-
});
|
|
67
|
-
socket.on('error', error => finish(error));
|
|
68
|
-
socket.on('end', () => {
|
|
69
|
-
if (!settled) {
|
|
70
|
-
finish(new Error('Broker closed without a response'));
|
|
71
|
-
}
|
|
72
|
-
});
|
|
73
|
-
});
|
|
204
|
+
} finally {
|
|
205
|
+
releaseRequestSlot();
|
|
206
|
+
}
|
|
74
207
|
}
|
|
75
208
|
|
|
76
209
|
function sendBrokerRequestSync(request, options = {}) {
|
|
77
210
|
const socketPath = options.socketPath || brokerSocketPath();
|
|
78
211
|
if (!socketPath) {
|
|
79
|
-
throw new Error('Broker socket unavailable');
|
|
212
|
+
throw markBrokerRequestState(new Error('Broker socket unavailable'), false);
|
|
80
213
|
}
|
|
81
214
|
|
|
82
215
|
const timeoutMs = options.timeoutMs || brokerTimeoutMs();
|
|
83
216
|
const line = JSON.stringify(request);
|
|
84
217
|
const nativeAddon = options.nativeAddon;
|
|
85
218
|
if (nativeAddon && typeof nativeAddon.requestBrokerSync === 'function') {
|
|
86
|
-
|
|
219
|
+
try {
|
|
220
|
+
return parseBrokerResponse(nativeAddon.requestBrokerSync(socketPath, line, timeoutMs));
|
|
221
|
+
} catch (error) {
|
|
222
|
+
throw markBrokerRequestState(error, nativeSyncErrorMayHaveBeenSent(error));
|
|
223
|
+
}
|
|
87
224
|
}
|
|
88
225
|
|
|
89
226
|
const nodeBin = process.env.CODEBUDDY_NODE_BIN || process.execPath;
|
|
@@ -108,12 +245,20 @@ function sendBrokerRequestSync(request, options = {}) {
|
|
|
108
245
|
});
|
|
109
246
|
|
|
110
247
|
if (result.error) {
|
|
111
|
-
|
|
248
|
+
const requestSent = !['ENOENT', 'EACCES', 'ENOEXEC'].includes(result.error.code);
|
|
249
|
+
throw markBrokerRequestState(result.error, requestSent);
|
|
112
250
|
}
|
|
113
251
|
if (result.status !== 0) {
|
|
114
|
-
throw
|
|
252
|
+
throw markBrokerRequestState(
|
|
253
|
+
new Error((result.stderr || '').trim() || `Broker sync helper exited with ${result.status}`),
|
|
254
|
+
result.status !== 3 && result.status !== 123,
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
try {
|
|
258
|
+
return parseBrokerResponse(result.stdout);
|
|
259
|
+
} catch (error) {
|
|
260
|
+
throw markBrokerRequestState(error, true);
|
|
115
261
|
}
|
|
116
|
-
return parseBrokerResponse(result.stdout);
|
|
117
262
|
}
|
|
118
263
|
|
|
119
264
|
function runSyncRequestCli(argv) {
|
|
@@ -124,6 +269,7 @@ function runSyncRequestCli(argv) {
|
|
|
124
269
|
}
|
|
125
270
|
const timeoutMs = Number.parseInt(timeoutValue || '', 10) || DEFAULT_TIMEOUT_MS;
|
|
126
271
|
let settled = false;
|
|
272
|
+
let requestSent = false;
|
|
127
273
|
let buffer = '';
|
|
128
274
|
const socket = net.createConnection(socketPath);
|
|
129
275
|
const finish = (code, message) => {
|
|
@@ -136,9 +282,12 @@ function runSyncRequestCli(argv) {
|
|
|
136
282
|
}
|
|
137
283
|
process.exit(code);
|
|
138
284
|
};
|
|
139
|
-
const timer = setTimeout(() => finish(124, 'Broker request timed out'), timeoutMs);
|
|
285
|
+
const timer = setTimeout(() => finish(requestSent ? 124 : 123, 'Broker request timed out'), timeoutMs);
|
|
140
286
|
socket.setEncoding('utf8');
|
|
141
|
-
socket.on('connect', () =>
|
|
287
|
+
socket.on('connect', () => {
|
|
288
|
+
requestSent = true;
|
|
289
|
+
socket.write(`${line}\n`);
|
|
290
|
+
});
|
|
142
291
|
socket.on('data', chunk => {
|
|
143
292
|
buffer += chunk;
|
|
144
293
|
const newlineIndex = buffer.indexOf('\n');
|
|
@@ -146,7 +295,7 @@ function runSyncRequestCli(argv) {
|
|
|
146
295
|
finish(0, buffer.slice(0, newlineIndex));
|
|
147
296
|
}
|
|
148
297
|
});
|
|
149
|
-
socket.on('error', error => finish(1, error && error.message ? error.message : String(error)));
|
|
298
|
+
socket.on('error', error => finish(requestSent ? 1 : 3, error && error.message ? error.message : String(error)));
|
|
150
299
|
socket.on('end', () => finish(2, 'Broker closed without a response'));
|
|
151
300
|
}
|
|
152
301
|
|