codeclaw 0.2.4 → 0.2.5
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/bot-telegram.js +72 -63
- package/dist/bot.js +5 -3
- package/package.json +1 -1
package/dist/bot-telegram.js
CHANGED
|
@@ -105,7 +105,72 @@ const ARTIFACT_PHOTO_EXTS = new Set(['.jpg', '.jpeg', '.png', '.webp']);
|
|
|
105
105
|
function isPhotoFilename(filename) {
|
|
106
106
|
return ARTIFACT_PHOTO_EXTS.has(path.extname(filename).toLowerCase());
|
|
107
107
|
}
|
|
108
|
-
function
|
|
108
|
+
export function collectArtifacts(dirPath, manifestPath, log) {
|
|
109
|
+
const _log = log || (() => { });
|
|
110
|
+
if (!fs.existsSync(manifestPath))
|
|
111
|
+
return [];
|
|
112
|
+
let parsed;
|
|
113
|
+
try {
|
|
114
|
+
parsed = JSON.parse(fs.readFileSync(manifestPath, 'utf-8'));
|
|
115
|
+
}
|
|
116
|
+
catch (e) {
|
|
117
|
+
_log(`artifact manifest parse error: ${e}`);
|
|
118
|
+
return [];
|
|
119
|
+
}
|
|
120
|
+
const entries = Array.isArray(parsed) ? parsed : Array.isArray(parsed?.files) ? parsed.files : [];
|
|
121
|
+
if (!entries.length)
|
|
122
|
+
return [];
|
|
123
|
+
const realDir = fs.realpathSync(dirPath);
|
|
124
|
+
const artifacts = [];
|
|
125
|
+
for (const entry of entries.slice(0, ARTIFACT_MAX_FILES)) {
|
|
126
|
+
const rawPath = typeof entry?.path === 'string' ? entry.path
|
|
127
|
+
: typeof entry?.name === 'string' ? entry.name
|
|
128
|
+
: '';
|
|
129
|
+
const relPath = rawPath.trim();
|
|
130
|
+
if (!relPath || path.isAbsolute(relPath)) {
|
|
131
|
+
_log(`artifact skipped: invalid path "${rawPath}"`);
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
const resolved = path.resolve(dirPath, relPath);
|
|
135
|
+
const relative = path.relative(dirPath, resolved);
|
|
136
|
+
if (!relative || relative.startsWith('..') || path.isAbsolute(relative)) {
|
|
137
|
+
_log(`artifact skipped: outside turn dir "${relPath}"`);
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
if (!fs.existsSync(resolved)) {
|
|
141
|
+
_log(`artifact skipped: missing file "${relPath}"`);
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
const realFile = fs.realpathSync(resolved);
|
|
145
|
+
const realRelative = path.relative(realDir, realFile);
|
|
146
|
+
if (realRelative.startsWith('..') || path.isAbsolute(realRelative)) {
|
|
147
|
+
_log(`artifact skipped: symlink outside turn dir "${relPath}"`);
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
const stat = fs.statSync(realFile);
|
|
151
|
+
if (!stat.isFile()) {
|
|
152
|
+
_log(`artifact skipped: not a file "${relPath}"`);
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
if (stat.size > ARTIFACT_MAX_BYTES) {
|
|
156
|
+
_log(`artifact skipped: too large "${relPath}" (${stat.size} bytes)`);
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
const filename = path.basename(realFile);
|
|
160
|
+
const requestedKind = typeof entry?.kind === 'string' ? entry.kind.toLowerCase()
|
|
161
|
+
: typeof entry?.type === 'string' ? entry.type.toLowerCase()
|
|
162
|
+
: '';
|
|
163
|
+
let kind = requestedKind === 'document' ? 'document'
|
|
164
|
+
: requestedKind === 'photo' ? 'photo'
|
|
165
|
+
: isPhotoFilename(filename) ? 'photo' : 'document';
|
|
166
|
+
if (kind === 'photo' && !isPhotoFilename(filename))
|
|
167
|
+
kind = 'document';
|
|
168
|
+
const caption = typeof entry?.caption === 'string' ? entry.caption.trim().slice(0, 1024) || undefined : undefined;
|
|
169
|
+
artifacts.push({ filePath: realFile, filename, kind, caption });
|
|
170
|
+
}
|
|
171
|
+
return artifacts;
|
|
172
|
+
}
|
|
173
|
+
export function buildArtifactPrompt(prompt, artifactDir, manifestPath) {
|
|
109
174
|
const base = prompt.trim() || 'Please help with this request.';
|
|
110
175
|
return [
|
|
111
176
|
base,
|
|
@@ -409,12 +474,17 @@ export class TelegramBot extends Bot {
|
|
|
409
474
|
const baseArgs = ensureNonInteractiveRestartArgs(bin, rawArgs);
|
|
410
475
|
const allArgs = [...baseArgs, ...process.argv.slice(2)];
|
|
411
476
|
this.log(`restart: spawning \`${bin} ${allArgs.join(' ')}\``);
|
|
477
|
+
// Collect all known chat IDs so the new process can send startup notices
|
|
478
|
+
const knownIds = new Set(this.allowedChatIds);
|
|
479
|
+
for (const cid of this.channel.knownChats)
|
|
480
|
+
knownIds.add(cid);
|
|
412
481
|
const child = spawn(bin, allArgs, {
|
|
413
482
|
stdio: 'inherit',
|
|
414
483
|
detached: true,
|
|
415
484
|
env: {
|
|
416
485
|
...process.env,
|
|
417
486
|
npm_config_yes: process.env.npm_config_yes || 'true',
|
|
487
|
+
...(knownIds.size ? { TELEGRAM_ALLOWED_CHAT_IDS: [...knownIds].join(',') } : {}),
|
|
418
488
|
},
|
|
419
489
|
});
|
|
420
490
|
child.unref();
|
|
@@ -494,68 +564,7 @@ export class TelegramBot extends Bot {
|
|
|
494
564
|
fs.rmSync(dirPath, { recursive: true, force: true });
|
|
495
565
|
}
|
|
496
566
|
collectArtifacts(dirPath, manifestPath) {
|
|
497
|
-
|
|
498
|
-
return [];
|
|
499
|
-
let parsed;
|
|
500
|
-
try {
|
|
501
|
-
parsed = JSON.parse(fs.readFileSync(manifestPath, 'utf-8'));
|
|
502
|
-
}
|
|
503
|
-
catch (e) {
|
|
504
|
-
this.log(`artifact manifest parse error: ${e}`);
|
|
505
|
-
return [];
|
|
506
|
-
}
|
|
507
|
-
const entries = Array.isArray(parsed) ? parsed : Array.isArray(parsed?.files) ? parsed.files : [];
|
|
508
|
-
if (!entries.length)
|
|
509
|
-
return [];
|
|
510
|
-
const realDir = fs.realpathSync(dirPath);
|
|
511
|
-
const artifacts = [];
|
|
512
|
-
for (const entry of entries.slice(0, ARTIFACT_MAX_FILES)) {
|
|
513
|
-
const rawPath = typeof entry?.path === 'string' ? entry.path
|
|
514
|
-
: typeof entry?.name === 'string' ? entry.name
|
|
515
|
-
: '';
|
|
516
|
-
const relPath = rawPath.trim();
|
|
517
|
-
if (!relPath || path.isAbsolute(relPath)) {
|
|
518
|
-
this.log(`artifact skipped: invalid path "${rawPath}"`);
|
|
519
|
-
continue;
|
|
520
|
-
}
|
|
521
|
-
const resolved = path.resolve(dirPath, relPath);
|
|
522
|
-
const relative = path.relative(dirPath, resolved);
|
|
523
|
-
if (!relative || relative.startsWith('..') || path.isAbsolute(relative)) {
|
|
524
|
-
this.log(`artifact skipped: outside turn dir "${relPath}"`);
|
|
525
|
-
continue;
|
|
526
|
-
}
|
|
527
|
-
if (!fs.existsSync(resolved)) {
|
|
528
|
-
this.log(`artifact skipped: missing file "${relPath}"`);
|
|
529
|
-
continue;
|
|
530
|
-
}
|
|
531
|
-
const realFile = fs.realpathSync(resolved);
|
|
532
|
-
const realRelative = path.relative(realDir, realFile);
|
|
533
|
-
if (realRelative.startsWith('..') || path.isAbsolute(realRelative)) {
|
|
534
|
-
this.log(`artifact skipped: symlink outside turn dir "${relPath}"`);
|
|
535
|
-
continue;
|
|
536
|
-
}
|
|
537
|
-
const stat = fs.statSync(realFile);
|
|
538
|
-
if (!stat.isFile()) {
|
|
539
|
-
this.log(`artifact skipped: not a file "${relPath}"`);
|
|
540
|
-
continue;
|
|
541
|
-
}
|
|
542
|
-
if (stat.size > ARTIFACT_MAX_BYTES) {
|
|
543
|
-
this.log(`artifact skipped: too large "${relPath}" (${stat.size} bytes)`);
|
|
544
|
-
continue;
|
|
545
|
-
}
|
|
546
|
-
const filename = path.basename(realFile);
|
|
547
|
-
const requestedKind = typeof entry?.kind === 'string' ? entry.kind.toLowerCase()
|
|
548
|
-
: typeof entry?.type === 'string' ? entry.type.toLowerCase()
|
|
549
|
-
: '';
|
|
550
|
-
let kind = requestedKind === 'document' ? 'document'
|
|
551
|
-
: requestedKind === 'photo' ? 'photo'
|
|
552
|
-
: isPhotoFilename(filename) ? 'photo' : 'document';
|
|
553
|
-
if (kind === 'photo' && !isPhotoFilename(filename))
|
|
554
|
-
kind = 'document';
|
|
555
|
-
const caption = typeof entry?.caption === 'string' ? entry.caption.trim().slice(0, 1024) || undefined : undefined;
|
|
556
|
-
artifacts.push({ filePath: realFile, filename, kind, caption });
|
|
557
|
-
}
|
|
558
|
-
return artifacts;
|
|
567
|
+
return collectArtifacts(dirPath, manifestPath, msg => this.log(msg));
|
|
559
568
|
}
|
|
560
569
|
async sendArtifacts(ctx, replyTo, artifacts) {
|
|
561
570
|
for (const artifact of artifacts) {
|
package/dist/bot.js
CHANGED
|
@@ -8,7 +8,7 @@ import fs from 'node:fs';
|
|
|
8
8
|
import path from 'node:path';
|
|
9
9
|
import { execSync, spawn } from 'node:child_process';
|
|
10
10
|
import { doStream, getSessions, getUsage, listAgents, } from './code-agent.js';
|
|
11
|
-
export const VERSION = '0.2.
|
|
11
|
+
export const VERSION = '0.2.5';
|
|
12
12
|
// ---------------------------------------------------------------------------
|
|
13
13
|
// Helpers
|
|
14
14
|
// ---------------------------------------------------------------------------
|
|
@@ -235,9 +235,10 @@ export class Bot {
|
|
|
235
235
|
else if (cs.agent === 'codex') {
|
|
236
236
|
this.log(`[runStream] codex config: model=${this.codexModel} reasoning=${this.codexReasoningEffort} fullAccess=${this.codexFullAccess} extraArgs=[${this.codexExtraArgs.join(' ')}]`);
|
|
237
237
|
}
|
|
238
|
+
const snapshotSessionId = cs.sessionId;
|
|
238
239
|
const opts = {
|
|
239
240
|
agent: cs.agent, prompt, workdir: this.workdir, timeout: this.runTimeout,
|
|
240
|
-
sessionId:
|
|
241
|
+
sessionId: snapshotSessionId, model: null, thinkingEffort: this.codexReasoningEffort, onText,
|
|
241
242
|
attachments: attachments.length ? attachments : undefined,
|
|
242
243
|
codexModel: this.codexModel, codexFullAccess: this.codexFullAccess,
|
|
243
244
|
codexExtraArgs: this.codexExtraArgs.length ? this.codexExtraArgs : undefined,
|
|
@@ -252,7 +253,8 @@ export class Bot {
|
|
|
252
253
|
this.stats.totalOutputTokens += result.outputTokens;
|
|
253
254
|
if (result.cachedInputTokens)
|
|
254
255
|
this.stats.totalCachedTokens += result.cachedInputTokens;
|
|
255
|
-
if (
|
|
256
|
+
// Only update sessionId if it hasn't been changed externally (e.g. user switched session during run)
|
|
257
|
+
if (result.sessionId && cs.sessionId === snapshotSessionId)
|
|
256
258
|
cs.sessionId = result.sessionId;
|
|
257
259
|
this.log(`[runStream] completed turn=${this.stats.totalTurns} cumulative: in=${fmtTokens(this.stats.totalInputTokens)} out=${fmtTokens(this.stats.totalOutputTokens)} cached=${fmtTokens(this.stats.totalCachedTokens)}`);
|
|
258
260
|
return result;
|