omnikey-cli 1.0.37 → 1.0.38
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.
|
@@ -23,9 +23,9 @@ const SHELL_SCRIPT_RE = /<shell_script>([\s\S]*?)<\/shell_script>/;
|
|
|
23
23
|
const FINAL_ANSWER_RE = /<final_answer>/;
|
|
24
24
|
// Maximum time a single job may run before it is forcibly cancelled.
|
|
25
25
|
const JOB_TIMEOUT_MS = 10 * 60 * 1000;
|
|
26
|
-
|
|
27
|
-
//
|
|
28
|
-
const
|
|
26
|
+
const MAX_AGENT_ERROR_RECOVERY_ATTEMPTS = 4;
|
|
27
|
+
// Single-process guard to avoid running the same job concurrently.
|
|
28
|
+
const RUNNING_JOB_IDS = new Set();
|
|
29
29
|
function computeNextRunAt(cronExpression, runAt) {
|
|
30
30
|
if (cronExpression) {
|
|
31
31
|
try {
|
|
@@ -104,6 +104,7 @@ async function runScript(script) {
|
|
|
104
104
|
}
|
|
105
105
|
function runCronJob(job, subscription, sessionId) {
|
|
106
106
|
return new Promise((resolve, reject) => {
|
|
107
|
+
let agentErrorRecoveryAttempts = 0;
|
|
107
108
|
let settled = false;
|
|
108
109
|
const settle = (err) => {
|
|
109
110
|
if (settled)
|
|
@@ -119,11 +120,27 @@ function runCronJob(job, subscription, sessionId) {
|
|
|
119
120
|
void (async () => {
|
|
120
121
|
const content = msg.content ?? '';
|
|
121
122
|
if (msg.is_error) {
|
|
122
|
-
|
|
123
|
+
agentErrorRecoveryAttempts += 1;
|
|
124
|
+
logger_1.logger.warn('Cron job: agent returned error; attempting recovery.', {
|
|
123
125
|
jobId: job.id,
|
|
126
|
+
attempt: agentErrorRecoveryAttempts,
|
|
124
127
|
content: content.slice(0, 300),
|
|
125
128
|
});
|
|
126
|
-
|
|
129
|
+
const shouldFailNow = FINAL_ANSWER_RE.test(content) ||
|
|
130
|
+
agentErrorRecoveryAttempts > MAX_AGENT_ERROR_RECOVERY_ATTEMPTS;
|
|
131
|
+
if (shouldFailNow) {
|
|
132
|
+
settle(new Error(`Agent error: ${content.slice(0, 200)}`));
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
(0, agentServer_1.runAgentTurn)(sessionId, subscription, {
|
|
136
|
+
session_id: sessionId,
|
|
137
|
+
sender: 'user',
|
|
138
|
+
content: `Agent turn failed while processing this cron job. ` +
|
|
139
|
+
`Recover from the latest state and return the next ` +
|
|
140
|
+
`<shell_script> or a <final_answer>.\n\n` +
|
|
141
|
+
`Error details:\n${content}`,
|
|
142
|
+
is_error: true,
|
|
143
|
+
}, send, logger_1.logger, { isCronJob: true }).catch((err) => settle(err instanceof Error ? err : new Error(String(err))));
|
|
127
144
|
return;
|
|
128
145
|
}
|
|
129
146
|
const scriptMatch = SHELL_SCRIPT_RE.exec(content);
|
|
@@ -147,10 +164,28 @@ function runCronJob(job, subscription, sessionId) {
|
|
|
147
164
|
}, send, logger_1.logger, { isCronJob: true }).catch((err) => settle(err instanceof Error ? err : new Error(String(err))));
|
|
148
165
|
return;
|
|
149
166
|
}
|
|
167
|
+
if (msg.is_web_call || msg.is_image_rendering) {
|
|
168
|
+
logger_1.logger.debug('Cron job: received progress notification; waiting for next message.', {
|
|
169
|
+
jobId: job.id,
|
|
170
|
+
isWebCall: !!msg.is_web_call,
|
|
171
|
+
isImageRendering: !!msg.is_image_rendering,
|
|
172
|
+
});
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
150
175
|
if (FINAL_ANSWER_RE.test(content)) {
|
|
151
176
|
logger_1.logger.info('Cron job: received final answer.', { jobId: job.id });
|
|
152
177
|
settle();
|
|
178
|
+
return;
|
|
153
179
|
}
|
|
180
|
+
if (content.trim()) {
|
|
181
|
+
logger_1.logger.warn('Cron job: received untagged agent content; treating as final answer.', {
|
|
182
|
+
jobId: job.id,
|
|
183
|
+
content: content.slice(0, 300),
|
|
184
|
+
});
|
|
185
|
+
settle();
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
settle(new Error('Agent returned empty response with no shell script or final answer.'));
|
|
154
189
|
})();
|
|
155
190
|
};
|
|
156
191
|
(0, agentServer_1.runAgentTurn)(sessionId, subscription, {
|
|
@@ -162,38 +197,51 @@ function runCronJob(job, subscription, sessionId) {
|
|
|
162
197
|
});
|
|
163
198
|
}
|
|
164
199
|
async function executeJob(job) {
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
if (!subscription) {
|
|
168
|
-
logger_1.logger.error('Subscription not found for scheduled job; skipping.', {
|
|
200
|
+
if (RUNNING_JOB_IDS.has(job.id)) {
|
|
201
|
+
logger_1.logger.warn('Scheduled job is already running; skipping duplicate execution.', {
|
|
169
202
|
jobId: job.id,
|
|
170
|
-
|
|
203
|
+
label: job.label,
|
|
171
204
|
});
|
|
172
205
|
return;
|
|
173
206
|
}
|
|
174
|
-
|
|
207
|
+
RUNNING_JOB_IDS.add(job.id);
|
|
208
|
+
logger_1.logger.info('Executing scheduled job.', { jobId: job.id, label: job.label });
|
|
175
209
|
try {
|
|
176
|
-
await
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
210
|
+
const subscription = await subscription_1.Subscription.findByPk(job.subscriptionId);
|
|
211
|
+
if (!subscription) {
|
|
212
|
+
logger_1.logger.error('Subscription not found for scheduled job; skipping.', {
|
|
213
|
+
jobId: job.id,
|
|
214
|
+
subscriptionId: job.subscriptionId,
|
|
215
|
+
});
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
const sessionId = (0, cuid_1.default)();
|
|
219
|
+
try {
|
|
220
|
+
await runCronJob(job, subscription, sessionId);
|
|
221
|
+
logger_1.logger.info('Scheduled job completed.', { jobId: job.id, label: job.label });
|
|
222
|
+
}
|
|
223
|
+
catch (err) {
|
|
224
|
+
logger_1.logger.error('Scheduled job failed.', { jobId: job.id, label: job.label, error: err });
|
|
225
|
+
// Fall through — always update lastRunAt so the next poll does not re-run immediately.
|
|
226
|
+
}
|
|
227
|
+
const now = new Date();
|
|
228
|
+
if (job.cronExpression) {
|
|
229
|
+
await job.update({
|
|
230
|
+
lastRunAt: now,
|
|
231
|
+
nextRunAt: computeNextRunAt(job.cronExpression, null),
|
|
232
|
+
lastRunSessionId: sessionId,
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
else {
|
|
236
|
+
await job.update({
|
|
237
|
+
lastRunAt: now,
|
|
238
|
+
isActive: false,
|
|
239
|
+
nextRunAt: null,
|
|
240
|
+
lastRunSessionId: sessionId,
|
|
241
|
+
});
|
|
242
|
+
}
|
|
190
243
|
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
lastRunAt: now,
|
|
194
|
-
isActive: false,
|
|
195
|
-
nextRunAt: null,
|
|
196
|
-
lastRunSessionId: sessionId,
|
|
197
|
-
});
|
|
244
|
+
finally {
|
|
245
|
+
RUNNING_JOB_IDS.delete(job.id);
|
|
198
246
|
}
|
|
199
247
|
}
|
package/package.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"access": "public",
|
|
5
5
|
"registry": "https://registry.npmjs.org/"
|
|
6
6
|
},
|
|
7
|
-
"version": "1.0.
|
|
7
|
+
"version": "1.0.38",
|
|
8
8
|
"description": "CLI for onboarding users to Omnikey AI and configuring OPENAI_API_KEY. Use Yarn for install/build.",
|
|
9
9
|
"engines": {
|
|
10
10
|
"node": ">=14.0.0",
|