@tamsensedev/runner 0.19.0 → 0.20.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +64 -22
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -906,6 +906,7 @@ function helperStopRequest(stop) {
|
|
|
906
906
|
var DEFAULT_MODEL = "default";
|
|
907
907
|
var QUIET_MS = 1e3;
|
|
908
908
|
var IDLE_LIMIT_MS = 3e4;
|
|
909
|
+
var BACKGROUND_LIMIT_MS = 6e5;
|
|
909
910
|
function userMessage(text2) {
|
|
910
911
|
return { type: "user", message: { role: "user", content: text2 }, parent_tool_use_id: null };
|
|
911
912
|
}
|
|
@@ -926,6 +927,12 @@ function switchLine(change) {
|
|
|
926
927
|
function isActivity(message) {
|
|
927
928
|
return message.type === "assistant" || message.type === "user" || message.type === "system" && message.subtype === "init";
|
|
928
929
|
}
|
|
930
|
+
function backgroundOf(message) {
|
|
931
|
+
if (message.type !== "system" || message.subtype !== "background_tasks_changed") {
|
|
932
|
+
return null;
|
|
933
|
+
}
|
|
934
|
+
return message.tasks.filter((task) => !task.ambient).length;
|
|
935
|
+
}
|
|
929
936
|
function inputChannel(options = {}) {
|
|
930
937
|
const queue = [];
|
|
931
938
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -941,6 +948,7 @@ function inputChannel(options = {}) {
|
|
|
941
948
|
let quiet = false;
|
|
942
949
|
let idleEpoch = 0;
|
|
943
950
|
let inFlight = 0;
|
|
951
|
+
let background = 0;
|
|
944
952
|
let statesSeen = false;
|
|
945
953
|
let wake = null;
|
|
946
954
|
let timers = [];
|
|
@@ -966,7 +974,7 @@ function inputChannel(options = {}) {
|
|
|
966
974
|
wakeUp();
|
|
967
975
|
}
|
|
968
976
|
function maybeClose() {
|
|
969
|
-
if (started && idle && drained && quiet && queue.length === 0 && inFlight === 0) {
|
|
977
|
+
if (started && idle && drained && quiet && background === 0 && queue.length === 0 && inFlight === 0) {
|
|
970
978
|
close();
|
|
971
979
|
}
|
|
972
980
|
}
|
|
@@ -974,23 +982,34 @@ function inputChannel(options = {}) {
|
|
|
974
982
|
idle = false;
|
|
975
983
|
clearTimers();
|
|
976
984
|
}
|
|
977
|
-
function
|
|
978
|
-
|
|
979
|
-
return;
|
|
980
|
-
}
|
|
981
|
-
idle = true;
|
|
982
|
-
idleEpoch += 1;
|
|
983
|
-
drained = !options.onIdle;
|
|
985
|
+
function armTimers() {
|
|
986
|
+
clearTimers();
|
|
984
987
|
quiet = false;
|
|
985
|
-
timers = [
|
|
988
|
+
timers = background > 0 ? [setTimeout(close, options.backgroundLimitMs ?? BACKGROUND_LIMIT_MS)] : [
|
|
986
989
|
setTimeout(() => {
|
|
987
990
|
quiet = true;
|
|
988
991
|
maybeClose();
|
|
989
992
|
}, options.quietMs ?? QUIET_MS),
|
|
990
993
|
setTimeout(close, options.idleLimitMs ?? IDLE_LIMIT_MS)
|
|
991
994
|
];
|
|
995
|
+
}
|
|
996
|
+
function turnEnded() {
|
|
997
|
+
if (closed || idle) {
|
|
998
|
+
return;
|
|
999
|
+
}
|
|
1000
|
+
idle = true;
|
|
1001
|
+
idleEpoch += 1;
|
|
1002
|
+
drained = !options.onIdle;
|
|
1003
|
+
armTimers();
|
|
992
1004
|
options.onIdle?.();
|
|
993
1005
|
}
|
|
1006
|
+
function backgroundChanged(count) {
|
|
1007
|
+
const was = background;
|
|
1008
|
+
background = count;
|
|
1009
|
+
if (idle && !closed && was > 0 !== count > 0) {
|
|
1010
|
+
armTimers();
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
994
1013
|
async function* stream(own) {
|
|
995
1014
|
for (; ; ) {
|
|
996
1015
|
if (closed || own !== generation) {
|
|
@@ -1032,6 +1051,11 @@ function inputChannel(options = {}) {
|
|
|
1032
1051
|
},
|
|
1033
1052
|
// Если Claude Code сообщает состояние сессии, верим только ему: idle приходит после фоновых задач
|
|
1034
1053
|
observe(message) {
|
|
1054
|
+
const tasks = backgroundOf(message);
|
|
1055
|
+
if (tasks !== null) {
|
|
1056
|
+
backgroundChanged(tasks);
|
|
1057
|
+
return;
|
|
1058
|
+
}
|
|
1035
1059
|
if (message.type === "system" && message.subtype === "session_state_changed") {
|
|
1036
1060
|
statesSeen = true;
|
|
1037
1061
|
if (message.state === "idle") {
|
|
@@ -3918,7 +3942,7 @@ function describeDevice(identity) {
|
|
|
3918
3942
|
}
|
|
3919
3943
|
|
|
3920
3944
|
// src/sense.ts
|
|
3921
|
-
import { mkdtemp, rm as rm5 } from "fs/promises";
|
|
3945
|
+
import { mkdtemp, rm as rm5, stat as stat2 } from "fs/promises";
|
|
3922
3946
|
import { tmpdir } from "os";
|
|
3923
3947
|
import { dirname as dirname4, join as join9 } from "path";
|
|
3924
3948
|
import process5 from "process";
|
|
@@ -3971,7 +3995,8 @@ async function sleep(ms, signal) {
|
|
|
3971
3995
|
|
|
3972
3996
|
// src/sense.ts
|
|
3973
3997
|
var SERVER_NAME3 = "tamsense";
|
|
3974
|
-
var
|
|
3998
|
+
var WEB_TOOL = "WebFetch";
|
|
3999
|
+
var MAX_SENSE_TURNS = 6;
|
|
3975
4000
|
var HEARTBEAT_MS = 5e3;
|
|
3976
4001
|
var STOPPED_HERE = "stopped_here";
|
|
3977
4002
|
var CLOSED_ON_SITE = "closed_on_site";
|
|
@@ -3989,12 +4014,13 @@ function claudeSenseRunner(startQuery) {
|
|
|
3989
4014
|
options: {
|
|
3990
4015
|
cwd: request.cwd,
|
|
3991
4016
|
systemPrompt: request.system,
|
|
3992
|
-
tools: [],
|
|
4017
|
+
tools: [WEB_TOOL],
|
|
3993
4018
|
settingSources: [],
|
|
3994
4019
|
persistSession: false,
|
|
4020
|
+
...request.resume === null ? {} : { resume: request.resume, forkSession: true },
|
|
3995
4021
|
strictMcpConfig: true,
|
|
3996
4022
|
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}`),
|
|
4023
|
+
allowedTools: [WEB_TOOL, ...tools.map((item) => `mcp__${SERVER_NAME3}__${item.name}`)],
|
|
3998
4024
|
permissionMode: "auto",
|
|
3999
4025
|
canUseTool: denyWhenAsked,
|
|
4000
4026
|
maxTurns: MAX_SENSE_TURNS,
|
|
@@ -4027,8 +4053,17 @@ function senseHeartbeat(api, job, abort) {
|
|
|
4027
4053
|
}
|
|
4028
4054
|
};
|
|
4029
4055
|
}
|
|
4056
|
+
async function authorSession(job, dataFolder) {
|
|
4057
|
+
if (!job.author) {
|
|
4058
|
+
return null;
|
|
4059
|
+
}
|
|
4060
|
+
const folder = taskFolder(dataFolder, { taskId: job.taskId, stage: { columnId: job.author.columnId } });
|
|
4061
|
+
const found = await stat2(folder).catch(() => null);
|
|
4062
|
+
return found?.isDirectory() ? { folder, sessionId: job.author.sessionId } : null;
|
|
4063
|
+
}
|
|
4030
4064
|
async function runSenseJob(job, runSense, tools, stop) {
|
|
4031
|
-
const
|
|
4065
|
+
const scratch = await mkdtemp(join9(tmpdir(), "tamsense-sense-"));
|
|
4066
|
+
const author = await authorSession(job, tools.dataFolder);
|
|
4032
4067
|
const run2 = new AbortController();
|
|
4033
4068
|
const stopRun = () => run2.abort(STOPPED_HERE);
|
|
4034
4069
|
if (stop.aborted) {
|
|
@@ -4038,17 +4073,24 @@ async function runSenseJob(job, runSense, tools, stop) {
|
|
|
4038
4073
|
const deadline = setTimeout(() => run2.abort(TOO_LONG), job.deadlineMs);
|
|
4039
4074
|
const beat = senseHeartbeat(tools.api, job, () => run2.abort(CLOSED_ON_SITE));
|
|
4040
4075
|
const beating = setInterval(() => void beat(), HEARTBEAT_MS);
|
|
4041
|
-
let body;
|
|
4076
|
+
let body = FAILED;
|
|
4042
4077
|
try {
|
|
4043
4078
|
const env = withNodeOnPath(process5.env, dirname4(process5.execPath));
|
|
4044
|
-
|
|
4079
|
+
const sources = sourceTools(job.tools, tools.api, sensePath(job.senseId, "sources"));
|
|
4080
|
+
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);
|
|
4081
|
+
if (author) {
|
|
4082
|
+
body = await ask(author.folder, author.sessionId);
|
|
4083
|
+
}
|
|
4084
|
+
if (body.outcome === "failed" && !run2.signal.aborted) {
|
|
4085
|
+
body = await ask(scratch, null);
|
|
4086
|
+
}
|
|
4045
4087
|
} catch {
|
|
4046
4088
|
body = FAILED;
|
|
4047
4089
|
} finally {
|
|
4048
4090
|
clearTimeout(deadline);
|
|
4049
4091
|
clearInterval(beating);
|
|
4050
4092
|
stop.removeEventListener("abort", stopRun);
|
|
4051
|
-
await rm5(
|
|
4093
|
+
await rm5(scratch, { recursive: true, force: true });
|
|
4052
4094
|
}
|
|
4053
4095
|
if (run2.signal.reason === CLOSED_ON_SITE) {
|
|
4054
4096
|
return;
|
|
@@ -4218,7 +4260,7 @@ async function prepareLibrary(folder, skills, source = LIBRARY_PATH) {
|
|
|
4218
4260
|
// src/recordings.ts
|
|
4219
4261
|
import { randomUUID } from "crypto";
|
|
4220
4262
|
import { createReadStream } from "fs";
|
|
4221
|
-
import { readdir as readdir2, readFile as readFile4, stat as
|
|
4263
|
+
import { readdir as readdir2, readFile as readFile4, stat as stat3, writeFile as writeFile4 } from "fs/promises";
|
|
4222
4264
|
import { basename as basename4, dirname as dirname5, extname as extname2, isAbsolute as isAbsolute3, join as join11 } from "path";
|
|
4223
4265
|
import { createInterface as createInterface2 } from "readline";
|
|
4224
4266
|
import { Readable } from "stream";
|
|
@@ -4325,7 +4367,7 @@ async function remember(dir, file) {
|
|
|
4325
4367
|
await writeFile4(join11(dir, LEDGER_FILE), JSON.stringify([...done2]));
|
|
4326
4368
|
}
|
|
4327
4369
|
async function nonEmpty(file) {
|
|
4328
|
-
return file !== null && ((await
|
|
4370
|
+
return file !== null && ((await stat3(file).catch(() => null))?.size ?? 0) > 0;
|
|
4329
4371
|
}
|
|
4330
4372
|
async function pendingRecordings(dir) {
|
|
4331
4373
|
const done2 = await ledger(dir);
|
|
@@ -4360,7 +4402,7 @@ async function uploadReplay(upload, file, label) {
|
|
|
4360
4402
|
}
|
|
4361
4403
|
async function uploadVideo(upload, file, label, meta) {
|
|
4362
4404
|
const contentType = VIDEO_TYPES_BY_EXTENSION[extname2(file).toLowerCase()];
|
|
4363
|
-
const info = await
|
|
4405
|
+
const info = await stat3(file);
|
|
4364
4406
|
if (info.size > MAX_VIDEO_BYTES) {
|
|
4365
4407
|
throw new Error(`The video is larger than ${MAX_VIDEO_BYTES / 1024 / 1024} MB. Record a shorter one.`);
|
|
4366
4408
|
}
|
|
@@ -4449,7 +4491,7 @@ async function uploadPendingRecordings(api, runId, dir) {
|
|
|
4449
4491
|
}
|
|
4450
4492
|
|
|
4451
4493
|
// src/screenshot.ts
|
|
4452
|
-
import { readFile as readFile5, stat as
|
|
4494
|
+
import { readFile as readFile5, stat as stat4 } from "fs/promises";
|
|
4453
4495
|
import { basename as basename5, extname as extname3, isAbsolute as isAbsolute4 } from "path";
|
|
4454
4496
|
var TYPES_BY_EXTENSION2 = {
|
|
4455
4497
|
".png": "image/png",
|
|
@@ -4471,7 +4513,7 @@ async function readScreenshot(request) {
|
|
|
4471
4513
|
} catch (error) {
|
|
4472
4514
|
return error instanceof Error ? error.message : String(error);
|
|
4473
4515
|
}
|
|
4474
|
-
const info = await
|
|
4516
|
+
const info = await stat4(request.path).catch(() => null);
|
|
4475
4517
|
if (!info?.isFile()) {
|
|
4476
4518
|
return `There is no file at ${request.path}.`;
|
|
4477
4519
|
}
|