create-loopengine 0.1.63 → 0.1.65
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/package.json +1 -1
- package/template/adapters/http.ts +26 -5
- package/template/package.json +1 -1
package/package.json
CHANGED
|
@@ -124,6 +124,7 @@ import {
|
|
|
124
124
|
listAgentPending,
|
|
125
125
|
listAgentSlackChannels,
|
|
126
126
|
slackCredentialsFor,
|
|
127
|
+
slackConversationsOn,
|
|
127
128
|
openSlackAnswerModal,
|
|
128
129
|
parseSlackInteraction,
|
|
129
130
|
replyInSlack,
|
|
@@ -2354,6 +2355,10 @@ async function answerPendingQuestion(res: ServerResponse, pendingId: string, bod
|
|
|
2354
2355
|
* resumeAgent(), through the same durable sessions.withSession append
|
|
2355
2356
|
* path any other turn goes through. */
|
|
2356
2357
|
async function respondAfterResolution(res: ServerResponse, finalCheckpoint: TurnCheckpoint): Promise<void> {
|
|
2358
|
+
await tracked(() => respondAfterResolutionNow(res, finalCheckpoint))
|
|
2359
|
+
}
|
|
2360
|
+
|
|
2361
|
+
async function respondAfterResolutionNow(res: ServerResponse, finalCheckpoint: TurnCheckpoint): Promise<void> {
|
|
2357
2362
|
if (Object.keys(finalCheckpoint.outstanding).length > 0) {
|
|
2358
2363
|
res.writeHead(200, { 'content-type': 'application/json' }).end(
|
|
2359
2364
|
JSON.stringify({ resolved: true, outstanding: Object.keys(finalCheckpoint.outstanding).length }),
|
|
@@ -2588,11 +2593,24 @@ function exitIfCodeChanged(res: ServerResponse): void {
|
|
|
2588
2593
|
// checks before stopping an idle server, so it never stops one mid-turn.
|
|
2589
2594
|
let openStreams = 0
|
|
2590
2595
|
let lastRequestAt = Date.now()
|
|
2596
|
+
// Work still running after its request was answered (a Slack thread reply
|
|
2597
|
+
// being answered, a run resumed after a decision), so a host never
|
|
2598
|
+
// suspends this server in the middle of it.
|
|
2599
|
+
let backgroundWork = 0
|
|
2600
|
+
|
|
2601
|
+
async function tracked<T>(fn: () => Promise<T>): Promise<T> {
|
|
2602
|
+
backgroundWork++
|
|
2603
|
+
try {
|
|
2604
|
+
return await fn()
|
|
2605
|
+
} finally {
|
|
2606
|
+
backgroundWork--
|
|
2607
|
+
}
|
|
2608
|
+
}
|
|
2591
2609
|
|
|
2592
2610
|
async function handleActivityGet(res: ServerResponse): Promise<void> {
|
|
2593
2611
|
res.writeHead(200, { 'content-type': 'application/json' }).end(
|
|
2594
2612
|
JSON.stringify({
|
|
2595
|
-
inFlightTurns: sessionTurns.size + scheduler.running,
|
|
2613
|
+
inFlightTurns: sessionTurns.size + scheduler.running + backgroundWork,
|
|
2596
2614
|
scheduledRuns: scheduler.running,
|
|
2597
2615
|
// When the next scheduled task is due — wake this server by then
|
|
2598
2616
|
// (POST /internal/scheduler/tick) if it's been stopped while idle.
|
|
@@ -2741,7 +2759,7 @@ async function handleSlackEvents(req: IncomingMessage, res: ServerResponse, agen
|
|
|
2741
2759
|
}
|
|
2742
2760
|
// Slack wants an answer within three seconds; the reply is posted after.
|
|
2743
2761
|
res.writeHead(200).end()
|
|
2744
|
-
if (event.type !== 'message' || !firstSighting(event)) return
|
|
2762
|
+
if (event.type !== 'message' || !firstSighting(event) || !slackConversationsOn(entry.config)) return
|
|
2745
2763
|
const environment = process.env.LOOPENGINE_ENV ?? 'production'
|
|
2746
2764
|
await handleInboundMessage(event, {
|
|
2747
2765
|
agentName,
|
|
@@ -2772,7 +2790,8 @@ const server = createServer(async (req, res) => {
|
|
|
2772
2790
|
const slackEventsMatch = req.method === 'POST' && (req.url ?? '/').split('?')[0].match(/^\/hooks\/slack\/([^/]+)\/events$/)
|
|
2773
2791
|
if (slackEventsMatch) {
|
|
2774
2792
|
try {
|
|
2775
|
-
|
|
2793
|
+
lastRequestAt = Date.now()
|
|
2794
|
+
await tracked(() => handleSlackEvents(req, res, decodeURIComponent(slackEventsMatch[1])))
|
|
2776
2795
|
} catch (err) {
|
|
2777
2796
|
console.error('[loopengine] Slack event failed:', err)
|
|
2778
2797
|
if (!res.headersSent) res.writeHead(500).end()
|
|
@@ -2783,7 +2802,8 @@ const server = createServer(async (req, res) => {
|
|
|
2783
2802
|
const slackHookMatch = req.method === 'POST' && (req.url ?? '/').split('?')[0].match(/^\/hooks\/slack\/([^/]+)$/)
|
|
2784
2803
|
if (slackHookMatch) {
|
|
2785
2804
|
try {
|
|
2786
|
-
|
|
2805
|
+
lastRequestAt = Date.now()
|
|
2806
|
+
await tracked(() => handleSlackInteraction(req, res, decodeURIComponent(slackHookMatch[1])))
|
|
2787
2807
|
} catch (err) {
|
|
2788
2808
|
console.error('[loopengine] Slack interaction failed:', err)
|
|
2789
2809
|
if (!res.headersSent) res.writeHead(500).end()
|
|
@@ -2794,7 +2814,8 @@ const server = createServer(async (req, res) => {
|
|
|
2794
2814
|
const decideMatch = (req.method === 'GET' || req.method === 'POST') && (req.url ?? '/').split('?')[0].match(/^\/decide\/([A-Za-z0-9_.-]+)$/)
|
|
2795
2815
|
if (decideMatch) {
|
|
2796
2816
|
try {
|
|
2797
|
-
|
|
2817
|
+
lastRequestAt = Date.now()
|
|
2818
|
+
await tracked(() => handleDecide(req, res, decideMatch[1]))
|
|
2798
2819
|
} catch (err) {
|
|
2799
2820
|
console.error('[loopengine] decision link failed:', err)
|
|
2800
2821
|
if (!res.headersSent) res.writeHead(500, { 'content-type': 'application/json' }).end(JSON.stringify({ error: 'Something went wrong; try again.' }))
|