create-loopengine 0.1.63 → 0.1.64

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.64",
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",
@@ -2354,6 +2354,10 @@ async function answerPendingQuestion(res: ServerResponse, pendingId: string, bod
2354
2354
  * resumeAgent(), through the same durable sessions.withSession append
2355
2355
  * path any other turn goes through. */
2356
2356
  async function respondAfterResolution(res: ServerResponse, finalCheckpoint: TurnCheckpoint): Promise<void> {
2357
+ await tracked(() => respondAfterResolutionNow(res, finalCheckpoint))
2358
+ }
2359
+
2360
+ async function respondAfterResolutionNow(res: ServerResponse, finalCheckpoint: TurnCheckpoint): Promise<void> {
2357
2361
  if (Object.keys(finalCheckpoint.outstanding).length > 0) {
2358
2362
  res.writeHead(200, { 'content-type': 'application/json' }).end(
2359
2363
  JSON.stringify({ resolved: true, outstanding: Object.keys(finalCheckpoint.outstanding).length }),
@@ -2588,11 +2592,24 @@ function exitIfCodeChanged(res: ServerResponse): void {
2588
2592
  // checks before stopping an idle server, so it never stops one mid-turn.
2589
2593
  let openStreams = 0
2590
2594
  let lastRequestAt = Date.now()
2595
+ // Work still running after its request was answered (a Slack thread reply
2596
+ // being answered, a run resumed after a decision), so a host never
2597
+ // suspends this server in the middle of it.
2598
+ let backgroundWork = 0
2599
+
2600
+ async function tracked<T>(fn: () => Promise<T>): Promise<T> {
2601
+ backgroundWork++
2602
+ try {
2603
+ return await fn()
2604
+ } finally {
2605
+ backgroundWork--
2606
+ }
2607
+ }
2591
2608
 
2592
2609
  async function handleActivityGet(res: ServerResponse): Promise<void> {
2593
2610
  res.writeHead(200, { 'content-type': 'application/json' }).end(
2594
2611
  JSON.stringify({
2595
- inFlightTurns: sessionTurns.size + scheduler.running,
2612
+ inFlightTurns: sessionTurns.size + scheduler.running + backgroundWork,
2596
2613
  scheduledRuns: scheduler.running,
2597
2614
  // When the next scheduled task is due — wake this server by then
2598
2615
  // (POST /internal/scheduler/tick) if it's been stopped while idle.
@@ -2772,7 +2789,8 @@ const server = createServer(async (req, res) => {
2772
2789
  const slackEventsMatch = req.method === 'POST' && (req.url ?? '/').split('?')[0].match(/^\/hooks\/slack\/([^/]+)\/events$/)
2773
2790
  if (slackEventsMatch) {
2774
2791
  try {
2775
- await handleSlackEvents(req, res, decodeURIComponent(slackEventsMatch[1]))
2792
+ lastRequestAt = Date.now()
2793
+ await tracked(() => handleSlackEvents(req, res, decodeURIComponent(slackEventsMatch[1])))
2776
2794
  } catch (err) {
2777
2795
  console.error('[loopengine] Slack event failed:', err)
2778
2796
  if (!res.headersSent) res.writeHead(500).end()
@@ -2783,7 +2801,8 @@ const server = createServer(async (req, res) => {
2783
2801
  const slackHookMatch = req.method === 'POST' && (req.url ?? '/').split('?')[0].match(/^\/hooks\/slack\/([^/]+)$/)
2784
2802
  if (slackHookMatch) {
2785
2803
  try {
2786
- await handleSlackInteraction(req, res, decodeURIComponent(slackHookMatch[1]))
2804
+ lastRequestAt = Date.now()
2805
+ await tracked(() => handleSlackInteraction(req, res, decodeURIComponent(slackHookMatch[1])))
2787
2806
  } catch (err) {
2788
2807
  console.error('[loopengine] Slack interaction failed:', err)
2789
2808
  if (!res.headersSent) res.writeHead(500).end()
@@ -2794,7 +2813,8 @@ const server = createServer(async (req, res) => {
2794
2813
  const decideMatch = (req.method === 'GET' || req.method === 'POST') && (req.url ?? '/').split('?')[0].match(/^\/decide\/([A-Za-z0-9_.-]+)$/)
2795
2814
  if (decideMatch) {
2796
2815
  try {
2797
- await handleDecide(req, res, decideMatch[1])
2816
+ lastRequestAt = Date.now()
2817
+ await tracked(() => handleDecide(req, res, decideMatch[1]))
2798
2818
  } catch (err) {
2799
2819
  console.error('[loopengine] decision link failed:', err)
2800
2820
  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.79",
13
13
  "actauth": "^0.0.12",
14
14
  "tsx": "^4.19.0",
15
15
  "typescript": "^5.7.0"