qlogicagent 2.16.8 → 2.16.10

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 (35) hide show
  1. package/dist/agent.js +20 -20
  2. package/dist/cli.js +429 -396
  3. package/dist/default-project-knowledge/INSTRUCTIONS.md +7 -7
  4. package/dist/default-project-knowledge/rules/project-workflow.md +6 -6
  5. package/dist/index.js +425 -392
  6. package/dist/runtime/infra/mcp-bridge-server.js +338 -0
  7. package/dist/skills/mcp/astraclaw-native-mcp-server.js +9 -0
  8. package/dist/types/agent/tool-loop/artifact-final-contract.d.ts +3 -0
  9. package/dist/types/cli/handlers/turn-handler.d.ts +2 -0
  10. package/dist/types/cli/pet-runtime.d.ts +5 -0
  11. package/dist/types/cli/skills-query-service.d.ts +6 -1
  12. package/dist/types/orchestration/delegation-coordinator.d.ts +1 -0
  13. package/dist/types/runtime/infra/agent-process.d.ts +6 -0
  14. package/dist/types/runtime/infra/astraclaw-capabilities.d.ts +30 -0
  15. package/dist/types/runtime/infra/electron-node.d.ts +36 -0
  16. package/dist/types/runtime/infra/native-mcp-config-sync.d.ts +16 -0
  17. package/dist/types/runtime/infra/skill-resolver.d.ts +4 -0
  18. package/dist/types/skills/mcp/astraclaw-native-mcp-server.d.ts +1 -0
  19. package/dist/types/skills/plugins/plugin-marketplace.d.ts +16 -0
  20. package/dist/types/skills/skill-system/skill-validation.d.ts +10 -0
  21. package/dist/types/skills/tools/skill-tool.d.ts +1 -1
  22. package/dist/vendor/hatch-pet/LICENSE.txt +201 -201
  23. package/dist/vendor/hatch-pet/NOTICE.md +25 -25
  24. package/dist/vendor/hatch-pet/references/animation-rows.md +29 -29
  25. package/dist/vendor/hatch-pet/references/codex-pet-contract.md +35 -35
  26. package/dist/vendor/hatch-pet/references/qa-rubric.md +66 -66
  27. package/dist/vendor/hatch-pet/scripts/compose_atlas.py +169 -169
  28. package/dist/vendor/hatch-pet/scripts/derive_running_left_from_running_right.py +150 -150
  29. package/dist/vendor/hatch-pet/scripts/extract_strip_frames.py +408 -408
  30. package/dist/vendor/hatch-pet/scripts/inspect_frames.py +256 -256
  31. package/dist/vendor/hatch-pet/scripts/make_contact_sheet.py +96 -96
  32. package/dist/vendor/hatch-pet/scripts/prepare_pet_run.py +834 -834
  33. package/dist/vendor/hatch-pet/scripts/render_animation_previews.py +78 -78
  34. package/dist/vendor/hatch-pet/scripts/validate_atlas.py +157 -157
  35. package/package.json +1 -1
@@ -0,0 +1,338 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * MCP Bridge Server — stdio MCP server for external ACP agents.
4
+ *
5
+ * Spawned by qlogicagent as a child of the external agent process.
6
+ * Receives MCP requests (tools/list, tools/call) from the external agent
7
+ * and proxies tool calls back to qlogicagent's parent RPC.
8
+ *
9
+ * Environment:
10
+ * QLOGICAGENT_PARENT_RPC — IPC/pipe path to parent qlogicagent
11
+ * QLOGICAGENT_SESSION_ID — session scope for tool calls
12
+ *
13
+ * Protocol: JSON-RPC 2.0 over stdio (newline-delimited)
14
+ */
15
+
16
+ import readline from "node:readline";
17
+ import net from "node:net";
18
+
19
+ // ── Tool Manifest (must match MCP_BRIDGE_TOOLS in mcp-bridge.ts) ────
20
+
21
+ const TOOLS = [
22
+ {
23
+ name: "skills_list",
24
+ description: "List AstraClaw skills currently active for this workspace and session.",
25
+ inputSchema: {
26
+ type: "object",
27
+ properties: {},
28
+ },
29
+ },
30
+ {
31
+ name: "skill_view",
32
+ description: "Read the SKILL.md workflow for an installed AstraClaw skill by name.",
33
+ inputSchema: {
34
+ type: "object",
35
+ properties: {
36
+ name: { type: "string", description: "Installed skill name" },
37
+ },
38
+ required: ["name"],
39
+ },
40
+ },
41
+ {
42
+ name: "mcp_connectors_list",
43
+ description: "List MCP connector tools installed in AstraClaw's Plugins page and shared with this external agent.",
44
+ inputSchema: {
45
+ type: "object",
46
+ properties: {},
47
+ },
48
+ },
49
+ {
50
+ name: "mcp_tool_call",
51
+ description: "Call one AstraClaw shared MCP connector tool by toolName from mcp_connectors_list.",
52
+ inputSchema: {
53
+ type: "object",
54
+ properties: {
55
+ toolName: { type: "string", description: "Connector tool name, for example mcp__filesystem__read_text_file" },
56
+ args: { type: "object", description: "Arguments for the connector tool" },
57
+ },
58
+ required: ["toolName"],
59
+ },
60
+ },
61
+ {
62
+ name: "media_generate",
63
+ description: "Generate images or videos using AI models.",
64
+ inputSchema: {
65
+ type: "object",
66
+ properties: {
67
+ prompt: { type: "string", description: "Generation prompt" },
68
+ type: { type: "string", enum: ["image", "video"], description: "Media type" },
69
+ },
70
+ required: ["prompt"],
71
+ },
72
+ },
73
+ {
74
+ name: "media_status",
75
+ description: "Check status of an async media generation job.",
76
+ inputSchema: {
77
+ type: "object",
78
+ properties: {
79
+ jobId: { type: "string", description: "Job ID to check" },
80
+ },
81
+ required: ["jobId"],
82
+ },
83
+ },
84
+ {
85
+ name: "memory_read",
86
+ description: "Read from persistent memory.",
87
+ inputSchema: {
88
+ type: "object",
89
+ properties: {
90
+ key: { type: "string", description: "Memory key to read" },
91
+ },
92
+ required: ["key"],
93
+ },
94
+ },
95
+ {
96
+ name: "memory_write",
97
+ description: "Write to persistent memory.",
98
+ inputSchema: {
99
+ type: "object",
100
+ properties: {
101
+ key: { type: "string", description: "Memory key" },
102
+ value: { type: "string", description: "Value to store" },
103
+ },
104
+ required: ["key", "value"],
105
+ },
106
+ },
107
+ {
108
+ name: "memory_search",
109
+ description: "Search persistent memory by query.",
110
+ inputSchema: {
111
+ type: "object",
112
+ properties: {
113
+ query: { type: "string", description: "Search query" },
114
+ },
115
+ required: ["query"],
116
+ },
117
+ },
118
+ {
119
+ name: "web_search",
120
+ description: "Search the web for information.",
121
+ inputSchema: {
122
+ type: "object",
123
+ properties: {
124
+ query: { type: "string", description: "Search query" },
125
+ },
126
+ required: ["query"],
127
+ },
128
+ },
129
+ {
130
+ name: "web_fetch",
131
+ description: "Fetch content from a URL.",
132
+ inputSchema: {
133
+ type: "object",
134
+ properties: {
135
+ url: { type: "string", description: "URL to fetch" },
136
+ },
137
+ required: ["url"],
138
+ },
139
+ },
140
+ {
141
+ name: "team_status",
142
+ description: "Get status of all agent team members.",
143
+ inputSchema: {
144
+ type: "object",
145
+ properties: {},
146
+ },
147
+ },
148
+ {
149
+ name: "team_message",
150
+ description: "Send a message to another team member agent.",
151
+ inputSchema: {
152
+ type: "object",
153
+ properties: {
154
+ targetAgentId: { type: "string", description: "Target agent ID" },
155
+ message: { type: "string", description: "Message to send" },
156
+ },
157
+ required: ["targetAgentId", "message"],
158
+ },
159
+ },
160
+ ];
161
+
162
+ // ── Parent RPC Connection ───────────────────────────────────
163
+
164
+ const PARENT_RPC = process.env.QLOGICAGENT_PARENT_RPC;
165
+ const SESSION_ID = process.env.QLOGICAGENT_SESSION_ID || "unknown";
166
+
167
+ let parentSocket = null;
168
+ let rpcId = 1;
169
+ const pendingRpc = new Map();
170
+
171
+ function connectParent() {
172
+ if (!PARENT_RPC) return;
173
+
174
+ parentSocket = net.createConnection(PARENT_RPC, () => {
175
+ process.stderr.write(`[mcp-bridge] connected to parent: ${PARENT_RPC}\n`);
176
+ });
177
+
178
+ let buffer = "";
179
+ parentSocket.on("data", (chunk) => {
180
+ buffer += chunk.toString();
181
+ let nl;
182
+ while ((nl = buffer.indexOf("\n")) !== -1) {
183
+ const line = buffer.slice(0, nl).trim();
184
+ buffer = buffer.slice(nl + 1);
185
+ if (!line) continue;
186
+ try {
187
+ const msg = JSON.parse(line);
188
+ if (msg.id !== undefined && pendingRpc.has(msg.id)) {
189
+ const resolve = pendingRpc.get(msg.id);
190
+ pendingRpc.delete(msg.id);
191
+ resolve(msg);
192
+ }
193
+ } catch {
194
+ // ignore parse errors
195
+ }
196
+ }
197
+ });
198
+
199
+ parentSocket.on("error", (err) => {
200
+ process.stderr.write(`[mcp-bridge] parent connection error: ${err.message}\n`);
201
+ });
202
+
203
+ parentSocket.on("close", () => {
204
+ parentSocket = null;
205
+ });
206
+ }
207
+
208
+ function callParent(method, params) {
209
+ return new Promise((resolve, reject) => {
210
+ if (!parentSocket) {
211
+ reject(new Error("Not connected to parent"));
212
+ return;
213
+ }
214
+ const id = rpcId++;
215
+ const msg = JSON.stringify({ jsonrpc: "2.0", id, method, params }) + "\n";
216
+ pendingRpc.set(id, resolve);
217
+ parentSocket.write(msg);
218
+
219
+ // Connector calls can legitimately run longer than a simple capability lookup.
220
+ setTimeout(() => {
221
+ if (pendingRpc.has(id)) {
222
+ pendingRpc.delete(id);
223
+ reject(new Error(`Parent RPC timeout for ${method}`));
224
+ }
225
+ }, 120_000);
226
+ });
227
+ }
228
+
229
+ // ── MCP Stdio Protocol ──────────────────────────────────────
230
+
231
+ function sendResponse(id, result, error) {
232
+ const msg = { jsonrpc: "2.0", id };
233
+ if (error) msg.error = error;
234
+ else msg.result = result;
235
+ process.stdout.write(JSON.stringify(msg) + "\n");
236
+ }
237
+
238
+ async function handleRequest(msg) {
239
+ const { id, method, params } = msg;
240
+
241
+ if (method === "initialize") {
242
+ sendResponse(id, {
243
+ protocolVersion: "2024-11-05",
244
+ capabilities: { tools: {} },
245
+ serverInfo: { name: "astraclaw_capabilities", version: "1.0.0" },
246
+ });
247
+ return;
248
+ }
249
+
250
+ if (method === "tools/list") {
251
+ sendResponse(id, { tools: TOOLS });
252
+ return;
253
+ }
254
+
255
+ if (method === "tools/call") {
256
+ const toolName = params?.name;
257
+ const toolArgs = params?.arguments || {};
258
+
259
+ if (!toolName) {
260
+ sendResponse(id, null, { code: -32602, message: "Missing tool name" });
261
+ return;
262
+ }
263
+
264
+ const knownTool = TOOLS.find((t) => t.name === toolName);
265
+ if (!knownTool) {
266
+ sendResponse(id, null, { code: -32602, message: `Unknown tool: ${toolName}` });
267
+ return;
268
+ }
269
+
270
+ // Proxy to parent qlogicagent
271
+ if (!parentSocket) {
272
+ sendResponse(id, {
273
+ content: [{ type: "text", text: `Error: MCP bridge not connected to parent` }],
274
+ isError: true,
275
+ });
276
+ return;
277
+ }
278
+
279
+ try {
280
+ const result = await callParent("mcp.toolCall", {
281
+ sessionId: SESSION_ID,
282
+ tool: toolName,
283
+ arguments: toolArgs,
284
+ });
285
+
286
+ if (result.error) {
287
+ sendResponse(id, {
288
+ content: [{ type: "text", text: `Error: ${result.error.message}` }],
289
+ isError: true,
290
+ });
291
+ } else {
292
+ const text = typeof result.result === "string"
293
+ ? result.result
294
+ : JSON.stringify(result.result);
295
+ sendResponse(id, {
296
+ content: [{ type: "text", text }],
297
+ });
298
+ }
299
+ } catch (err) {
300
+ sendResponse(id, {
301
+ content: [{ type: "text", text: `Error: ${err.message}` }],
302
+ isError: true,
303
+ });
304
+ }
305
+ return;
306
+ }
307
+
308
+ // Notification — no response needed
309
+ if (id === undefined) return;
310
+
311
+ sendResponse(id, null, { code: -32601, message: `Unknown method: ${method}` });
312
+ }
313
+
314
+ // ── Main ────────────────────────────────────────────────────
315
+
316
+ connectParent();
317
+
318
+ const rl = readline.createInterface({ input: process.stdin, terminal: false });
319
+
320
+ rl.on("line", (line) => {
321
+ const trimmed = line.trim();
322
+ if (!trimmed) return;
323
+ try {
324
+ const msg = JSON.parse(trimmed);
325
+ handleRequest(msg).catch((err) => {
326
+ if (msg.id !== undefined) {
327
+ sendResponse(msg.id, null, { code: -32603, message: err.message });
328
+ }
329
+ });
330
+ } catch {
331
+ // Malformed JSON — ignore
332
+ }
333
+ });
334
+
335
+ rl.on("close", () => {
336
+ if (parentSocket) parentSocket.destroy();
337
+ process.exit(0);
338
+ });
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env node
2
+ import{createInterface as Qe}from"node:readline";import{existsSync as Ye,readFileSync as Ze}from"node:fs";import{readFileSync as be}from"node:fs";import{randomUUID as Te}from"node:crypto";import{readdirSync as le,readFileSync as ce,statSync as b,writeFileSync as ue}from"node:fs";import{join as d}from"node:path";import{homedir as ee}from"node:os";import{join as u,resolve as lt}from"node:path";import{existsSync as ut}from"node:fs";import{createHash as pt}from"node:crypto";var x=".qlogicagent";function te(){return process.env.QLOGICAGENT_HOME||u(ee(),x)}function oe(){let o=process.env.QLOGIC_LLMROUTER_USER_ID?.trim();if(o)return o;let e=process.env.QLOGIC_IMPLICIT_OWNER_ID?.trim();if(e)return e;let t=process.env.QLOGIC_DEVICE_ID?.trim();return t?`oc_${t}`:"oc_local"}function re(o){let e=o.trim();if(!e)throw new Error("ownerUserId is required for profile-scoped storage");return encodeURIComponent(e).replace(/\./g,"%2E")}function f(o=oe()){return u(te(),"profiles",re(o))}function y(){return u(f(),"skills")}function A(){return u(f(),"mcp.json")}function ne(o){if(!o)throw new Error("getProjectAgentDir: cwd is required (must not fall back to process.cwd())");return u(o,x)}function R(o){return u(ne(o),"skills-disabled.json")}import{readFileSync as se,writeFileSync as gt,mkdirSync as ht,renameSync as yt}from"node:fs";import{dirname as Tt,join as ie}from"node:path";function E(){return{version:1,disabled:[]}}function M(o){try{let e=JSON.parse(se(o,"utf8"));return!e||typeof e!="object"||!Array.isArray(e.disabled)?E():{version:1,disabled:[...new Set(e.disabled.filter(r=>typeof r=="string"&&r.trim().length>0))]}}catch{return E()}}function ae(){return ie(f(),"skills-disabled.json")}function D(){return new Set(M(ae()).disabled)}function I(o){return new Set(M(R(o)).disabled)}var de=["auto-skill-","test-skill-"],pe=".skills_prompt_snapshot.json";function me(o){return de.some(e=>o.startsWith(e))}function fe(o){let e=o.replace(/\r\n/g,`
3
+ `),t=e.match(/^---\n[\s\S]*?^version:\s*(\S+)/m),r=e.match(/^---\n[\s\S]*?^description:\s*(.+)/m),n=e.match(/^---\n[\s\S]*?^category:\s*(.+)/m),s=e.match(/^---\n[\s\S]*?^author:\s*(.+)/m),i=e.match(/^---\n[\s\S]*?^requires:\s*(.+)/m),a=e.match(/^---\n[\s\S]*?^environments:\s*(.+)/m);return{version:t?.[1],description:r?.[1]?.trim(),category:n?.[1]?.trim()||void 0,author:s?.[1]?.trim()||void 0,requiredTools:j(i?.[1]),environments:j(a?.[1])}}function ge(o,e){let t=y(),r=D(),n=o?I(o):new Set,s=[],i;try{i=le(t,{withFileTypes:!0})}catch{i=[]}for(let a of i){if(!a.isDirectory()||a.name.startsWith("."))continue;let l=d(t,a.name),h=d(l,"SKILL.md"),k;try{if(!b(h).isFile())continue;k=ce(h,"utf8")}catch{continue}let P=r.has(a.name),C=n.has(a.name),c=fe(k),X=!he(c,e);s.push({name:a.name,filePath:h,baseDir:l,globallyDisabled:P,projectDisabled:C,active:!P&&!C&&!X,systemGenerated:me(a.name),description:c.description,version:c.version,category:c.category,author:c.author,requiredTools:c.requiredTools,environments:c.environments})}return ye(t,s),s}function L(o,e){return ge(o,e).filter(t=>t.active)}function O(o){let e=y(),t=d(e,o),r=d(t,"SKILL.md");try{if(b(r).isFile())return{baseDir:t,filePath:r}}catch{}return null}function j(o){if(!o)return;let e=o.replace(/^\[|\]$/g,"").split(/[,\s]+/).map(t=>t.replace(/^["']|["']$/g,"").trim()).filter(Boolean);return e.length>0?e:void 0}function he(o,e){if(o.environments&&e?.currentEnvironment&&!o.environments.includes(e.currentEnvironment))return!1;if(o.requiredTools){if(!e?.availableToolNames)return!1;let t=new Set(e.availableToolNames);return o.requiredTools.every(r=>t.has(r))}return!0}function ye(o,e){try{let r={version:1,entries:e.map(n=>{let s=b(n.filePath);return{name:n.name,size:s.size,mtimeMs:s.mtimeMs,description:n.description,version:n.version,requiredTools:n.requiredTools,environments:n.environments}})};ue(d(o,pe),JSON.stringify(r,null,2),"utf8")}catch{}}var N="astraclaw_capabilities";async function $(o){if(o.tool==="skills_list")return{handled:!0,result:{skills:L(o.projectRoot,{availableToolNames:o.availableToolNames,currentEnvironment:o.currentEnvironment??process.platform}).map(we)}};if(o.tool==="skill_view"){let e=typeof o.args.name=="string"?o.args.name.trim():"";if(!e)throw new Error("skill_view requires a skill name");let t=O(e);if(!t)throw new Error(`Skill not found: ${e}`);return{handled:!0,result:{name:e,content:be(t.filePath,"utf8")}}}if(o.tool==="mcp_connectors_list"){if(!o.toolCatalog)return{handled:!0,result:{tools:[],note:"AstraClaw MCP connector catalog is not attached to this capability server instance."}};let e=o.toolCatalog.getToolManifest().filter(t=>t.function.name.startsWith("mcp__")).map(t=>({toolName:t.function.name,server:Se(t.function.name),name:_e(t.function.name),description:t.function.description,inputSchema:t.function.parameters}));return{handled:!0,result:{tools:e,count:e.length}}}if(o.tool==="mcp_tool_call"){if(!o.toolCatalog)throw new Error("AstraClaw MCP connector catalog is not attached");let e=typeof o.args.toolName=="string"?o.args.toolName.trim():"";if(!e.startsWith("mcp__"))throw new Error("mcp_tool_call requires a connector toolName returned by mcp_connectors_list");let t=o.toolCatalog.findTool(e);if(!t?.execute)throw new Error(`MCP connector tool not found or not executable: ${e}`);let r=ve(o.args.args),n=`astraclaw_mcp_${Te().slice(0,8)}`,s=await t.execute(n,r);return{handled:!0,result:{toolName:e,content:s.content,details:s.details,imageUrls:s.imageUrls}}}return{handled:!1}}function we(o){return{name:o.name,description:o.description,version:o.version,category:o.category,requiredTools:o.requiredTools,environments:o.environments}}function ve(o){return typeof o=="object"&&o!==null&&!Array.isArray(o)?o:{}}function Se(o){let e=o.split("__");return e.length>=3?e[1]??"":""}function _e(o){let e=o.split("__");return e.length>=3?e.slice(2).join("__"):o}import p from"node:path";import{fileURLToPath as Ae}from"node:url";import{createRuntime as Re}from"mcporter";var U=new Set(["gateway","agents_list","session_status","sessions_send","sessions_list","sessions_history","sessions_spawn","cron","config","workflow"]),ke=new Set([...U,"agent"]);function q(o){return o?.riskLevel?o.riskLevel:o?.isDangerous?"system":o?.isReadOnly?"read":o?.isEgress?"external_egress":"write"}var T=class{toolPool=new Map;setTools(e){this.toolPool.clear();for(let t of e)this.toolPool.set(t.name,t)}addTool(e){this.toolPool.set(e.name,e)}addTools(e){for(let t of e)this.addTool(t)}removeTool(e){return this.toolPool.delete(e)}findTool(e){return this.toolPool.get(e)}hasTool(e){return this.toolPool.has(e)}getToolNames(){return Array.from(this.toolPool.keys())}getToolCount(){return this.toolPool.size}async executeTool(e,t,r,n){let s=this.toolPool.get(e);if(!s)throw new Error(`Tool not found: ${e}`);return s.execute(t,r,n)}getTools(){return Array.from(this.toolPool.values()).filter(e=>e.isEnabled?.()!==!1)}getToolManifest(){let e=[];for(let t of this.toolPool.values()){if(t.isEnabled?.()===!1)continue;let r=q(t);r==="write"&&(t.category==="media"||t.category==="web"||t.category==="mcp"?r="external_egress":(t.category==="system"||t.name==="exec"||t.name==="computer")&&(r="system")),e.push({type:"function",function:{name:t.name,description:t.description,parameters:t.parameters},meta:{category:t.category??"other",displayName:t.displayName??{key:`capability.tool.${t.name}.name`,fallback:t.label},displayDescription:t.displayDescription??{key:`capability.tool.${t.name}.description`,fallback:""},parallelSafe:t.isConcurrencySafe??!1,riskLevel:r,isReadOnly:r==="read",isDangerous:r==="system",isDelete:t.isDelete??!1,isEgress:t.isEgress??r==="external_egress",egressCarriesData:t.egressCarriesData??!1}})}return e}},W=new T;function F(o){W.addTools(o)}function G(o){return W.removeTool(o)}var Pe=new Set(["describe","fetch","find","get","inspect","list","query","read","search","stat"]),Ce=new Set(["append","copy","create","delete","edit","move","patch","remove","rename","set","update","upload","write"]);function B(o){let e=o.annotations,t=xe(o.name),r=t.some(l=>Ce.has(l)),n=t.some(l=>Pe.has(l)),s=e?.destructiveHint===!0||r,i=e?.readOnlyHint===!0||!s&&n,a=i||e?.idempotentHint===!0&&!s;return{isReadOnly:i,isConcurrencySafe:a,isDestructive:s}}function xe(o){return o.replace(/([a-z0-9])([A-Z])/g,"$1_$2").toLowerCase().split(/[^a-z0-9]+/).filter(Boolean)}var Ee=12e4,w=3,Me=1e3;async function De(o,e){let t=e.sleep??(s=>new Promise(i=>setTimeout(i,s))),r=Math.max(1,e.attempts),n;for(let s=1;s<=r;s++)try{return await o()}catch(i){n=i,s<r&&(e.onRetry?.(s,i),await t(e.baseDelayMs*s))}throw n}var g=class{runtime=null;definitions=[];toolsByServer=new Map;injectedNames=new Set;log;workspaceRoot;toolCatalog;serverToolFilters=new Map;serverOAuth=new Map;constructor(e){this.log=e.log??{info:()=>{},warn:()=>{}},this.workspaceRoot=e.workspaceRoot?p.resolve(e.workspaceRoot):void 0,this.toolCatalog=e.toolCatalog,this.definitions=e.servers.filter(t=>!t.disabled).filter(t=>{let r=ze(t);for(let n of r)this.log.warn(`[mcp] rejected server "${t.name}": ${n.message}`);return t.tools&&this.serverToolFilters.set(t.name,t.tools),t.oauth===!0&&this.serverOAuth.set(t.name,!0),r.length===0}).map(t=>this.toServerDefinition(t)).filter(t=>t!==null)}toServerDefinition(e){if((e.type??(e.url?"http":"stdio"))==="http"){if(!e.url)return this.log.warn(`[mcp] server "${e.name}" is type "http" but has no url, skipping`),null;let r;try{r=new URL(e.url)}catch{return this.log.warn(`[mcp] server "${e.name}" has an invalid url "${e.url}", skipping`),null}return{name:e.name,command:{kind:"http",url:r,headers:e.headers}}}return e.command?{name:e.name,command:{kind:"stdio",command:e.command,args:e.args??[],cwd:e.cwd??this.workspaceRoot??process.cwd()},env:e.env}:(this.log.warn(`[mcp] server "${e.name}" is type "stdio" but has no command, skipping`),null)}async connectAll(){this.definitions.length!==0&&(this.runtime=await Re({servers:this.definitions,clientInfo:{name:"qlogicagent",version:"1.0.0"},logger:{info:e=>this.log.info(`[mcp] ${e}`),warn:e=>this.log.warn(`[mcp] ${e}`),error:(e,t)=>this.log.warn(`[mcp] ${e}${t?` (${t instanceof Error?t.message:String(t)})`:""}`),debug:()=>{}}}),await Promise.allSettled(this.definitions.map(async e=>{try{let t=await De(()=>this.runtime.listTools(e.name,{includeSchema:!0,disableOAuth:this.serverOAuth.get(e.name)!==!0}),{attempts:w,baseDelayMs:Me,onRetry:(n,s)=>this.log.warn(`[mcp] connect attempt ${n}/${w} for ${e.name} failed, retrying: ${s instanceof Error?s.message:s}`)}),r=J(t,this.serverToolFilters.get(e.name));this.toolsByServer.set(e.name,r),this.log.info(`[mcp] connected to ${e.name} (${r.length}/${t.length} tools)`)}catch(t){this.log.warn(`[mcp] failed to connect to ${e.name} after ${w} attempts: ${t instanceof Error?t.message:t}`)}})))}injectTools(){for(let[e,t]of this.toolsByServer){let r=this.injectPortableTools(e,t);this.log.info(`[mcp] injected ${r.length} tools from ${e}`)}}async refreshServerTools(e){if(!this.runtime)throw new Error("MCP runtime is not connected");let t=await this.runtime.listTools(e,{includeSchema:!0,disableOAuth:this.serverOAuth.get(e)!==!0}),r=J(t,this.serverToolFilters.get(e));return this.toolsByServer.set(e,r),this.retractServerTools(e),this.injectPortableTools(e,r),r.length}getConnectedServers(){return Array.from(this.toolsByServer.keys())}getToolCount(){let e=0;for(let t of this.toolsByServer.values())e+=t.length;return e}async disconnectAll(){for(let t of this.injectedNames)this.removeRegisteredTool(t);this.injectedNames.clear(),this.toolsByServer.clear();let e=this.runtime;if(this.runtime=null,e)try{await e.close()}catch{}}toPortableTool(e,t){let r=B({name:t.name,description:t.description});return{name:`mcp__${H(e)}__${t.name}`,label:`[${e}] ${t.name}`,category:"mcp",description:t.description??`MCP tool from ${e}`,parameters:t.inputSchema??{type:"object",properties:{}},isConcurrencySafe:r.isConcurrencySafe,isReadOnly:r.isReadOnly,isDestructive:r.isDestructive,searchHint:`mcp ${e} ${t.name.replace(/[_-]+/g," ")}`,execute:async(n,s)=>{let i=this.runtime;if(!i)return{content:[{type:"text",text:`MCP server "${e}" is not connected.`}],details:{error:"mcp_not_connected"}};try{let a=await i.callTool(e,t.name,{args:s,timeoutMs:Ee});return Ie(a)}catch(a){let l=a instanceof Error?a.message:String(a);return{content:[{type:"text",text:`MCP tool error: ${l}`}],details:{error:l}}}}}}wrapToolsForWorkspaceBoundary(e){return this.workspaceRoot?e.map(t=>{if(!t.execute)return t;let r=t.execute;return{...t,execute:async(n,s,i)=>{let a=Le(t.name,s,this.workspaceRoot);return a?{content:[{type:"text",text:a}],details:{type:"mcp",error:"workspace_boundary"}}:r(n,s,i)}}}):e}injectPortableTools(e,t){let r=this.wrapToolsForWorkspaceBoundary(t.map(n=>this.toPortableTool(e,n)));this.registerTools(r);for(let n of r)this.injectedNames.add(n.name);return r}retractServerTools(e){let t=`mcp__${H(e)}__`;for(let r of[...this.injectedNames])r.startsWith(t)&&(this.removeRegisteredTool(r),this.injectedNames.delete(r))}registerTools(e){this.toolCatalog?this.toolCatalog.addTools(e):F(e)}removeRegisteredTool(e){return this.toolCatalog?.removeTool(e)??G(e)}};function Ie(o){let e=o&&typeof o=="object"?o:{};return{content:[{type:"text",text:(Array.isArray(e.content)?e.content:[]).filter(s=>!!s&&typeof s=="object").filter(s=>s.type==="text"&&typeof s.text=="string").map(s=>s.text).join(`
4
+ `)||"(no text output)"}],...e.isError?{details:{error:"mcp_tool_error"}}:{}}}function H(o){return o.replace(/[^a-zA-Z0-9_]/g,"_").toLowerCase()}var je=/(?:^|_)(path|paths|file|files|filename|filenames|dir|dirs|directory|directories|folder|folders|cwd|root|uri)(?:$|_)/i;function Le(o,e,t){if(!t)return null;for(let r of S(e)){let n=$e(r.value,t);if(n&&!Ue(n.candidate,n.root,n.pathApi))return`Blocked: MCP tool "${o}" path "${r.value}" is outside the workspace boundary "${n.root}"`}return null}function S(o,e="",t=0){if(t>8)return[];if(typeof o=="string")return Oe(e,o)?[{key:e,value:o}]:[];if(Array.isArray(o))return o.flatMap(n=>S(n,e,t+1));if(!o||typeof o!="object")return[];let r=[];for(let[n,s]of Object.entries(o))r.push(...S(s,n,t+1));return r}function Oe(o,e){if(!je.test(o))return!1;let t=e.trim();return!t||/^https?:\/\//i.test(t)?!1:Ne(t)}function Ne(o){return p.isAbsolute(o)||/^[A-Za-z]:[\\/]/.test(o)||/^\\\\/.test(o)||/^file:\/\//i.test(o)||o.includes("../")||o.includes("..\\")||o.startsWith(".\\")||o.startsWith("./")||o.includes("/")||o.includes("\\")}function $e(o,e){let t=o.trim();if(/^file:\/\//i.test(t))try{t=Ae(t)}catch{return null}let r=z(t)||z(e)?p.win32:p,n=r.resolve(e);return{candidate:r.isAbsolute(t)?r.resolve(t):r.resolve(n,t),root:n,pathApi:r}}function Ue(o,e,t){let r=t.relative(e,o);return r===""||!r.startsWith("..")&&!t.isAbsolute(r)}function z(o){return/^[A-Za-z]:[\\/]/.test(o)||/^\\\\/.test(o)}function Q(o){if(!o||typeof o!="object")return[];let e=o,t=[],r=e.mcpServers??e.servers??e;for(let[n,s]of Object.entries(r)){if(!s||typeof s!="object")continue;let i=s;if(typeof i.url=="string"){t.push({name:n,type:"http",url:i.url,headers:i.headers&&typeof i.headers=="object"?i.headers:void 0,disabled:i.disabled===!0,oauth:i.oauth===!0,tools:V(i.tools)});continue}typeof i.command=="string"&&t.push({name:n,type:"stdio",command:i.command,args:Array.isArray(i.args)?i.args:void 0,env:i.env&&typeof i.env=="object"?i.env:void 0,cwd:typeof i.cwd=="string"?i.cwd:void 0,disabled:i.disabled===!0,oauth:i.oauth===!0,tools:V(i.tools)})}return t}function J(o,e){let t=new Set(e?.include??[]),r=new Set(e?.exclude??[]);return o.filter(n=>t.size>0&&!t.has(n.name)?!1:!r.has(n.name))}function V(o){if(!o||typeof o!="object")return;let e=o,t=K(e.include),r=K(e.exclude);if(!(!t&&!r))return{...t?{include:t}:{},...r?{exclude:r}:{}}}function K(o){if(!Array.isArray(o))return;let e=o.filter(t=>typeof t=="string"&&t.trim().length>0);return e.length>0?e:void 0}var qe=new Set(["bash","sh","zsh","fish","cmd","cmd.exe","powershell","powershell.exe","pwsh","pwsh.exe"]),We=new Set(["-c","/c","-command","-encodedcommand","-e"]),Fe=["curl","wget","invokewebrequest","invoke-restmethod","iwr","irm","nc","netcat"],Ge=["env","processenv","apikey","api_key","token","secret","credential","credentials","awsaccesskey","awssecret"],Be=["authorizedkeys","sshknownhosts","crontab","launchctl","schtasks","startup","runkey","runonce","bashrc","profile"],He=/\bssh-(?:rsa|ed25519|ecdsa-[^\s]+)\s+[A-Za-z0-9+/=]{40,}/i;function ze(o){let e=[],t=[o.command??"",...o.args??[],...Object.keys(o.env??{}),...Object.values(o.env??{})].join(`
5
+ `);if(He.test(t)&&e.push({code:"ioc_public_key",message:"configuration contains an inline SSH public key IOC"}),(o.type??(o.url?"http":"stdio"))!=="stdio"||!Je(o.command))return e;let r=Ve(o.args??[]);if(!r)return e;let n=Ke(r);return v(n,Be)&&e.push({code:"persistence",message:"stdio shell command attempts a persistence write"}),v(n,Fe)&&v(n,Ge)&&e.push({code:"dangerous_egress",message:"stdio shell command combines network egress with likely secret access"}),e}function Je(o){if(!o)return!1;let e=p.basename(o).toLowerCase();return qe.has(e)}function Ve(o){for(let e=0;e<o.length;e++){let t=o[e]?.toLowerCase();if(t&&We.has(t))return o.slice(e+1).join(" ")}return""}function Ke(o){return o.toLowerCase().replace(/[^a-z0-9]+/g,"")}function v(o,e){return e.some(t=>o.includes(t))}var Xe=[{name:"skills_list",description:"List AstraClaw skills active for the current workspace.",inputSchema:{type:"object",properties:{},additionalProperties:!1}},{name:"skill_view",description:"Read the full SKILL.md instructions for one AstraClaw skill.",inputSchema:{type:"object",properties:{name:{type:"string",description:"Skill name from skills_list."}},required:["name"],additionalProperties:!1}},{name:"mcp_connectors_list",description:"List MCP connector tools installed in AstraClaw's Plugins page and shared with this external agent.",inputSchema:{type:"object",properties:{},additionalProperties:!1}},{name:"mcp_tool_call",description:"Call one AstraClaw shared MCP connector tool by toolName from mcp_connectors_list.",inputSchema:{type:"object",properties:{toolName:{type:"string",description:"Connector tool name, for example mcp__filesystem__read_text_file."},args:{type:"object",description:"Arguments for the connector tool."}},required:["toolName"],additionalProperties:!1}}],_=class{tools=new Map;findTool(e){return this.tools.get(e)}getToolManifest(){return Array.from(this.tools.values()).map(e=>({type:"function",function:{name:e.name,description:e.description,parameters:e.parameters}}))}getToolNames(){return Array.from(this.tools.keys())}setTools(e){this.tools.clear(),this.addTools(e)}addTool(e){this.tools.set(e.name,e)}addTools(e){for(let t of e)this.addTool(t)}removeTool(e){return this.tools.delete(e)}},m=null,et=Qe({input:process.stdin,crlfDelay:Number.POSITIVE_INFINITY});et.on("line",o=>{tt(o)});async function tt(o){let e=o.trim();if(!e)return;let t;try{t=JSON.parse(e)}catch{return}if(t.id===void 0||t.id===null)return t.method==="notifications/initialized",void 0;try{let r=await ot(t);Z({jsonrpc:"2.0",id:t.id,result:r})}catch(r){Z({jsonrpc:"2.0",id:t.id,error:{code:-32603,message:r instanceof Error?r.message:String(r)}})}}async function ot(o){switch(o.method){case"initialize":return{protocolVersion:"2024-11-05",capabilities:{tools:{}},serverInfo:{name:N,version:"1.0.0"}};case"tools/list":return{tools:Xe};case"tools/call":{let e=Y(o.params),t=typeof e.name=="string"?e.name:"",r=Y(e.arguments),n=rt(),s=t==="mcp_connectors_list"||t==="mcp_tool_call"?await nt(n):void 0,i=await $({tool:t,args:r,projectRoot:n,toolCatalog:s});if(!i.handled)throw new Error(`Unknown tool: ${t}`);return{content:[{type:"text",text:JSON.stringify(i.result)}]}}default:throw new Error(`Unknown method: ${o.method}`)}}function rt(){return process.env.ASTRACLAW_PROJECT_ROOT||process.env.QLOGICAGENT_PROJECT_ROOT||process.cwd()}async function nt(o){if(m?.projectRoot===o)return await m.ready,m.catalog;m?.manager?.disconnectAll().catch(()=>{});let e=new _,t=new g({servers:st(),workspaceRoot:o,toolCatalog:e,log:{info:n=>process.stderr.write(`${n}
6
+ `),warn:n=>process.stderr.write(`${n}
7
+ `)}}),r=t.connectAll().then(()=>t.injectTools());return m={projectRoot:o,catalog:e,manager:t,ready:r},await r,e}function st(){let o=process.env.ASTRACLAW_MCP_CONFIG_PATH||A();if(!Ye(o))return[];try{let e=Ze(o,"utf8").replace(/^\uFEFF/,"");return Q(JSON.parse(e))}catch(e){return process.stderr.write(`[astraclaw-native-mcp] failed to read MCP config: ${e instanceof Error?e.message:String(e)}
8
+ `),[]}}function Y(o){return typeof o=="object"&&o!==null&&!Array.isArray(o)?o:{}}function Z(o){process.stdout.write(`${JSON.stringify(o)}
9
+ `)}
@@ -40,6 +40,7 @@ export declare function createArtifactContractState(inputMessages: readonly Chat
40
40
  export declare function requiresBrowserAcceptance(inputMessages: readonly ChatMessage[]): boolean;
41
41
  export declare function requiresBuildVerification(inputMessages: readonly ChatMessage[]): boolean;
42
42
  export declare function requiresResearchQualityGate(inputMessages: readonly ChatMessage[]): boolean;
43
+ export declare function requiresFinalStatusAudit(inputMessages: readonly ChatMessage[]): boolean;
43
44
  export declare function hasFinalStatusContract(content: string): boolean;
44
45
  export declare function hasResearchQualityGate(content: string): boolean;
45
46
  export declare function toolCallWritePath(toolName: string, rawArguments: string): string | null;
@@ -82,6 +83,8 @@ export declare function applyFinalStatusContract(params: {
82
83
  messages: readonly ChatMessage[];
83
84
  totalToolCallCount: number;
84
85
  distinctToolNames: Iterable<string>;
86
+ visibleToolCallCount?: number;
87
+ visibleToolNames?: Iterable<string>;
85
88
  artifactContract: ArtifactContractState;
86
89
  blockedToolCalls?: ReadonlyArray<{
87
90
  name: string;
@@ -22,6 +22,7 @@ export declare function resolveAutoToolChoice(params: {
22
22
  currentToolChoice?: TurnConfig["toolChoice"];
23
23
  explicitNoToolTurn: boolean;
24
24
  looksLikeBuildIntent: boolean;
25
+ requiresPreToolPreamble?: boolean;
25
26
  }): TurnConfig["toolChoice"] | undefined;
26
27
  export declare function hydrateAttachmentReferenceUrlsForTurn(messages: ChatMessage[]): ChatMessage[];
27
28
  export declare function getFocusedAttachmentToolNamesForTurn(messages: ChatMessage[]): Set<string> | null;
@@ -49,6 +50,7 @@ export declare function augmentLastUserMessageContent(messages: ChatMessage[], m
49
50
  */
50
51
  export declare function injectSkillContextBeforeLastUser(messages: ChatMessage[], skillContext: string): ChatMessage[];
51
52
  export declare function getFocusedAttachmentModelPurposeForTurn(messages: ChatMessage[]): FocusedAttachmentModelPurpose | null;
53
+ export declare function requiresPreToolPreambleForTurn(messages: readonly ChatMessage[]): boolean;
52
54
  export declare function refreshPermissionCheckerToolMeta(permissionChecker: PermissionMetadataResolver | null | undefined, tools: ToolDefinition[]): void;
53
55
  export declare function handleUserResponse(this: TurnControlHandlerHost, msg: AgentRpcRequest): void;
54
56
  export interface TurnPipelineInput {
@@ -44,6 +44,9 @@ export interface PetRuntime {
44
44
  forgeGeneratePetdexAtlas(input: PetdexForgeInput): Promise<PetdexForgeOutput>;
45
45
  awardXp(event: string): Promise<void>;
46
46
  reactAfterTurn(messages: ChatMessage[]): Promise<void>;
47
+ /** Backfill missing daily journey summaries (per-owner, cross-project, idempotent, best-effort).
48
+ * Owns the LLM + project-listing side effects so stdio-server doesn't reach into runtime/infra. */
49
+ catchUpJourney(memoryUserId: string): Promise<string[]>;
47
50
  isActive(): boolean;
48
51
  }
49
52
  export interface PetRuntimeDeps {
@@ -51,5 +54,7 @@ export interface PetRuntimeDeps {
51
54
  resolveOwnerUserId?: () => string;
52
55
  resolveClientForPurpose(purpose: ModelPurpose): PetLlmClient | null;
53
56
  sendNotification(method: string, params: Record<string, unknown>): void;
57
+ /** Project ids for the cross-project journey backfill — injected so pet-runtime never imports the project store. */
58
+ listProjectIds?: () => string[];
54
59
  }
55
60
  export declare function createPetRuntime(deps: PetRuntimeDeps): PetRuntime;
@@ -8,7 +8,12 @@ export interface SkillListEntry {
8
8
  globallyDisabled: boolean;
9
9
  /** Muted in THIS project only (the project opt-out). */
10
10
  projectDisabled: boolean;
11
- category: "automation";
11
+ /** Frontmatter classification (Hub category vocabulary) — drives the card palette/tag. Undefined for unclassified legacy skills. */
12
+ category?: string;
13
+ /** Frontmatter author — agent-authored skills carry "用户". */
14
+ author?: string;
15
+ /** Provenance (lifecycle store): learned/created/promoted/copied = local origin; "installed" = from the Hub. */
16
+ source?: "learned" | "created" | "installed" | "promoted";
12
17
  displayName: {
13
18
  key: string;
14
19
  fallback: string;
@@ -34,6 +34,7 @@ export declare class DelegationCoordinator {
34
34
  */
35
35
  delegate(agentId: string, brief: string, opts?: {
36
36
  acceptanceCriteria?: string;
37
+ sessionId?: string;
37
38
  }): {
38
39
  delegationId: string;
39
40
  };
@@ -99,6 +99,8 @@ export interface AgentProcessHandle {
99
99
  lastActivityAt?: number;
100
100
  /** Whether the ACP agent supports session/resume (D-1). */
101
101
  supportsResume?: boolean;
102
+ /** Whether the ACP agent advertised stdio MCP support during initialize. */
103
+ supportsStdioMcp?: boolean;
102
104
  /** ACP session/update types observed during the current external-agent prompt. */
103
105
  acpUpdateTypes?: string[];
104
106
  /** Stderr log path for this agent process, when diagnostics logging is enabled. */
@@ -139,6 +141,8 @@ export interface AgentProcessCallbacks {
139
141
  };
140
142
  /** Session directory for stderr logs. If set, ACP agent stderr is written to {sessionDir}/{agentId}.stderr.log. */
141
143
  sessionDir?: string;
144
+ /** Test hook: redirect native third-party MCP config writes away from the real user home. */
145
+ nativeMcpHomeDir?: string;
142
146
  }
143
147
  export declare function ensureLoopbackNoProxy(env: Record<string, string>): Record<string, string>;
144
148
  /**
@@ -184,6 +188,7 @@ export declare class AgentProcessManager {
184
188
  private callbacks;
185
189
  private cliBinaryPath;
186
190
  private mcpBridgeScriptPath;
191
+ private nativeMcpServerScriptPath;
187
192
  constructor(callbacks?: AgentProcessCallbacks);
188
193
  /** Format: [ISO] [level] [agent:id] [phase] message */
189
194
  private slog;
@@ -273,6 +278,7 @@ export declare class AgentProcessManager {
273
278
  * Uses the ACP protocol adapter for lifecycle management.
274
279
  */
275
280
  private spawnAcpAgent;
281
+ private syncNativeMcpConfig;
276
282
  /** Kill a specific child agent process. */
277
283
  kill(memberId: string): void;
278
284
  /** Kill all child processes and clean up. */
@@ -0,0 +1,30 @@
1
+ import type { ToolCatalog } from "../ports/index.js";
2
+ export declare const ASTRACLAW_CAPABILITIES_SERVER_NAME = "astraclaw_capabilities";
3
+ export declare const ASTRACLAW_CAPABILITIES_SERVER_ALIASES: readonly ["astraclaw_capabilities", "astraclaw"];
4
+ export interface AstraClawCapabilityToolInput {
5
+ tool: string;
6
+ args: Record<string, unknown>;
7
+ projectRoot: string;
8
+ availableToolNames?: Iterable<string>;
9
+ currentEnvironment?: string;
10
+ toolCatalog?: ToolCatalog;
11
+ }
12
+ export type AstraClawCapabilityToolResult = {
13
+ handled: false;
14
+ } | {
15
+ handled: true;
16
+ result: unknown;
17
+ };
18
+ export declare function buildAstraClawCapabilitiesSystemPrompt(input: {
19
+ projectRoot: string;
20
+ mcpAvailable: boolean;
21
+ availableToolNames?: Iterable<string>;
22
+ currentEnvironment?: string;
23
+ }): string | undefined;
24
+ export declare function buildAstraClawCapabilitiesPromptPreamble(input: {
25
+ projectRoot: string;
26
+ mcpAvailable: boolean;
27
+ availableToolNames?: Iterable<string>;
28
+ currentEnvironment?: string;
29
+ }): string | undefined;
30
+ export declare function handleAstraClawCapabilityToolCall(input: AstraClawCapabilityToolInput): Promise<AstraClawCapabilityToolResult>;
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Desktop (Electron-as-Node) npm bridge.
3
+ *
4
+ * The packaged desktop app is the ONLY Node/npm on a terminal user's machine: most machines have no
5
+ * system Node, so `npm i -g <agent>` through a plain shell fails with "'npm' is not recognized". The
6
+ * desktop ships npm INSIDE the runtime and exposes, via env, how to run it — `AstraClaw.exe npm-cli.js …`
7
+ * with ELECTRON_RUN_AS_NODE=1 (process.execPath runs as pure Node). These helpers let the install runner
8
+ * and the CLI detector drive THAT npm instead of a missing system npm.
9
+ *
10
+ * The env vars are set by the Electron shell (embedded-gateway) before it spawns the gateway that hosts
11
+ * qlogicagent. In every other environment (dev with a real Node, CI, tests, the cloud server) they are
12
+ * absent and {@link resolveElectronNpm} returns null, so callers fall back to the system `npm` unchanged.
13
+ */
14
+ /** Resolved desktop npm bridge (all paths absolute). */
15
+ export interface ElectronNpm {
16
+ /** The app binary (process.execPath), run as Node via ELECTRON_RUN_AS_NODE=1. */
17
+ nodeExe: string;
18
+ /** Bundled npm's CLI entry (…/node_modules/npm/bin/npm-cli.js). */
19
+ npmCli: string;
20
+ /** Writable global-install prefix (~/.xiaozhiclaw/npm-global), or undefined if the shell set none. */
21
+ prefix?: string;
22
+ }
23
+ /** Resolve the desktop npm bridge from env, or null when not running under the packaged desktop app. */
24
+ export declare function resolveElectronNpm(): ElectronNpm | null;
25
+ /**
26
+ * Build the env for a desktop npm invocation. CRITICAL: it KEEPS ELECTRON_RUN_AS_NODE=1 — npm shells out
27
+ * to `node` for lifecycle scripts / node-gyp, and those grandchildren must run as Node too, not pop the
28
+ * Electron GUI. Pins npm_config_prefix to the writable global prefix so `npm i -g` installs there (never
29
+ * read-only Program Files → EACCES) and `npm root -g` reports that same location for version detection.
30
+ */
31
+ export declare function electronNpmEnv(bridge: ElectronNpm): NodeJS.ProcessEnv;
32
+ /** True when `command` is an npm invocation the bridge should handle (checked after any mirror rewrite). */
33
+ export declare function isNpmCommand(command: string): boolean;
34
+ export declare function isForeignInstaller(command: string): boolean;
35
+ /** A copy of process.env with ELECTRON_RUN_AS_NODE stripped, for spawning non-Node foreign installers. */
36
+ export declare function envWithoutElectronRunAsNode(): NodeJS.ProcessEnv;
@@ -0,0 +1,16 @@
1
+ export type NativeMcpSyncStatus = "updated" | "unchanged" | "unsupported" | "skipped";
2
+ export interface NativeMcpSyncInput {
3
+ agentId: string;
4
+ projectRoot: string;
5
+ serverScriptPath: string;
6
+ nodePath?: string;
7
+ homeDir?: string;
8
+ codexHome?: string;
9
+ }
10
+ export interface NativeMcpSyncResult {
11
+ agentId: string;
12
+ status: NativeMcpSyncStatus;
13
+ configPath?: string;
14
+ reason?: string;
15
+ }
16
+ export declare function syncAstraClawNativeMcpConfig(input: NativeMcpSyncInput): NativeMcpSyncResult;
@@ -26,6 +26,10 @@ export interface ResolvedSkill {
26
26
  systemGenerated: boolean;
27
27
  description?: string;
28
28
  version?: string;
29
+ /** Frontmatter classification (Hub category vocabulary: efficiency/dev/writing/…) — drives the card palette. */
30
+ category?: string;
31
+ /** Frontmatter author — agent-authored skills carry "用户". */
32
+ author?: string;
29
33
  requiredTools?: string[];
30
34
  environments?: string[];
31
35
  }
@@ -1,3 +1,4 @@
1
+ import { type ElectronNpm } from "../../runtime/infra/electron-node.js";
1
2
  export interface MarketplaceSource {
2
3
  /** Source type */
3
4
  type: "npm" | "git" | "url";
@@ -39,6 +40,21 @@ export interface MarketplaceLogger {
39
40
  info(msg: string): void;
40
41
  warn(msg: string): void;
41
42
  }
43
+ /**
44
+ * Resolve the executable + argv for an npm invocation. The packaged desktop app is usually the ONLY
45
+ * Node/npm on a terminal user's machine, so `execFileAsync("npm", …)` fails there with "'npm' is not
46
+ * recognized". When the desktop bridge is present we run the bundled npm as `AstraClaw.exe npm-cli.js …`
47
+ * with ELECTRON_RUN_AS_NODE=1 (via electronNpmEnv); in dev / CI / server / tests the bridge is null and
48
+ * we fall back to the system `npm` unchanged. Same routing as the agent-install runner.
49
+ *
50
+ * Note: electronNpmEnv pins npm_config_prefix to the GLOBAL prefix only — it does not affect the local
51
+ * `npm install --production` this module runs inside each extracted plugin dir.
52
+ */
53
+ export declare function resolveNpmExec(bridge: ElectronNpm | null, args: string[]): {
54
+ file: string;
55
+ argv: string[];
56
+ env?: NodeJS.ProcessEnv;
57
+ };
42
58
  /**
43
59
  * Load plugins from cache only (no network). Fast startup path.
44
60
  * CC parity: loadAllPluginsCacheOnly()
@@ -22,6 +22,16 @@ export declare function generateSkillContent(params: {
22
22
  body: string;
23
23
  version?: string;
24
24
  category?: string;
25
+ author?: string;
26
+ }): string;
27
+ /**
28
+ * Inject frontmatter fields (author / category) into already-valid SKILL.md content when they're
29
+ * absent — used so agent-authored skills always carry an author ("用户") and a classification, even
30
+ * when the model's `content` didn't include them. Never overwrites a field the model did provide.
31
+ */
32
+ export declare function ensureFrontmatterFields(content: string, fields: {
33
+ author?: string;
34
+ category?: string;
25
35
  }): string;
26
36
  /**
27
37
  * Auto-fix common frontmatter issues in LLM-generated skill content.
@@ -64,7 +64,7 @@ export declare const SKILL_MANAGE_TOOL_SCHEMA: {
64
64
  };
65
65
  readonly category: {
66
66
  readonly type: "string";
67
- readonly description: "Optional category label for create/edit.";
67
+ readonly description: "Classification for create/edit — pick the ONE best-fitting label from: efficiency, dev, writing, data, search, automation, knowledge, lifestyle. Drives the skill card's color/tag on the plugins page.";
68
68
  };
69
69
  readonly content: {
70
70
  readonly type: "string";