@papercraneai/sandbox-agent 0.1.10 → 0.1.12

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 (2) hide show
  1. package/dist/index.js +41 -8
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -373,6 +373,8 @@ const app = express();
373
373
  app.use(express.json());
374
374
  const PORT = cliArgs.agentPort;
375
375
  let PROJECT_DIR = getProjectDir(); // Will be updated after template setup
376
+ // Per-request UI context for GetContext tool (keyed by requestId, cleaned up after each turn)
377
+ const sessionContext = {};
376
378
  // Registration state
377
379
  let environmentId = null;
378
380
  let connectionToken = null;
@@ -390,10 +392,17 @@ const showPreviewTool = tool("ShowPreview", "Shows the preview iframe for a spec
390
392
  });
391
393
  // Factory to create a fresh MCP server per chat session
392
394
  // (each Protocol instance can only be connected to one transport at a time)
393
- function createClientToolsServer() {
395
+ // contextKey links to the per-request entry in sessionContext for GetContext
396
+ function createClientToolsServer(contextKey) {
397
+ const getContextTool = tool("GetContext", "Returns the current UI context — which dashboard the user is viewing in the preview panel. Use this when the user's request is ambiguous about which dashboard they're referring to.", {}, async () => ({
398
+ content: [{
399
+ type: "text",
400
+ text: JSON.stringify(sessionContext[contextKey] || { selectedDashboard: null })
401
+ }]
402
+ }));
394
403
  return createSdkMcpServer({
395
404
  name: "client-tools",
396
- tools: [showPreviewTool]
405
+ tools: [showPreviewTool, getContextTool]
397
406
  });
398
407
  }
399
408
  // Recursively build file tree
@@ -1125,7 +1134,7 @@ const buildHooks = (res, verbose, requestId, requestStartTime) => {
1125
1134
  app.post("/chat", async (req, res) => {
1126
1135
  const requestStartTime = Date.now();
1127
1136
  const requestId = Math.random().toString(36).substring(7);
1128
- const { message, sessionId, systemPrompt, verbose = false, subdir,
1137
+ const { message, sessionId, systemPrompt, verbose = false, subdir, selectedDashboard,
1129
1138
  // Configurable agent options
1130
1139
  maxTurns = 40, allowedTools, disallowedTools, model, maxBudgetUsd } = req.body;
1131
1140
  const ctx = { requestId, sessionId };
@@ -1187,9 +1196,12 @@ app.post("/chat", async (req, res) => {
1187
1196
  "WebSearch",
1188
1197
  "AgentOutput",
1189
1198
  "KillShell",
1190
- "mcp__client-tools__ShowPreview"
1199
+ "mcp__client-tools__ShowPreview",
1200
+ "mcp__client-tools__GetContext"
1191
1201
  ];
1192
- const clientTools = createClientToolsServer();
1202
+ // Store UI context for GetContext tool
1203
+ sessionContext[requestId] = { selectedDashboard: selectedDashboard || null };
1204
+ const clientTools = createClientToolsServer(requestId);
1193
1205
  const options = {
1194
1206
  maxTurns,
1195
1207
  cwd,
@@ -1238,7 +1250,21 @@ app.post("/chat", async (req, res) => {
1238
1250
  const resultWithModel = { ...msg, model: usedModel };
1239
1251
  res.write(`data: ${JSON.stringify(resultWithModel)}\n\n`);
1240
1252
  gotResult = true;
1241
- log.info({ ...ctx, elapsed: Date.now() - requestStartTime }, "Query complete");
1253
+ const baseLogCtx = {
1254
+ ...ctx,
1255
+ elapsed: Date.now() - requestStartTime,
1256
+ subtype: msg.subtype,
1257
+ num_turns: msg.num_turns,
1258
+ is_error: msg.is_error,
1259
+ cost_usd: msg.total_cost_usd
1260
+ };
1261
+ if (msg.is_error) {
1262
+ const errMsg = msg;
1263
+ log.warn({ ...baseLogCtx, errors: errMsg.errors }, `Query ended with error: ${msg.subtype}`);
1264
+ }
1265
+ else {
1266
+ log.info(baseLogCtx, "Query complete");
1267
+ }
1242
1268
  res.write("data: [DONE]\n\n");
1243
1269
  res.end();
1244
1270
  return;
@@ -1261,6 +1287,7 @@ app.post("/chat", async (req, res) => {
1261
1287
  const isAbortError = error instanceof Error &&
1262
1288
  (error.name === "AbortError" || error.constructor.name === "AbortError");
1263
1289
  if (isAbortError || isAborted) {
1290
+ log.info(ctx, "Chat request aborted by client");
1264
1291
  if (!res.writableEnded) {
1265
1292
  res.write(`data: ${JSON.stringify({ type: "aborted" })}\n\n`);
1266
1293
  res.write("data: [DONE]\n\n");
@@ -1268,17 +1295,23 @@ app.post("/chat", async (req, res) => {
1268
1295
  }
1269
1296
  return;
1270
1297
  }
1271
- log.error({ ...ctx, error: error instanceof Error ? error.message : String(error) }, "Agent error");
1298
+ log.error({
1299
+ ...ctx,
1300
+ err: error instanceof Error ? error.message : String(error),
1301
+ stack: error instanceof Error ? error.stack : undefined
1302
+ }, "Unhandled error in chat stream — stream terminated");
1272
1303
  if (!res.writableEnded) {
1273
1304
  res.write(`data: ${JSON.stringify({
1274
1305
  type: "error",
1275
1306
  error: error instanceof Error ? error.message : "Unknown error"
1276
1307
  })}\n\n`);
1308
+ res.write("data: [DONE]\n\n");
1277
1309
  res.end();
1278
1310
  }
1279
1311
  }
1280
1312
  finally {
1281
- // Close the per-session MCP server to free resources
1313
+ // Clean up per-request context and close MCP server
1314
+ delete sessionContext[requestId];
1282
1315
  await clientTools.instance?.close().catch(() => { });
1283
1316
  }
1284
1317
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@papercraneai/sandbox-agent",
3
- "version": "0.1.10",
3
+ "version": "0.1.12",
4
4
  "description": "Claude Agent SDK server for sandbox environments",
5
5
  "license": "MIT",
6
6
  "type": "module",