@poncho-ai/cli 0.33.3 → 0.34.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/.turbo/turbo-build.log +5 -5
- package/CHANGELOG.md +20 -0
- package/dist/{chunk-METMUDY6.js → chunk-C5XPPHBB.js} +64 -17
- package/dist/cli.js +1 -1
- package/dist/index.js +1 -1
- package/dist/{run-interactive-ink-XQM7OIZT.js → run-interactive-ink-DYCMDTTJ.js} +1 -1
- package/package.json +4 -4
- package/src/index.ts +76 -21
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
> @poncho-ai/cli@0.
|
|
2
|
+
> @poncho-ai/cli@0.34.0 build /home/runner/work/poncho-ai/poncho-ai/packages/cli
|
|
3
3
|
> tsup src/index.ts src/cli.ts --format esm --dts
|
|
4
4
|
|
|
5
5
|
[34mCLI[39m Building entry: src/cli.ts, src/index.ts
|
|
@@ -7,12 +7,12 @@
|
|
|
7
7
|
[34mCLI[39m tsup v8.5.1
|
|
8
8
|
[34mCLI[39m Target: es2022
|
|
9
9
|
[34mESM[39m Build start
|
|
10
|
+
[32mESM[39m [1mdist/run-interactive-ink-DYCMDTTJ.js [22m[32m56.86 KB[39m
|
|
10
11
|
[32mESM[39m [1mdist/index.js [22m[32m917.00 B[39m
|
|
11
12
|
[32mESM[39m [1mdist/cli.js [22m[32m94.00 B[39m
|
|
12
|
-
[32mESM[39m [1mdist/
|
|
13
|
-
[32mESM[39m
|
|
14
|
-
[32mESM[39m ⚡️ Build success in 65ms
|
|
13
|
+
[32mESM[39m [1mdist/chunk-C5XPPHBB.js [22m[32m530.96 KB[39m
|
|
14
|
+
[32mESM[39m ⚡️ Build success in 72ms
|
|
15
15
|
[34mDTS[39m Build start
|
|
16
|
-
[32mDTS[39m ⚡️ Build success in
|
|
16
|
+
[32mDTS[39m ⚡️ Build success in 4522ms
|
|
17
17
|
[32mDTS[39m [1mdist/cli.d.ts [22m[32m20.00 B[39m
|
|
18
18
|
[32mDTS[39m [1mdist/index.d.ts [22m[32m7.07 KB[39m
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# @poncho-ai/cli
|
|
2
2
|
|
|
3
|
+
## 0.34.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#75](https://github.com/cesr/poncho-ai/pull/75) [`d447d0a`](https://github.com/cesr/poncho-ai/commit/d447d0a3cb77f3d097276b524b5f870dddf1899e) Thanks [@cesr](https://github.com/cesr)! - Add `maxRuns` option to cron jobs for automatic pruning of old conversations, preventing unbounded storage growth on hosted stores.
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- Updated dependencies [[`d447d0a`](https://github.com/cesr/poncho-ai/commit/d447d0a3cb77f3d097276b524b5f870dddf1899e)]:
|
|
12
|
+
- @poncho-ai/harness@0.33.0
|
|
13
|
+
|
|
14
|
+
## 0.33.4
|
|
15
|
+
|
|
16
|
+
### Patch Changes
|
|
17
|
+
|
|
18
|
+
- [#73](https://github.com/cesr/poncho-ai/pull/73) [`f72f202`](https://github.com/cesr/poncho-ai/commit/f72f202d839dbbb8240336ec76eb6340aba20f06) Thanks [@cesr](https://github.com/cesr)! - Fix Telegram approval message ordering: send accumulated assistant text before approval buttons so the conversation reads naturally. Skip empty bridge replies when text was already sent at checkpoint.
|
|
19
|
+
|
|
20
|
+
- Updated dependencies [[`f72f202`](https://github.com/cesr/poncho-ai/commit/f72f202d839dbbb8240336ec76eb6340aba20f06)]:
|
|
21
|
+
- @poncho-ai/messaging@0.7.10
|
|
22
|
+
|
|
3
23
|
## 0.33.3
|
|
4
24
|
|
|
5
25
|
### Patch Changes
|
|
@@ -7902,6 +7902,22 @@ var appendCronTurn = (conv, task, result) => {
|
|
|
7902
7902
|
...result.hasContent ? [{ role: "assistant", content: result.response, metadata: result.assistantMetadata }] : []
|
|
7903
7903
|
);
|
|
7904
7904
|
};
|
|
7905
|
+
var MAX_PRUNE_PER_RUN = 25;
|
|
7906
|
+
var pruneCronConversations = async (store, ownerId, jobName, maxRuns) => {
|
|
7907
|
+
const summaries = await store.listSummaries(ownerId);
|
|
7908
|
+
const cronPrefix = `[cron] ${jobName} `;
|
|
7909
|
+
const cronSummaries = summaries.filter((s) => s.title?.startsWith(cronPrefix)).sort((a, b) => (b.updatedAt ?? 0) - (a.updatedAt ?? 0));
|
|
7910
|
+
if (cronSummaries.length <= maxRuns) return 0;
|
|
7911
|
+
const toDelete = cronSummaries.slice(maxRuns, maxRuns + MAX_PRUNE_PER_RUN);
|
|
7912
|
+
let deleted = 0;
|
|
7913
|
+
for (const s of toDelete) {
|
|
7914
|
+
try {
|
|
7915
|
+
if (await store.delete(s.conversationId)) deleted++;
|
|
7916
|
+
} catch {
|
|
7917
|
+
}
|
|
7918
|
+
}
|
|
7919
|
+
return deleted;
|
|
7920
|
+
};
|
|
7905
7921
|
var AGENT_TEMPLATE = (name, id, options) => `---
|
|
7906
7922
|
name: ${name}
|
|
7907
7923
|
id: ${id}
|
|
@@ -7936,31 +7952,30 @@ var resolveLocalPackagesRoot = () => {
|
|
|
7936
7952
|
}
|
|
7937
7953
|
return null;
|
|
7938
7954
|
};
|
|
7939
|
-
var
|
|
7955
|
+
var resolveCliDep = async (projectDir) => {
|
|
7940
7956
|
const packagesRoot = resolveLocalPackagesRoot();
|
|
7941
7957
|
if (packagesRoot) {
|
|
7942
|
-
const
|
|
7943
|
-
|
|
7944
|
-
return {
|
|
7945
|
-
harness: `link:${relative(projectDir, harnessAbs)}`,
|
|
7946
|
-
sdk: `link:${relative(projectDir, sdkAbs)}`
|
|
7947
|
-
};
|
|
7958
|
+
const cliAbs = resolve3(packagesRoot, "cli");
|
|
7959
|
+
return `link:${relative(projectDir, cliAbs)}`;
|
|
7948
7960
|
}
|
|
7949
|
-
|
|
7950
|
-
|
|
7951
|
-
sdk: await readCliDependencyVersion("@poncho-ai/sdk", "^0.6.0")
|
|
7952
|
-
};
|
|
7961
|
+
const version = await readCliVersion();
|
|
7962
|
+
return `^${version}`;
|
|
7953
7963
|
};
|
|
7954
7964
|
var PACKAGE_TEMPLATE = async (name, projectDir) => {
|
|
7955
|
-
const
|
|
7965
|
+
const cliDep = await resolveCliDep(projectDir);
|
|
7956
7966
|
return JSON.stringify(
|
|
7957
7967
|
{
|
|
7958
7968
|
name,
|
|
7959
7969
|
private: true,
|
|
7960
7970
|
type: "module",
|
|
7971
|
+
scripts: {
|
|
7972
|
+
dev: "poncho dev",
|
|
7973
|
+
start: "poncho dev",
|
|
7974
|
+
test: "poncho test",
|
|
7975
|
+
build: "poncho build"
|
|
7976
|
+
},
|
|
7961
7977
|
dependencies: {
|
|
7962
|
-
"@poncho-ai/
|
|
7963
|
-
"@poncho-ai/sdk": deps.sdk
|
|
7978
|
+
"@poncho-ai/cli": cliDep
|
|
7964
7979
|
}
|
|
7965
7980
|
},
|
|
7966
7981
|
null,
|
|
@@ -10178,6 +10193,7 @@ ${resultBody}`,
|
|
|
10178
10193
|
let latestRunId = "";
|
|
10179
10194
|
const draft = createTurnDraftState();
|
|
10180
10195
|
let checkpointedRun = false;
|
|
10196
|
+
let checkpointTextAlreadySent = false;
|
|
10181
10197
|
let runContextTokens = 0;
|
|
10182
10198
|
let runContextWindow = 0;
|
|
10183
10199
|
let runContinuation2 = false;
|
|
@@ -10268,6 +10284,19 @@ ${resultBody}`,
|
|
|
10268
10284
|
if (conv?.channelMeta?.platform === "telegram") {
|
|
10269
10285
|
const tgAdapter = messagingAdapters.get("telegram");
|
|
10270
10286
|
if (tgAdapter) {
|
|
10287
|
+
const threadRef = {
|
|
10288
|
+
channelId: conv.channelMeta.channelId,
|
|
10289
|
+
platformThreadId: conv.channelMeta.platformThreadId
|
|
10290
|
+
};
|
|
10291
|
+
const pendingText = draft.assistantResponse.trim();
|
|
10292
|
+
if (pendingText) {
|
|
10293
|
+
try {
|
|
10294
|
+
await tgAdapter.sendReply(threadRef, pendingText);
|
|
10295
|
+
checkpointTextAlreadySent = true;
|
|
10296
|
+
} catch (err) {
|
|
10297
|
+
console.error("[messaging-runner] failed to send pre-approval text:", err instanceof Error ? err.message : err);
|
|
10298
|
+
}
|
|
10299
|
+
}
|
|
10271
10300
|
const approvals = event.approvals.map((a) => ({
|
|
10272
10301
|
approvalId: a.approvalId,
|
|
10273
10302
|
tool: a.tool,
|
|
@@ -10351,8 +10380,8 @@ ${resultBody}`,
|
|
|
10351
10380
|
runOwners.delete(latestRunId);
|
|
10352
10381
|
runConversations.delete(latestRunId);
|
|
10353
10382
|
}
|
|
10354
|
-
|
|
10355
|
-
|
|
10383
|
+
const response = checkpointTextAlreadySent ? "" : draft.assistantResponse;
|
|
10384
|
+
console.log("[messaging-runner] run complete, response length:", response.length, checkpointTextAlreadySent ? "(text sent at checkpoint)" : "", runContinuation2 ? "(continuation)" : "");
|
|
10356
10385
|
return {
|
|
10357
10386
|
response,
|
|
10358
10387
|
continuation: runContinuation2,
|
|
@@ -12565,6 +12594,18 @@ ${cronJob.task}`;
|
|
|
12565
12594
|
});
|
|
12566
12595
|
await conversationStore.update(freshConv);
|
|
12567
12596
|
}
|
|
12597
|
+
const pruneWork = pruneCronConversations(
|
|
12598
|
+
conversationStore,
|
|
12599
|
+
cronOwnerId,
|
|
12600
|
+
jobName,
|
|
12601
|
+
cronJob.maxRuns ?? 5
|
|
12602
|
+
).then((n) => {
|
|
12603
|
+
if (n > 0) process.stdout.write(`[cron] ${jobName}: pruned ${n} old conversation${n === 1 ? "" : "s"}
|
|
12604
|
+
`);
|
|
12605
|
+
}).catch(
|
|
12606
|
+
(err) => console.error(`[cron] ${jobName}: prune failed:`, err instanceof Error ? err.message : err)
|
|
12607
|
+
);
|
|
12608
|
+
doWaitUntil(pruneWork);
|
|
12568
12609
|
if (result.continuation) {
|
|
12569
12610
|
const work = selfFetchWithRetry(`/api/internal/continue/${encodeURIComponent(convId)}`).catch(
|
|
12570
12611
|
(err) => console.error(`[poncho][cron] Continuation self-fetch failed:`, err instanceof Error ? err.message : err)
|
|
@@ -12924,6 +12965,12 @@ ${config.task}`;
|
|
|
12924
12965
|
});
|
|
12925
12966
|
await store.update(freshConv);
|
|
12926
12967
|
}
|
|
12968
|
+
pruneCronConversations(store, "local-owner", jobName, config.maxRuns ?? 5).then((n) => {
|
|
12969
|
+
if (n > 0) process.stdout.write(`[cron] ${jobName}: pruned ${n} old conversation${n === 1 ? "" : "s"}
|
|
12970
|
+
`);
|
|
12971
|
+
}).catch(
|
|
12972
|
+
(err) => console.error(`[cron] ${jobName}: prune failed:`, err instanceof Error ? err.message : err)
|
|
12973
|
+
);
|
|
12927
12974
|
const elapsed = ((Date.now() - start) / 1e3).toFixed(1);
|
|
12928
12975
|
process.stdout.write(
|
|
12929
12976
|
`[cron] ${jobName} completed in ${elapsed}s (${result.steps} steps)
|
|
@@ -13075,7 +13122,7 @@ var runInteractive = async (workingDir, params) => {
|
|
|
13075
13122
|
await harness.initialize();
|
|
13076
13123
|
const identity = await ensureAgentIdentity2(workingDir);
|
|
13077
13124
|
try {
|
|
13078
|
-
const { runInteractiveInk } = await import("./run-interactive-ink-
|
|
13125
|
+
const { runInteractiveInk } = await import("./run-interactive-ink-DYCMDTTJ.js");
|
|
13079
13126
|
await runInteractiveInk({
|
|
13080
13127
|
harness,
|
|
13081
13128
|
params,
|
package/dist/cli.js
CHANGED
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@poncho-ai/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.34.0",
|
|
4
4
|
"description": "CLI for building and deploying AI agents",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -27,9 +27,9 @@
|
|
|
27
27
|
"react": "^19.2.4",
|
|
28
28
|
"react-devtools-core": "^6.1.5",
|
|
29
29
|
"yaml": "^2.8.1",
|
|
30
|
-
"@poncho-ai/harness": "0.
|
|
31
|
-
"@poncho-ai/
|
|
32
|
-
"@poncho-ai/
|
|
30
|
+
"@poncho-ai/harness": "0.33.0",
|
|
31
|
+
"@poncho-ai/sdk": "1.7.1",
|
|
32
|
+
"@poncho-ai/messaging": "0.7.10"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
35
|
"@types/busboy": "^1.5.4",
|
package/src/index.ts
CHANGED
|
@@ -779,6 +779,33 @@ const appendCronTurn = (conv: Conversation, task: string, result: CronRunResult)
|
|
|
779
779
|
);
|
|
780
780
|
};
|
|
781
781
|
|
|
782
|
+
const MAX_PRUNE_PER_RUN = 25;
|
|
783
|
+
|
|
784
|
+
/** Delete old cron conversations beyond `maxRuns`, capped to avoid API storms on catch-up. */
|
|
785
|
+
const pruneCronConversations = async (
|
|
786
|
+
store: ConversationStore,
|
|
787
|
+
ownerId: string,
|
|
788
|
+
jobName: string,
|
|
789
|
+
maxRuns: number,
|
|
790
|
+
): Promise<number> => {
|
|
791
|
+
const summaries = await store.listSummaries(ownerId);
|
|
792
|
+
const cronPrefix = `[cron] ${jobName} `;
|
|
793
|
+
const cronSummaries = summaries
|
|
794
|
+
.filter((s) => s.title?.startsWith(cronPrefix))
|
|
795
|
+
.sort((a, b) => (b.updatedAt ?? 0) - (a.updatedAt ?? 0));
|
|
796
|
+
|
|
797
|
+
if (cronSummaries.length <= maxRuns) return 0;
|
|
798
|
+
|
|
799
|
+
const toDelete = cronSummaries.slice(maxRuns, maxRuns + MAX_PRUNE_PER_RUN);
|
|
800
|
+
let deleted = 0;
|
|
801
|
+
for (const s of toDelete) {
|
|
802
|
+
try {
|
|
803
|
+
if (await store.delete(s.conversationId)) deleted++;
|
|
804
|
+
} catch { /* best-effort per entry */ }
|
|
805
|
+
}
|
|
806
|
+
return deleted;
|
|
807
|
+
};
|
|
808
|
+
|
|
782
809
|
const AGENT_TEMPLATE = (
|
|
783
810
|
name: string,
|
|
784
811
|
id: string,
|
|
@@ -826,38 +853,35 @@ const resolveLocalPackagesRoot = (): string | null => {
|
|
|
826
853
|
};
|
|
827
854
|
|
|
828
855
|
/**
|
|
829
|
-
*
|
|
830
|
-
* In dev mode we use `
|
|
856
|
+
* Resolve the @poncho-ai/cli dependency specifier for the scaffolded project.
|
|
857
|
+
* In dev mode we use `link:` so pnpm can resolve the local package;
|
|
831
858
|
* in production we point at the npm registry.
|
|
832
859
|
*/
|
|
833
|
-
const
|
|
834
|
-
projectDir: string,
|
|
835
|
-
): Promise<{ harness: string; sdk: string }> => {
|
|
860
|
+
const resolveCliDep = async (projectDir: string): Promise<string> => {
|
|
836
861
|
const packagesRoot = resolveLocalPackagesRoot();
|
|
837
862
|
if (packagesRoot) {
|
|
838
|
-
const
|
|
839
|
-
|
|
840
|
-
return {
|
|
841
|
-
harness: `link:${relative(projectDir, harnessAbs)}`,
|
|
842
|
-
sdk: `link:${relative(projectDir, sdkAbs)}`,
|
|
843
|
-
};
|
|
863
|
+
const cliAbs = resolve(packagesRoot, "cli");
|
|
864
|
+
return `link:${relative(projectDir, cliAbs)}`;
|
|
844
865
|
}
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
sdk: await readCliDependencyVersion("@poncho-ai/sdk", "^0.6.0"),
|
|
848
|
-
};
|
|
866
|
+
const version = await readCliVersion();
|
|
867
|
+
return `^${version}`;
|
|
849
868
|
};
|
|
850
869
|
|
|
851
870
|
const PACKAGE_TEMPLATE = async (name: string, projectDir: string): Promise<string> => {
|
|
852
|
-
const
|
|
871
|
+
const cliDep = await resolveCliDep(projectDir);
|
|
853
872
|
return JSON.stringify(
|
|
854
873
|
{
|
|
855
874
|
name,
|
|
856
875
|
private: true,
|
|
857
876
|
type: "module",
|
|
877
|
+
scripts: {
|
|
878
|
+
dev: "poncho dev",
|
|
879
|
+
start: "poncho dev",
|
|
880
|
+
test: "poncho test",
|
|
881
|
+
build: "poncho build",
|
|
882
|
+
},
|
|
858
883
|
dependencies: {
|
|
859
|
-
"@poncho-ai/
|
|
860
|
-
"@poncho-ai/sdk": deps.sdk,
|
|
884
|
+
"@poncho-ai/cli": cliDep,
|
|
861
885
|
},
|
|
862
886
|
},
|
|
863
887
|
null,
|
|
@@ -3385,6 +3409,7 @@ export const createRequestHandler = async (options?: {
|
|
|
3385
3409
|
let latestRunId = "";
|
|
3386
3410
|
const draft = createTurnDraftState();
|
|
3387
3411
|
let checkpointedRun = false;
|
|
3412
|
+
let checkpointTextAlreadySent = false;
|
|
3388
3413
|
let runContextTokens = 0;
|
|
3389
3414
|
let runContextWindow = 0;
|
|
3390
3415
|
let runContinuation = false;
|
|
@@ -3479,11 +3504,27 @@ export const createRequestHandler = async (options?: {
|
|
|
3479
3504
|
});
|
|
3480
3505
|
checkpointedRun = true;
|
|
3481
3506
|
|
|
3482
|
-
// Send inline keyboard approval buttons to Telegram
|
|
3483
3507
|
const conv = await conversationStore.get(conversationId);
|
|
3484
3508
|
if (conv?.channelMeta?.platform === "telegram") {
|
|
3485
3509
|
const tgAdapter = messagingAdapters.get("telegram") as TelegramAdapter | undefined;
|
|
3486
3510
|
if (tgAdapter) {
|
|
3511
|
+
const threadRef: import("@poncho-ai/messaging").ThreadRef = {
|
|
3512
|
+
channelId: conv.channelMeta.channelId,
|
|
3513
|
+
platformThreadId: conv.channelMeta.platformThreadId,
|
|
3514
|
+
};
|
|
3515
|
+
|
|
3516
|
+
// Send accumulated text BEFORE approval buttons so Telegram
|
|
3517
|
+
// shows them in the natural order (text → approval request).
|
|
3518
|
+
const pendingText = draft.assistantResponse.trim();
|
|
3519
|
+
if (pendingText) {
|
|
3520
|
+
try {
|
|
3521
|
+
await tgAdapter.sendReply(threadRef, pendingText);
|
|
3522
|
+
checkpointTextAlreadySent = true;
|
|
3523
|
+
} catch (err: unknown) {
|
|
3524
|
+
console.error("[messaging-runner] failed to send pre-approval text:", err instanceof Error ? err.message : err);
|
|
3525
|
+
}
|
|
3526
|
+
}
|
|
3527
|
+
|
|
3487
3528
|
const approvals = event.approvals.map(a => ({
|
|
3488
3529
|
approvalId: a.approvalId,
|
|
3489
3530
|
tool: a.tool,
|
|
@@ -3571,8 +3612,8 @@ export const createRequestHandler = async (options?: {
|
|
|
3571
3612
|
runConversations.delete(latestRunId);
|
|
3572
3613
|
}
|
|
3573
3614
|
|
|
3574
|
-
|
|
3575
|
-
|
|
3615
|
+
const response = checkpointTextAlreadySent ? "" : draft.assistantResponse;
|
|
3616
|
+
console.log("[messaging-runner] run complete, response length:", response.length, checkpointTextAlreadySent ? "(text sent at checkpoint)" : "", runContinuation ? "(continuation)" : "");
|
|
3576
3617
|
|
|
3577
3618
|
return {
|
|
3578
3619
|
response,
|
|
@@ -6069,6 +6110,15 @@ export const createRequestHandler = async (options?: {
|
|
|
6069
6110
|
await conversationStore.update(freshConv);
|
|
6070
6111
|
}
|
|
6071
6112
|
|
|
6113
|
+
const pruneWork = pruneCronConversations(
|
|
6114
|
+
conversationStore, cronOwnerId, jobName, cronJob.maxRuns ?? 5,
|
|
6115
|
+
).then(n => {
|
|
6116
|
+
if (n > 0) process.stdout.write(`[cron] ${jobName}: pruned ${n} old conversation${n === 1 ? "" : "s"}\n`);
|
|
6117
|
+
}).catch(err =>
|
|
6118
|
+
console.error(`[cron] ${jobName}: prune failed:`, err instanceof Error ? err.message : err),
|
|
6119
|
+
);
|
|
6120
|
+
doWaitUntil(pruneWork);
|
|
6121
|
+
|
|
6072
6122
|
if (result.continuation) {
|
|
6073
6123
|
const work = selfFetchWithRetry(`/api/internal/continue/${encodeURIComponent(convId)}`).catch(err =>
|
|
6074
6124
|
console.error(`[poncho][cron] Continuation self-fetch failed:`, err instanceof Error ? err.message : err),
|
|
@@ -6460,6 +6510,11 @@ export const startDevServer = async (
|
|
|
6460
6510
|
});
|
|
6461
6511
|
await store.update(freshConv);
|
|
6462
6512
|
}
|
|
6513
|
+
pruneCronConversations(store, "local-owner", jobName, config.maxRuns ?? 5).then(n => {
|
|
6514
|
+
if (n > 0) process.stdout.write(`[cron] ${jobName}: pruned ${n} old conversation${n === 1 ? "" : "s"}\n`);
|
|
6515
|
+
}).catch(err =>
|
|
6516
|
+
console.error(`[cron] ${jobName}: prune failed:`, err instanceof Error ? err.message : err),
|
|
6517
|
+
);
|
|
6463
6518
|
const elapsed = ((Date.now() - start) / 1000).toFixed(1);
|
|
6464
6519
|
process.stdout.write(
|
|
6465
6520
|
`[cron] ${jobName} completed in ${elapsed}s (${result.steps} steps)\n`,
|