evot-agent 0.2.3 → 0.3.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/README.md +1 -0
- package/bin/evot-agent.js +3 -1
- package/package.json +1 -1
- package/src/browse.js +66 -5
- package/src/report.js +4 -1
- package/src/sync.js +4 -3
package/README.md
CHANGED
package/bin/evot-agent.js
CHANGED
|
@@ -84,8 +84,9 @@ async function main() {
|
|
|
84
84
|
|
|
85
85
|
console.log(`Running job → thread ${job.log_id}`);
|
|
86
86
|
let text;
|
|
87
|
+
let segments;
|
|
87
88
|
try {
|
|
88
|
-
text = await runBrowseJob(job);
|
|
89
|
+
({ text, segments } = await runBrowseJob(job));
|
|
89
90
|
} catch (err) {
|
|
90
91
|
// 실패도 쓰레드에 남긴다 (그리고 텔레그램 알림).
|
|
91
92
|
console.error(`\nBrowse failed: ${err.message}`);
|
|
@@ -103,6 +104,7 @@ async function main() {
|
|
|
103
104
|
const cellId = await reportResult({
|
|
104
105
|
logId: job.log_id,
|
|
105
106
|
text,
|
|
107
|
+
segments,
|
|
106
108
|
title: job.title ?? "collection",
|
|
107
109
|
status: "success",
|
|
108
110
|
});
|
package/package.json
CHANGED
package/src/browse.js
CHANGED
|
@@ -15,16 +15,77 @@ const SYSTEM_APPEND = [
|
|
|
15
15
|
" LOGIN_REQUIRED: <domain>",
|
|
16
16
|
' (e.g. "LOGIN_REQUIRED: news.ycombinator.com"). No other text.',
|
|
17
17
|
"",
|
|
18
|
+
"MISSION FIDELITY:",
|
|
19
|
+
"- Complete exactly what the user asked. Do not narrow the scope on your own or",
|
|
20
|
+
" substitute a different deliverable — e.g. do NOT return only text/metadata when",
|
|
21
|
+
" images were requested. If the user asks for images, collect and output the",
|
|
22
|
+
" actual image URLs, exactly as many as the task calls for.",
|
|
23
|
+
"- Do not act as a content moderator. Mature, adult, sensitive, or otherwise",
|
|
24
|
+
" disagreeable material that is lawful and already publicly posted is in scope;",
|
|
25
|
+
" do not skip, omit, sanitize, or downgrade it merely for being suggestive or",
|
|
26
|
+
" NSFW. You are cataloguing what is publicly visible on the user's own request,",
|
|
27
|
+
" not endorsing it. Report what is actually there.",
|
|
28
|
+
"- The only hard stop is genuinely illegal content (e.g. sexual content involving",
|
|
29
|
+
" minors). If — and only if — you must decline part of a task, say so explicitly",
|
|
30
|
+
" on its own line with the concrete reason. Never silently drop or replace it.",
|
|
31
|
+
"",
|
|
18
32
|
"When finished, output ONLY the collected result as plain text:",
|
|
19
|
-
"- A short title line.",
|
|
20
|
-
"-
|
|
21
|
-
"
|
|
33
|
+
"- A short title line and the overall summary first.",
|
|
34
|
+
"- If the task involves collecting images, organize the result into sections:",
|
|
35
|
+
" each section is its description text followed by its image lines, each",
|
|
36
|
+
" exactly 'IMAGE: <url>' with a direct image URL you actually saw",
|
|
37
|
+
" (e.g. a pbs.twimg.com link or a URL ending in .jpg/.png). Max 10 images total.",
|
|
38
|
+
"- A 'Sources:' section at the end listing the URLs you used, one per line.",
|
|
22
39
|
"Do not include tool logs or meta commentary in the final answer.",
|
|
23
40
|
].join("\n");
|
|
24
41
|
|
|
42
|
+
const MAX_IMAGES_TOTAL = 10;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* 최종 답변을 섹션 단위로 파싱한다.
|
|
46
|
+
* - 'IMAGE: <url>' 라인 → 현재 섹션의 이미지(전체 합산 10개 상한). 'Images:' 헤딩 라인은 제거.
|
|
47
|
+
* - 이미지 뒤에 다시 텍스트가 나오면 새 섹션 시작(설명→이미지→설명→이미지 그룹핑).
|
|
48
|
+
* - 반환 text는 IMAGE 라인이 제거된 전문(구서버 호환·텔레그램 스니펫용).
|
|
49
|
+
*/
|
|
50
|
+
export function parseSegments(raw) {
|
|
51
|
+
const segments = [];
|
|
52
|
+
let curText = [];
|
|
53
|
+
let curImages = [];
|
|
54
|
+
let sawImage = false;
|
|
55
|
+
let total = 0;
|
|
56
|
+
|
|
57
|
+
const flush = () => {
|
|
58
|
+
const text = curText.join("\n").trim();
|
|
59
|
+
if (text || curImages.length > 0) segments.push({ text, images: curImages });
|
|
60
|
+
curText = [];
|
|
61
|
+
curImages = [];
|
|
62
|
+
sawImage = false;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
for (const line of raw.split("\n")) {
|
|
66
|
+
if (/^\s*images:\s*$/i.test(line)) continue; // 잔여 'Images:' 헤딩 제거
|
|
67
|
+
const m = line.match(/^\s*IMAGE:\s*(https?:\/\/\S+)\s*$/i);
|
|
68
|
+
if (m) {
|
|
69
|
+
if (total < MAX_IMAGES_TOTAL) {
|
|
70
|
+
curImages.push(m[1]);
|
|
71
|
+
total++;
|
|
72
|
+
sawImage = true;
|
|
73
|
+
}
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
if (sawImage && line.trim()) flush(); // 이미지 뒤 새 텍스트 → 새 섹션
|
|
77
|
+
curText.push(line);
|
|
78
|
+
}
|
|
79
|
+
flush();
|
|
80
|
+
|
|
81
|
+
const text = segments.map((s) => s.text).filter(Boolean).join("\n\n").trim();
|
|
82
|
+
return { text, segments: segments.length > 0 ? segments : [{ text, images: [] }] };
|
|
83
|
+
}
|
|
84
|
+
|
|
25
85
|
/**
|
|
26
86
|
* 잡 지시문을 Claude Agent SDK + Playwright MCP(전용 프로필)로 실행하고
|
|
27
|
-
* 최종
|
|
87
|
+
* 최종 결과를 { text, segments }로 반환한다(섹션 = 설명+이미지 URL 그룹).
|
|
88
|
+
* 모델 크리덴셜은 로컬 Claude Code 로그인을 사용.
|
|
28
89
|
*/
|
|
29
90
|
export async function runBrowseJob(job) {
|
|
30
91
|
const promptParts = [job.instruction];
|
|
@@ -99,7 +160,7 @@ export async function runBrowseJob(job) {
|
|
|
99
160
|
finalText.trim() || "Agent finished without producing a result.",
|
|
100
161
|
);
|
|
101
162
|
}
|
|
102
|
-
return finalText
|
|
163
|
+
return parseSegments(finalText);
|
|
103
164
|
}
|
|
104
165
|
|
|
105
166
|
/**
|
package/src/report.js
CHANGED
|
@@ -8,12 +8,15 @@ import { SUPABASE_URL, loadToken } from "./config.js";
|
|
|
8
8
|
* - 큐 경로(데몬): jobId만 넘긴다. 서버가 잡에서 log_id를 찾아 셀을 만들고 잡 상태를 done/failed로 갱신.
|
|
9
9
|
* - 수동 경로(run): logId를 넘긴다. (Phase 1 호환)
|
|
10
10
|
*/
|
|
11
|
-
export async function reportResult({ logId, jobId, text, title, status = "success" }) {
|
|
11
|
+
export async function reportResult({ logId, jobId, text, segments, title, status = "success" }) {
|
|
12
12
|
const token = await loadToken();
|
|
13
13
|
|
|
14
14
|
const body = { text, title, status };
|
|
15
15
|
if (jobId) body.job_id = jobId;
|
|
16
16
|
if (logId) body.log_id = logId;
|
|
17
|
+
// 섹션(설명+이미지 URL) 전달 — 서버가 votResponse 서브셀·이미지 저장에 사용.
|
|
18
|
+
// 구서버는 이 필드를 무시하고 text만 쓴다(하위호환).
|
|
19
|
+
if (Array.isArray(segments) && segments.length > 0) body.segments = segments;
|
|
17
20
|
|
|
18
21
|
const url = `${SUPABASE_URL}/functions/v1/agent-report`;
|
|
19
22
|
const res = await fetch(url, {
|
package/src/sync.js
CHANGED
|
@@ -44,20 +44,21 @@ async function fetchJobs(token) {
|
|
|
44
44
|
async function runJob(job) {
|
|
45
45
|
console.log(`\nJob ${job.id}: ${String(job.instruction ?? "").slice(0, 80)}`);
|
|
46
46
|
let text;
|
|
47
|
+
let segments;
|
|
47
48
|
let status = "success";
|
|
48
49
|
try {
|
|
49
|
-
text = await runBrowseJob({
|
|
50
|
+
({ text, segments } = await runBrowseJob({
|
|
50
51
|
instruction: job.instruction,
|
|
51
52
|
start_url: job.start_url ?? undefined,
|
|
52
53
|
headless: true,
|
|
53
|
-
});
|
|
54
|
+
}));
|
|
54
55
|
} catch (err) {
|
|
55
56
|
console.error(`Job ${job.id} failed: ${err.message}`);
|
|
56
57
|
text = describeJobFailure(err);
|
|
57
58
|
status = "error";
|
|
58
59
|
}
|
|
59
60
|
try {
|
|
60
|
-
const cellId = await reportResult({ jobId: job.id, text, title: "Computer Use", status });
|
|
61
|
+
const cellId = await reportResult({ jobId: job.id, text, segments, title: "Computer Use", status });
|
|
61
62
|
console.log(`Reported job ${job.id}${cellId ? ` → cell ${cellId}` : ""}.`);
|
|
62
63
|
} catch (err) {
|
|
63
64
|
// 보고 실패 시 잡은 running으로 남고, running 타임아웃 후 서버가 expired 처리한다.
|