opencode-auto-loop 0.1.2 → 0.1.3
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/commands/auto-loop.md +14 -4
- package/package.json +2 -2
- package/skills/auto-loop/SKILL.md +1 -1
- package/src/index.ts +122 -41
package/commands/auto-loop.md
CHANGED
|
@@ -1,13 +1,23 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: Start Auto Loop - auto-continues until task completion
|
|
2
|
+
description: "Start Auto Loop - auto-continues until task completion. Use: /auto-loop <task description>"
|
|
3
3
|
---
|
|
4
4
|
|
|
5
5
|
# Auto Loop
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Parse `$ARGUMENTS` for the task description and an optional `--max <number>` flag.
|
|
8
8
|
|
|
9
|
-
- **task
|
|
10
|
-
- **maxIterations**:
|
|
9
|
+
- If `$ARGUMENTS` contains `--max <number>`, extract that number as **maxIterations** and remove it from the task string.
|
|
10
|
+
- Otherwise, use **maxIterations**: 25
|
|
11
|
+
|
|
12
|
+
Invoke the `auto-loop` tool with:
|
|
13
|
+
|
|
14
|
+
- **task**: the extracted task description
|
|
15
|
+
- **maxIterations**: the extracted or default value
|
|
16
|
+
|
|
17
|
+
Examples:
|
|
18
|
+
- `/auto-loop Build a REST API` → task="Build a REST API", maxIterations=25
|
|
19
|
+
- `/auto-loop Build a REST API --max 50` → task="Build a REST API", maxIterations=50
|
|
20
|
+
- `/auto-loop --max 10 Fix all lint errors` → task="Fix all lint errors", maxIterations=10
|
|
11
21
|
|
|
12
22
|
After the tool confirms the loop is active, **immediately begin working on the task**. Do not just acknowledge — start doing the work right away.
|
|
13
23
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-auto-loop",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Auto-continue for OpenCode",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"type": "module",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"skills/"
|
|
28
28
|
],
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@opencode-ai/plugin": "^
|
|
30
|
+
"@opencode-ai/plugin": "^1.2.27"
|
|
31
31
|
},
|
|
32
32
|
"engines": {
|
|
33
33
|
"node": ">=18.0.0"
|
package/src/index.ts
CHANGED
|
@@ -2,11 +2,10 @@ import { type Plugin, type PluginInput, tool } from "@opencode-ai/plugin";
|
|
|
2
2
|
import {
|
|
3
3
|
existsSync,
|
|
4
4
|
readFileSync,
|
|
5
|
-
writeFileSync,
|
|
6
5
|
mkdirSync,
|
|
7
|
-
unlinkSync,
|
|
8
6
|
cpSync,
|
|
9
7
|
} from "fs";
|
|
8
|
+
import { readFile, writeFile, unlink, mkdir } from "fs/promises";
|
|
10
9
|
import { dirname, join } from "path";
|
|
11
10
|
import { fileURLToPath } from "url";
|
|
12
11
|
import { homedir } from "os";
|
|
@@ -16,6 +15,7 @@ interface LoopState {
|
|
|
16
15
|
active: boolean;
|
|
17
16
|
iteration: number;
|
|
18
17
|
maxIterations: number;
|
|
18
|
+
debounceMs: number;
|
|
19
19
|
sessionId?: string;
|
|
20
20
|
prompt?: string;
|
|
21
21
|
completed?: string;
|
|
@@ -31,7 +31,8 @@ const SERVICE_NAME = "auto-loop";
|
|
|
31
31
|
const STATE_FILENAME = "auto-loop.local.md";
|
|
32
32
|
const OPENCODE_CONFIG_DIR = join(homedir(), ".config/opencode");
|
|
33
33
|
const COMPLETION_TAG = /^\s*<promise>\s*DONE\s*<\/promise>\s*$/im;
|
|
34
|
-
const
|
|
34
|
+
const DEFAULT_DEBOUNCE_MS = 2000;
|
|
35
|
+
const DEFAULT_MAX_ITERATIONS = 25;
|
|
35
36
|
|
|
36
37
|
// Get plugin root directory (ESM only — package is "type": "module")
|
|
37
38
|
function getPluginRoot(): string {
|
|
@@ -104,13 +105,14 @@ function getStateFile(directory: string): string {
|
|
|
104
105
|
// Parse markdown frontmatter state
|
|
105
106
|
function parseState(content: string): LoopState {
|
|
106
107
|
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
107
|
-
if (!match) return { active: false, iteration: 0, maxIterations:
|
|
108
|
+
if (!match) return { active: false, iteration: 0, maxIterations: DEFAULT_MAX_ITERATIONS, debounceMs: DEFAULT_DEBOUNCE_MS };
|
|
108
109
|
|
|
109
110
|
const frontmatter = match[1];
|
|
110
111
|
const state: LoopState = {
|
|
111
112
|
active: false,
|
|
112
113
|
iteration: 0,
|
|
113
|
-
maxIterations:
|
|
114
|
+
maxIterations: DEFAULT_MAX_ITERATIONS,
|
|
115
|
+
debounceMs: DEFAULT_DEBOUNCE_MS,
|
|
114
116
|
};
|
|
115
117
|
|
|
116
118
|
for (const line of frontmatter.split("\n")) {
|
|
@@ -118,7 +120,8 @@ function parseState(content: string): LoopState {
|
|
|
118
120
|
const value = valueParts.join(":").trim();
|
|
119
121
|
if (key === "active") state.active = value === "true";
|
|
120
122
|
if (key === "iteration") state.iteration = parseInt(value) || 0;
|
|
121
|
-
if (key === "maxIterations") state.maxIterations = parseInt(value) ||
|
|
123
|
+
if (key === "maxIterations") state.maxIterations = parseInt(value) || DEFAULT_MAX_ITERATIONS;
|
|
124
|
+
if (key === "debounceMs") state.debounceMs = parseInt(value) || DEFAULT_DEBOUNCE_MS;
|
|
122
125
|
if (key === "sessionId") state.sessionId = value || undefined;
|
|
123
126
|
}
|
|
124
127
|
|
|
@@ -152,6 +155,7 @@ function serializeState(state: LoopState): string {
|
|
|
152
155
|
`active: ${state.active}`,
|
|
153
156
|
`iteration: ${state.iteration}`,
|
|
154
157
|
`maxIterations: ${state.maxIterations}`,
|
|
158
|
+
`debounceMs: ${state.debounceMs}`,
|
|
155
159
|
];
|
|
156
160
|
if (state.sessionId) lines.push(`sessionId: ${state.sessionId}`);
|
|
157
161
|
lines.push("---");
|
|
@@ -162,32 +166,37 @@ function serializeState(state: LoopState): string {
|
|
|
162
166
|
}
|
|
163
167
|
|
|
164
168
|
// Read state from project directory
|
|
165
|
-
function readState(directory: string): LoopState {
|
|
169
|
+
async function readState(directory: string): Promise<LoopState> {
|
|
166
170
|
const stateFile = getStateFile(directory);
|
|
167
|
-
|
|
168
|
-
|
|
171
|
+
try {
|
|
172
|
+
const content = await readFile(stateFile, "utf-8");
|
|
173
|
+
return parseState(content);
|
|
174
|
+
} catch {
|
|
175
|
+
return { active: false, iteration: 0, maxIterations: DEFAULT_MAX_ITERATIONS, debounceMs: DEFAULT_DEBOUNCE_MS };
|
|
169
176
|
}
|
|
170
|
-
return { active: false, iteration: 0, maxIterations: 100 };
|
|
171
177
|
}
|
|
172
178
|
|
|
173
179
|
// Write state to project directory
|
|
174
|
-
function writeState(directory: string, state: LoopState, log: LogFn): void {
|
|
180
|
+
async function writeState(directory: string, state: LoopState, log: LogFn): Promise<void> {
|
|
175
181
|
try {
|
|
176
182
|
const stateFile = getStateFile(directory);
|
|
177
|
-
|
|
178
|
-
|
|
183
|
+
await mkdir(dirname(stateFile), { recursive: true });
|
|
184
|
+
await writeFile(stateFile, serializeState(state));
|
|
179
185
|
} catch (err) {
|
|
180
186
|
log("error", `Failed to write state: ${err}`);
|
|
181
187
|
}
|
|
182
188
|
}
|
|
183
189
|
|
|
184
190
|
// Clear state
|
|
185
|
-
function clearState(directory: string, log: LogFn): void {
|
|
191
|
+
async function clearState(directory: string, log: LogFn): Promise<void> {
|
|
186
192
|
try {
|
|
187
193
|
const stateFile = getStateFile(directory);
|
|
188
|
-
|
|
194
|
+
await unlink(stateFile);
|
|
189
195
|
} catch (err) {
|
|
190
|
-
|
|
196
|
+
// ENOENT is fine — file already gone
|
|
197
|
+
if ((err as NodeJS.ErrnoException).code !== "ENOENT") {
|
|
198
|
+
log("warn", `Failed to clear state: ${err}`);
|
|
199
|
+
}
|
|
191
200
|
}
|
|
192
201
|
}
|
|
193
202
|
|
|
@@ -198,7 +207,8 @@ function stripCodeFences(text: string): string {
|
|
|
198
207
|
.replace(/`[^`]+`/g, ""); // inline backtick code
|
|
199
208
|
}
|
|
200
209
|
|
|
201
|
-
// Extract text from the last assistant message in a session
|
|
210
|
+
// Extract text from the last assistant message in a session.
|
|
211
|
+
// Fetches only the most recent messages to avoid pulling the entire history.
|
|
202
212
|
async function getLastAssistantText(
|
|
203
213
|
client: OpencodeClient,
|
|
204
214
|
sessionId: string,
|
|
@@ -208,7 +218,7 @@ async function getLastAssistantText(
|
|
|
208
218
|
try {
|
|
209
219
|
const response = await client.session.messages({
|
|
210
220
|
path: { id: sessionId },
|
|
211
|
-
query: { directory },
|
|
221
|
+
query: { directory, limit: 10 },
|
|
212
222
|
});
|
|
213
223
|
|
|
214
224
|
const messages = response.data ?? [];
|
|
@@ -350,6 +360,27 @@ Before going idle, list your progress using ## Completed and ## Next Steps secti
|
|
|
350
360
|
Do NOT output false completion promises. If blocked, explain the blocker.`;
|
|
351
361
|
}
|
|
352
362
|
|
|
363
|
+
// Check if session is currently busy (not idle)
|
|
364
|
+
async function isSessionBusy(
|
|
365
|
+
client: OpencodeClient,
|
|
366
|
+
sessionId: string,
|
|
367
|
+
log: LogFn
|
|
368
|
+
): Promise<boolean> {
|
|
369
|
+
try {
|
|
370
|
+
const response = await client.session.status({});
|
|
371
|
+
const statuses = response.data ?? {};
|
|
372
|
+
const status = statuses[sessionId];
|
|
373
|
+
if (status && status.type !== "idle") {
|
|
374
|
+
log("debug", `Session ${sessionId} is ${status.type}, skipping continuation`);
|
|
375
|
+
return true;
|
|
376
|
+
}
|
|
377
|
+
return false;
|
|
378
|
+
} catch (err) {
|
|
379
|
+
log("warn", `Failed to check session status: ${err}`);
|
|
380
|
+
return false; // Assume not busy if we can't check
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
353
384
|
// Main plugin
|
|
354
385
|
export const AutoLoopPlugin: Plugin = async (ctx) => {
|
|
355
386
|
const directory = ctx.directory || process.cwd();
|
|
@@ -386,6 +417,11 @@ export const AutoLoopPlugin: Plugin = async (ctx) => {
|
|
|
386
417
|
|
|
387
418
|
// Debounce tracking for idle events
|
|
388
419
|
let lastContinuation = 0;
|
|
420
|
+
// Guard: prevent sending while a continuation is already in-flight.
|
|
421
|
+
// Set to true when we send promptAsync, cleared when we receive a
|
|
422
|
+
// session.idle or session.status(idle) event — NOT in the finally block,
|
|
423
|
+
// which fires too early (~50ms after the 204, while AI is still busy).
|
|
424
|
+
let continuationInFlight = false;
|
|
389
425
|
|
|
390
426
|
return {
|
|
391
427
|
tool: {
|
|
@@ -399,17 +435,27 @@ export const AutoLoopPlugin: Plugin = async (ctx) => {
|
|
|
399
435
|
maxIterations: tool.schema
|
|
400
436
|
.number()
|
|
401
437
|
.optional()
|
|
402
|
-
.describe("Maximum iterations (default:
|
|
438
|
+
.describe("Maximum iterations (default: 25)"),
|
|
439
|
+
debounceMs: tool.schema
|
|
440
|
+
.number()
|
|
441
|
+
.optional()
|
|
442
|
+
.describe("Debounce delay between iterations in ms (default: 2000)"),
|
|
403
443
|
},
|
|
404
|
-
async execute({ task, maxIterations =
|
|
444
|
+
async execute({ task, maxIterations = DEFAULT_MAX_ITERATIONS, debounceMs = DEFAULT_DEBOUNCE_MS }, context) {
|
|
445
|
+
if (context.abort.aborted) return "Auto Loop start was cancelled.";
|
|
446
|
+
|
|
405
447
|
const state: LoopState = {
|
|
406
448
|
active: true,
|
|
407
449
|
iteration: 0,
|
|
408
450
|
maxIterations,
|
|
451
|
+
debounceMs,
|
|
409
452
|
sessionId: context.sessionID,
|
|
410
453
|
prompt: task,
|
|
411
454
|
};
|
|
412
|
-
writeState(directory, state, log);
|
|
455
|
+
await writeState(directory, state, log);
|
|
456
|
+
// Reset guards so the first idle event is not blocked
|
|
457
|
+
continuationInFlight = false;
|
|
458
|
+
lastContinuation = 0;
|
|
413
459
|
|
|
414
460
|
log("info", `Loop started for session ${context.sessionID}`);
|
|
415
461
|
toast(`Auto Loop started (max ${maxIterations} iterations)`, "success");
|
|
@@ -439,13 +485,15 @@ Use /cancel-auto-loop to stop early.`;
|
|
|
439
485
|
"cancel-auto-loop": tool({
|
|
440
486
|
description: "Cancel active Auto Loop",
|
|
441
487
|
args: {},
|
|
442
|
-
async execute() {
|
|
443
|
-
|
|
488
|
+
async execute(_args, context) {
|
|
489
|
+
if (context.abort.aborted) return "Cancel was aborted.";
|
|
490
|
+
const state = await readState(directory);
|
|
444
491
|
if (!state.active) {
|
|
445
492
|
return "No active Auto Loop to cancel.";
|
|
446
493
|
}
|
|
447
494
|
const iterations = state.iteration;
|
|
448
|
-
clearState(directory, log);
|
|
495
|
+
await clearState(directory, log);
|
|
496
|
+
continuationInFlight = false;
|
|
449
497
|
|
|
450
498
|
log("info", `Loop cancelled after ${iterations} iteration(s)`);
|
|
451
499
|
toast(`Auto Loop cancelled after ${iterations} iteration(s)`, "warning");
|
|
@@ -462,10 +510,16 @@ Use /cancel-auto-loop to stop early.`;
|
|
|
462
510
|
|
|
463
511
|
## Available Commands
|
|
464
512
|
|
|
465
|
-
- \`/auto-loop <task>\` - Start an auto-continuation loop
|
|
513
|
+
- \`/auto-loop <task>\` - Start an auto-continuation loop (default: 25 iterations)
|
|
514
|
+
- \`/auto-loop <task> --max <n>\` - Start with a custom iteration limit
|
|
466
515
|
- \`/cancel-auto-loop\` - Stop an active loop
|
|
467
516
|
- \`/auto-loop-help\` - Show this help
|
|
468
517
|
|
|
518
|
+
## Examples
|
|
519
|
+
|
|
520
|
+
- \`/auto-loop Build a REST API\` — runs up to 25 iterations
|
|
521
|
+
- \`/auto-loop Fix all lint errors --max 10\` — runs up to 10 iterations
|
|
522
|
+
|
|
469
523
|
## How It Works
|
|
470
524
|
|
|
471
525
|
1. Start with: /auto-loop "Build a REST API"
|
|
@@ -484,30 +538,37 @@ Located at: .opencode/auto-loop.local.md`;
|
|
|
484
538
|
event: async ({ event }) => {
|
|
485
539
|
// --- session.idle: core auto-continuation logic ---
|
|
486
540
|
if (event.type === "session.idle") {
|
|
487
|
-
const now = Date.now();
|
|
488
|
-
if (now - lastContinuation < DEBOUNCE_MS) return;
|
|
489
|
-
|
|
490
541
|
const sessionId = event.properties.sessionID;
|
|
491
|
-
|
|
542
|
+
|
|
543
|
+
// Session confirmed idle — safe to clear in-flight guard
|
|
544
|
+
continuationInFlight = false;
|
|
545
|
+
|
|
546
|
+
const state = await readState(directory);
|
|
492
547
|
|
|
493
548
|
if (!state.active) return;
|
|
494
549
|
if (!sessionId) return;
|
|
495
550
|
if (state.sessionId && state.sessionId !== sessionId) return;
|
|
496
551
|
|
|
552
|
+
const now = Date.now();
|
|
553
|
+
if (now - lastContinuation < state.debounceMs) return;
|
|
554
|
+
|
|
555
|
+
// Double-check the session is truly idle before sending
|
|
556
|
+
if (await isSessionBusy(client, sessionId, log)) return;
|
|
557
|
+
|
|
497
558
|
// Fetch last assistant message (used for completion check + progress extraction)
|
|
498
559
|
const lastText = await getLastAssistantText(client, sessionId, directory, log);
|
|
499
560
|
|
|
500
561
|
// Skip completion check on iteration 0 (first idle after loop start)
|
|
501
562
|
// to avoid false positives from the tool's initial response text
|
|
502
563
|
if (state.iteration > 0 && lastText && checkCompletion(lastText)) {
|
|
503
|
-
clearState(directory, log);
|
|
564
|
+
await clearState(directory, log);
|
|
504
565
|
log("info", `Loop completed at iteration ${state.iteration}`);
|
|
505
566
|
toast(`Auto Loop completed after ${state.iteration} iteration(s)`, "success");
|
|
506
567
|
return;
|
|
507
568
|
}
|
|
508
569
|
|
|
509
570
|
if (state.iteration >= state.maxIterations) {
|
|
510
|
-
clearState(directory, log);
|
|
571
|
+
await clearState(directory, log);
|
|
511
572
|
log("warn", `Loop hit max iterations (${state.maxIterations})`);
|
|
512
573
|
toast(`Auto Loop stopped — max iterations (${state.maxIterations}) reached`, "warning");
|
|
513
574
|
return;
|
|
@@ -521,12 +582,10 @@ Located at: .opencode/auto-loop.local.md`;
|
|
|
521
582
|
...state,
|
|
522
583
|
iteration: state.iteration + 1,
|
|
523
584
|
sessionId,
|
|
524
|
-
// Update next steps if we found new ones, otherwise keep previous
|
|
525
585
|
nextSteps: newNextSteps || state.nextSteps,
|
|
526
|
-
// Merge completed: append new completed items to existing
|
|
527
586
|
completed: mergeCompleted(state.completed, newCompleted),
|
|
528
587
|
};
|
|
529
|
-
writeState(directory, newState, log);
|
|
588
|
+
await writeState(directory, newState, log);
|
|
530
589
|
lastContinuation = Date.now();
|
|
531
590
|
|
|
532
591
|
// Build continuation prompt with progress context
|
|
@@ -546,7 +605,13 @@ Original task:
|
|
|
546
605
|
${state.prompt || "(no task specified)"}`;
|
|
547
606
|
|
|
548
607
|
try {
|
|
549
|
-
|
|
608
|
+
// Use promptAsync (fire-and-forget) so the event handler returns
|
|
609
|
+
// immediately. This allows the next session.idle event to fire
|
|
610
|
+
// naturally when the AI finishes, enabling the loop to continue.
|
|
611
|
+
// The synchronous prompt() blocks until the AI response completes,
|
|
612
|
+
// which prevents subsequent idle events from being processed.
|
|
613
|
+
continuationInFlight = true;
|
|
614
|
+
await client.session.promptAsync({
|
|
550
615
|
path: { id: sessionId },
|
|
551
616
|
body: {
|
|
552
617
|
parts: [{ type: "text", text: continuationPrompt }],
|
|
@@ -555,21 +620,31 @@ ${state.prompt || "(no task specified)"}`;
|
|
|
555
620
|
log("info", `Sent continuation ${newState.iteration}/${newState.maxIterations}`);
|
|
556
621
|
toast(`Auto Loop: iteration ${newState.iteration}/${newState.maxIterations}`);
|
|
557
622
|
} catch (err) {
|
|
623
|
+
// On failure, clear the guard so the next idle event can retry
|
|
624
|
+
continuationInFlight = false;
|
|
558
625
|
log("error", `Failed to send continuation prompt: ${err}`);
|
|
559
626
|
}
|
|
560
627
|
}
|
|
561
628
|
|
|
629
|
+
// --- session.status: clear in-flight guard when session returns to idle ---
|
|
630
|
+
if (event.type === "session.status") {
|
|
631
|
+
if (event.properties.status?.type === "idle") {
|
|
632
|
+
continuationInFlight = false;
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
|
|
562
636
|
// --- session.compacted: re-inject loop context after compaction ---
|
|
563
637
|
if (event.type === "session.compacted") {
|
|
564
638
|
const sessionId = event.properties.sessionID;
|
|
565
|
-
const state = readState(directory);
|
|
639
|
+
const state = await readState(directory);
|
|
566
640
|
|
|
567
641
|
if (!state.active) return;
|
|
568
642
|
if (state.sessionId && state.sessionId !== sessionId) return;
|
|
569
643
|
|
|
570
644
|
// After compaction, the AI loses loop context — send a reminder
|
|
645
|
+
// Use promptAsync so we don't block event processing
|
|
571
646
|
try {
|
|
572
|
-
await client.session.
|
|
647
|
+
await client.session.promptAsync({
|
|
573
648
|
path: { id: sessionId },
|
|
574
649
|
body: {
|
|
575
650
|
parts: [{ type: "text", text: buildLoopContextReminder(state) }],
|
|
@@ -584,7 +659,11 @@ ${state.prompt || "(no task specified)"}`;
|
|
|
584
659
|
// --- session.error: pause the loop on error ---
|
|
585
660
|
if (event.type === "session.error") {
|
|
586
661
|
const sessionId = event.properties.sessionID;
|
|
587
|
-
|
|
662
|
+
// sessionID is optional in the SDK types — if missing, we can't
|
|
663
|
+
// reliably attribute the error to our session, so skip.
|
|
664
|
+
if (!sessionId) return;
|
|
665
|
+
|
|
666
|
+
const state = await readState(directory);
|
|
588
667
|
|
|
589
668
|
if (
|
|
590
669
|
state.active &&
|
|
@@ -592,20 +671,22 @@ ${state.prompt || "(no task specified)"}`;
|
|
|
592
671
|
) {
|
|
593
672
|
log("warn", `Session error detected, pausing loop at iteration ${state.iteration}`);
|
|
594
673
|
toast("Auto Loop paused — session error", "error");
|
|
674
|
+
continuationInFlight = false;
|
|
595
675
|
// Mark inactive but keep state so user can inspect/resume
|
|
596
|
-
writeState(directory, { ...state, active: false }, log);
|
|
676
|
+
await writeState(directory, { ...state, active: false }, log);
|
|
597
677
|
}
|
|
598
678
|
}
|
|
599
679
|
|
|
600
680
|
// --- session.deleted: clean up if it's our session ---
|
|
601
681
|
if (event.type === "session.deleted") {
|
|
602
|
-
const state = readState(directory);
|
|
682
|
+
const state = await readState(directory);
|
|
603
683
|
if (!state.active) return;
|
|
604
684
|
|
|
605
685
|
const deletedSessionId = event.properties.info?.id;
|
|
606
686
|
if (state.sessionId && deletedSessionId && state.sessionId !== deletedSessionId) return;
|
|
607
687
|
|
|
608
|
-
clearState(directory, log);
|
|
688
|
+
await clearState(directory, log);
|
|
689
|
+
continuationInFlight = false;
|
|
609
690
|
log("info", "Session deleted, cleaning up loop state");
|
|
610
691
|
}
|
|
611
692
|
},
|