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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-loopengine",
3
- "version": "0.1.63",
3
+ "version": "0.1.65",
4
4
  "description": "Scaffold a new loopengine agent project — a runnable HTTP server with one example agent, in your own repo.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -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
- await handleSlackEvents(req, res, decodeURIComponent(slackEventsMatch[1]))
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
- await handleSlackInteraction(req, res, decodeURIComponent(slackHookMatch[1]))
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
- await handleDecide(req, res, decideMatch[1])
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.' }))
@@ -9,7 +9,7 @@
9
9
  "cli": "tsx adapters/cli.ts"
10
10
  },
11
11
  "dependencies": {
12
- "loopengine": "^0.1.78",
12
+ "loopengine": "^0.1.81",
13
13
  "actauth": "^0.0.12",
14
14
  "tsx": "^4.19.0",
15
15
  "typescript": "^5.7.0"