create-loopengine 0.1.61 → 0.1.63
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/dist/cli.js +3 -1
- package/package.json +1 -1
- package/template/_gitignore +2 -0
- package/template/adapters/http.ts +65 -2
- package/template/package.json +5 -5
package/dist/cli.js
CHANGED
|
@@ -199,7 +199,9 @@ function resolveLatestVersionFromNpm(pkg) {
|
|
|
199
199
|
return execFileSync('npm', ['view', pkg, 'version'], { stdio: 'pipe' }).toString().trim();
|
|
200
200
|
}
|
|
201
201
|
function runRealNpmInstall(projectDir) {
|
|
202
|
-
|
|
202
|
+
// --include=dev: with NODE_ENV=production npm would drop devDependencies,
|
|
203
|
+
// and an older project still lists tsx, which runs it, as one.
|
|
204
|
+
execFileSync('npm', ['install', '--include=dev'], { cwd: projectDir, stdio: 'pipe' });
|
|
203
205
|
}
|
|
204
206
|
// Only loopengine/actauth/skillgarden — the three packages a scaffolded
|
|
205
207
|
// project's own adapters/http.ts can import directly (see this repo's own
|
package/package.json
CHANGED
package/template/_gitignore
CHANGED
|
@@ -129,6 +129,10 @@ import {
|
|
|
129
129
|
replyInSlack,
|
|
130
130
|
slackOutcomeText,
|
|
131
131
|
verifySlackRequest,
|
|
132
|
+
firstSighting,
|
|
133
|
+
handleInboundMessage,
|
|
134
|
+
createSlackConnector,
|
|
135
|
+
parseSlackEvent,
|
|
132
136
|
removeAgentNotifier,
|
|
133
137
|
saveAgentNotifier,
|
|
134
138
|
testAgentNotifier,
|
|
@@ -1568,7 +1572,9 @@ async function handleAbilityInstallDepsPost(req: IncomingMessage, res: ServerRes
|
|
|
1568
1572
|
return
|
|
1569
1573
|
}
|
|
1570
1574
|
try {
|
|
1571
|
-
|
|
1575
|
+
// --include=dev: under NODE_ENV=production npm would otherwise drop
|
|
1576
|
+
// devDependencies, including tsx, which runs this project.
|
|
1577
|
+
await execFileAsync('npm', ['install', '--include=dev', ...deps], { cwd: process.cwd(), timeout: NPM_INSTALL_DEPS_TIMEOUT_MS })
|
|
1572
1578
|
res.writeHead(200, { 'content-type': 'application/json' }).end(JSON.stringify({ installed: deps }))
|
|
1573
1579
|
} catch (err) {
|
|
1574
1580
|
const stderr = err && typeof err === 'object' && 'stderr' in err ? String((err as { stderr: unknown }).stderr) : undefined
|
|
@@ -2664,7 +2670,7 @@ function captureResponse(): { res: ServerResponse; done: Promise<{ status: numbe
|
|
|
2664
2670
|
return { res: res as unknown as ServerResponse, done }
|
|
2665
2671
|
}
|
|
2666
2672
|
|
|
2667
|
-
// A Slack app's Interactivity Request URL (core/slack
|
|
2673
|
+
// A Slack app's Interactivity Request URL (core/messaging/connectors/slack/interactions.ts):
|
|
2668
2674
|
// Approve/Deny, an answer option, or the "Other…" modal, from a Slack
|
|
2669
2675
|
// notifier's message. Outside the admin login on purpose — Slack can't
|
|
2670
2676
|
// send it — and verified with that agent's Slack signing secret instead.
|
|
@@ -2703,6 +2709,52 @@ async function handleSlackInteraction(req: IncomingMessage, res: ServerResponse,
|
|
|
2703
2709
|
}
|
|
2704
2710
|
}
|
|
2705
2711
|
|
|
2712
|
+
// A Slack app's Event Subscriptions Request URL (core/messaging/):
|
|
2713
|
+
// a reply in the thread under an approval request or a question, which
|
|
2714
|
+
// the agent answers about that request. Outside the admin login like the
|
|
2715
|
+
// button endpoint above, and verified with the same signing secret.
|
|
2716
|
+
// Slack's URL check is answered before that, since the app is created
|
|
2717
|
+
// before its signing secret can be saved here; it only echoes Slack's
|
|
2718
|
+
// own challenge back.
|
|
2719
|
+
async function handleSlackEvents(req: IncomingMessage, res: ServerResponse, agentName: string): Promise<void> {
|
|
2720
|
+
const rawBody = await readRawBody(req)
|
|
2721
|
+
const entry = getEntry(agentName)
|
|
2722
|
+
if (!entry) {
|
|
2723
|
+
res.writeHead(404, { 'content-type': 'text/plain' }).end('No such agent.')
|
|
2724
|
+
return
|
|
2725
|
+
}
|
|
2726
|
+
const event = parseSlackEvent(rawBody)
|
|
2727
|
+
if (event.type === 'url_verification') {
|
|
2728
|
+
res.writeHead(200, { 'content-type': 'application/json' }).end(JSON.stringify({ challenge: event.challenge }))
|
|
2729
|
+
return
|
|
2730
|
+
}
|
|
2731
|
+
const { botToken, signingSecret } = slackCredentialsFor(entry.config)
|
|
2732
|
+
if (!signingSecret || !botToken) {
|
|
2733
|
+
res.writeHead(404, { 'content-type': 'text/plain' }).end('No Slack bot token and signing secret are set for this agent.')
|
|
2734
|
+
return
|
|
2735
|
+
}
|
|
2736
|
+
const timestamp = req.headers['x-slack-request-timestamp']
|
|
2737
|
+
const signature = req.headers['x-slack-signature']
|
|
2738
|
+
if (!verifySlackRequest(rawBody, typeof timestamp === 'string' ? timestamp : undefined, typeof signature === 'string' ? signature : undefined, signingSecret)) {
|
|
2739
|
+
res.writeHead(401, { 'content-type': 'text/plain' }).end('Invalid Slack signature.')
|
|
2740
|
+
return
|
|
2741
|
+
}
|
|
2742
|
+
// Slack wants an answer within three seconds; the reply is posted after.
|
|
2743
|
+
res.writeHead(200).end()
|
|
2744
|
+
if (event.type !== 'message' || !firstSighting(event)) return
|
|
2745
|
+
const environment = process.env.LOOPENGINE_ENV ?? 'production'
|
|
2746
|
+
await handleInboundMessage(event, {
|
|
2747
|
+
agentName,
|
|
2748
|
+
agentDir: agentDirFor(entry.config),
|
|
2749
|
+
systemPrompt: entry.config.systemPrompt,
|
|
2750
|
+
connector: createSlackConnector(botToken),
|
|
2751
|
+
createModelCall: () => entry.createModelCall(),
|
|
2752
|
+
getCheckpoint: (pendingId) => checkpoints.get(pendingId),
|
|
2753
|
+
getHistory: (checkpoint) => sessions.getHistory(`${checkpoint.tenant}:${environment}:${checkpoint.agent}:${checkpoint.sessionId}`),
|
|
2754
|
+
plan: process.env.LOOPENGINE_PLAN,
|
|
2755
|
+
})
|
|
2756
|
+
}
|
|
2757
|
+
|
|
2706
2758
|
// Set once shutdown() starts, so /healthz turns 503 while the server
|
|
2707
2759
|
// drains and a router or load balancer stops sending it new requests.
|
|
2708
2760
|
let shuttingDown = false
|
|
@@ -2717,6 +2769,17 @@ const server = createServer(async (req, res) => {
|
|
|
2717
2769
|
return
|
|
2718
2770
|
}
|
|
2719
2771
|
|
|
2772
|
+
const slackEventsMatch = req.method === 'POST' && (req.url ?? '/').split('?')[0].match(/^\/hooks\/slack\/([^/]+)\/events$/)
|
|
2773
|
+
if (slackEventsMatch) {
|
|
2774
|
+
try {
|
|
2775
|
+
await handleSlackEvents(req, res, decodeURIComponent(slackEventsMatch[1]))
|
|
2776
|
+
} catch (err) {
|
|
2777
|
+
console.error('[loopengine] Slack event failed:', err)
|
|
2778
|
+
if (!res.headersSent) res.writeHead(500).end()
|
|
2779
|
+
}
|
|
2780
|
+
return
|
|
2781
|
+
}
|
|
2782
|
+
|
|
2720
2783
|
const slackHookMatch = req.method === 'POST' && (req.url ?? '/').split('?')[0].match(/^\/hooks\/slack\/([^/]+)$/)
|
|
2721
2784
|
if (slackHookMatch) {
|
|
2722
2785
|
try {
|
package/template/package.json
CHANGED
|
@@ -9,12 +9,12 @@
|
|
|
9
9
|
"cli": "tsx adapters/cli.ts"
|
|
10
10
|
},
|
|
11
11
|
"dependencies": {
|
|
12
|
-
"loopengine": "^0.1.
|
|
13
|
-
"actauth": "^0.0.12"
|
|
14
|
-
},
|
|
15
|
-
"devDependencies": {
|
|
16
|
-
"@types/node": "^22.10.0",
|
|
12
|
+
"loopengine": "^0.1.78",
|
|
13
|
+
"actauth": "^0.0.12",
|
|
17
14
|
"tsx": "^4.19.0",
|
|
18
15
|
"typescript": "^5.7.0"
|
|
16
|
+
},
|
|
17
|
+
"devDependencies": {
|
|
18
|
+
"@types/node": "^22.10.0"
|
|
19
19
|
}
|
|
20
20
|
}
|