@tamsensedev/runner 0.19.0 → 0.20.0

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/dist/cli.js CHANGED
@@ -3918,7 +3918,7 @@ function describeDevice(identity) {
3918
3918
  }
3919
3919
 
3920
3920
  // src/sense.ts
3921
- import { mkdtemp, rm as rm5 } from "fs/promises";
3921
+ import { mkdtemp, rm as rm5, stat as stat2 } from "fs/promises";
3922
3922
  import { tmpdir } from "os";
3923
3923
  import { dirname as dirname4, join as join9 } from "path";
3924
3924
  import process5 from "process";
@@ -3971,7 +3971,8 @@ async function sleep(ms, signal) {
3971
3971
 
3972
3972
  // src/sense.ts
3973
3973
  var SERVER_NAME3 = "tamsense";
3974
- var MAX_SENSE_TURNS = 3;
3974
+ var WEB_TOOL = "WebFetch";
3975
+ var MAX_SENSE_TURNS = 6;
3975
3976
  var HEARTBEAT_MS = 5e3;
3976
3977
  var STOPPED_HERE = "stopped_here";
3977
3978
  var CLOSED_ON_SITE = "closed_on_site";
@@ -3989,12 +3990,13 @@ function claudeSenseRunner(startQuery) {
3989
3990
  options: {
3990
3991
  cwd: request.cwd,
3991
3992
  systemPrompt: request.system,
3992
- tools: [],
3993
+ tools: [WEB_TOOL],
3993
3994
  settingSources: [],
3994
3995
  persistSession: false,
3996
+ ...request.resume === null ? {} : { resume: request.resume, forkSession: true },
3995
3997
  strictMcpConfig: true,
3996
3998
  mcpServers: tools.length > 0 ? { [SERVER_NAME3]: createSdkMcpServer3({ name: SERVER_NAME3, version: "1.0.0", tools }) } : {},
3997
- allowedTools: tools.map((item) => `mcp__${SERVER_NAME3}__${item.name}`),
3999
+ allowedTools: [WEB_TOOL, ...tools.map((item) => `mcp__${SERVER_NAME3}__${item.name}`)],
3998
4000
  permissionMode: "auto",
3999
4001
  canUseTool: denyWhenAsked,
4000
4002
  maxTurns: MAX_SENSE_TURNS,
@@ -4027,8 +4029,17 @@ function senseHeartbeat(api, job, abort) {
4027
4029
  }
4028
4030
  };
4029
4031
  }
4032
+ async function authorSession(job, dataFolder) {
4033
+ if (!job.author) {
4034
+ return null;
4035
+ }
4036
+ const folder = taskFolder(dataFolder, { taskId: job.taskId, stage: { columnId: job.author.columnId } });
4037
+ const found = await stat2(folder).catch(() => null);
4038
+ return found?.isDirectory() ? { folder, sessionId: job.author.sessionId } : null;
4039
+ }
4030
4040
  async function runSenseJob(job, runSense, tools, stop) {
4031
- const cwd = await mkdtemp(join9(tmpdir(), "tamsense-sense-"));
4041
+ const scratch = await mkdtemp(join9(tmpdir(), "tamsense-sense-"));
4042
+ const author = await authorSession(job, tools.dataFolder);
4032
4043
  const run2 = new AbortController();
4033
4044
  const stopRun = () => run2.abort(STOPPED_HERE);
4034
4045
  if (stop.aborted) {
@@ -4038,17 +4049,24 @@ async function runSenseJob(job, runSense, tools, stop) {
4038
4049
  const deadline = setTimeout(() => run2.abort(TOO_LONG), job.deadlineMs);
4039
4050
  const beat = senseHeartbeat(tools.api, job, () => run2.abort(CLOSED_ON_SITE));
4040
4051
  const beating = setInterval(() => void beat(), HEARTBEAT_MS);
4041
- let body;
4052
+ let body = FAILED;
4042
4053
  try {
4043
4054
  const env = withNodeOnPath(process5.env, dirname4(process5.execPath));
4044
- body = await runSense({ system: job.system, prompt: job.prompt, schema: job.schema, model: job.model, cwd, env, signal: run2.signal, sources: sourceTools(job.tools, tools.api, sensePath(job.senseId, "sources")) });
4055
+ const sources = sourceTools(job.tools, tools.api, sensePath(job.senseId, "sources"));
4056
+ const ask = (cwd, resume) => runSense({ system: job.system, prompt: job.prompt, schema: job.schema, model: job.model, cwd, resume, env, signal: run2.signal, sources }).catch(() => FAILED);
4057
+ if (author) {
4058
+ body = await ask(author.folder, author.sessionId);
4059
+ }
4060
+ if (body.outcome === "failed" && !run2.signal.aborted) {
4061
+ body = await ask(scratch, null);
4062
+ }
4045
4063
  } catch {
4046
4064
  body = FAILED;
4047
4065
  } finally {
4048
4066
  clearTimeout(deadline);
4049
4067
  clearInterval(beating);
4050
4068
  stop.removeEventListener("abort", stopRun);
4051
- await rm5(cwd, { recursive: true, force: true });
4069
+ await rm5(scratch, { recursive: true, force: true });
4052
4070
  }
4053
4071
  if (run2.signal.reason === CLOSED_ON_SITE) {
4054
4072
  return;
@@ -4218,7 +4236,7 @@ async function prepareLibrary(folder, skills, source = LIBRARY_PATH) {
4218
4236
  // src/recordings.ts
4219
4237
  import { randomUUID } from "crypto";
4220
4238
  import { createReadStream } from "fs";
4221
- import { readdir as readdir2, readFile as readFile4, stat as stat2, writeFile as writeFile4 } from "fs/promises";
4239
+ import { readdir as readdir2, readFile as readFile4, stat as stat3, writeFile as writeFile4 } from "fs/promises";
4222
4240
  import { basename as basename4, dirname as dirname5, extname as extname2, isAbsolute as isAbsolute3, join as join11 } from "path";
4223
4241
  import { createInterface as createInterface2 } from "readline";
4224
4242
  import { Readable } from "stream";
@@ -4325,7 +4343,7 @@ async function remember(dir, file) {
4325
4343
  await writeFile4(join11(dir, LEDGER_FILE), JSON.stringify([...done2]));
4326
4344
  }
4327
4345
  async function nonEmpty(file) {
4328
- return file !== null && ((await stat2(file).catch(() => null))?.size ?? 0) > 0;
4346
+ return file !== null && ((await stat3(file).catch(() => null))?.size ?? 0) > 0;
4329
4347
  }
4330
4348
  async function pendingRecordings(dir) {
4331
4349
  const done2 = await ledger(dir);
@@ -4360,7 +4378,7 @@ async function uploadReplay(upload, file, label) {
4360
4378
  }
4361
4379
  async function uploadVideo(upload, file, label, meta) {
4362
4380
  const contentType = VIDEO_TYPES_BY_EXTENSION[extname2(file).toLowerCase()];
4363
- const info = await stat2(file);
4381
+ const info = await stat3(file);
4364
4382
  if (info.size > MAX_VIDEO_BYTES) {
4365
4383
  throw new Error(`The video is larger than ${MAX_VIDEO_BYTES / 1024 / 1024} MB. Record a shorter one.`);
4366
4384
  }
@@ -4449,7 +4467,7 @@ async function uploadPendingRecordings(api, runId, dir) {
4449
4467
  }
4450
4468
 
4451
4469
  // src/screenshot.ts
4452
- import { readFile as readFile5, stat as stat3 } from "fs/promises";
4470
+ import { readFile as readFile5, stat as stat4 } from "fs/promises";
4453
4471
  import { basename as basename5, extname as extname3, isAbsolute as isAbsolute4 } from "path";
4454
4472
  var TYPES_BY_EXTENSION2 = {
4455
4473
  ".png": "image/png",
@@ -4471,7 +4489,7 @@ async function readScreenshot(request) {
4471
4489
  } catch (error) {
4472
4490
  return error instanceof Error ? error.message : String(error);
4473
4491
  }
4474
- const info = await stat3(request.path).catch(() => null);
4492
+ const info = await stat4(request.path).catch(() => null);
4475
4493
  if (!info?.isFile()) {
4476
4494
  return `There is no file at ${request.path}.`;
4477
4495
  }