@slycode/slycode 0.2.3 → 0.2.4
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/bridge/api.js +1 -1
- package/dist/bridge/api.js.map +1 -1
- package/dist/bridge/session-manager.js +54 -14
- package/dist/bridge/session-manager.js.map +1 -1
- package/dist/bridge/types.d.ts +6 -0
- package/dist/web/.next/BUILD_ID +1 -1
- package/dist/web/.next/build-manifest.json +2 -2
- package/dist/web/.next/server/app/_global-error.html +2 -2
- package/dist/web/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/dist/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/.next/server/app/_not-found.html +1 -1
- package/dist/web/.next/server/app/_not-found.rsc +1 -1
- package/dist/web/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/dist/web/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/web/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/dist/web/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dist/web/.next/server/app/api/bridge/[...path]/route.js +1 -1
- package/dist/web/.next/server/app/api/bridge/[...path]/route.js.nft.json +1 -1
- package/dist/web/.next/server/app/api/kanban/route.js +4 -4
- package/dist/web/.next/server/app/api/kanban/route.js.nft.json +1 -1
- package/dist/web/.next/server/app/api/kanban/stream/route.js +1 -1
- package/dist/web/.next/server/app/api/kanban/stream/route.js.nft.json +1 -1
- package/dist/web/.next/server/app/api/scheduler/route.js +1 -1
- package/dist/web/.next/server/app/api/scheduler/route.js.nft.json +1 -1
- package/dist/web/.next/server/app/api/sly-actions/stream/route.js +1 -1
- package/dist/web/.next/server/app/api/sly-actions/stream/route.js.nft.json +1 -1
- package/dist/web/.next/server/chunks/{[externals]__3d917172._.js → [externals]__65615fd8._.js} +2 -2
- package/dist/web/.next/server/chunks/[root-of-the-server]__09aec55a._.js +1 -1
- package/dist/web/.next/server/chunks/[root-of-the-server]__198f01e0._.js +1 -1
- package/dist/web/.next/server/chunks/[root-of-the-server]__2b639eab._.js +1 -1
- package/dist/web/.next/server/chunks/[root-of-the-server]__3b9d3e43._.js +1 -1
- package/dist/web/.next/server/chunks/[root-of-the-server]__3f239285._.js +1 -1
- package/dist/web/.next/server/chunks/[root-of-the-server]__46b023d4._.js +1 -1
- package/dist/web/.next/server/chunks/[root-of-the-server]__4c7995bf._.js +1 -1
- package/dist/web/.next/server/chunks/[root-of-the-server]__4d0d3464._.js +1 -1
- package/dist/web/.next/server/chunks/[root-of-the-server]__6d330d40._.js +1 -1
- package/dist/web/.next/server/chunks/[root-of-the-server]__6ffce934._.js +3 -0
- package/dist/web/.next/server/chunks/[root-of-the-server]__884d73e4._.js +1 -1
- package/dist/web/.next/server/chunks/[root-of-the-server]__8ab096a3._.js +3 -0
- package/dist/web/.next/server/chunks/[root-of-the-server]__9058a007._.js +1 -1
- package/dist/web/.next/server/chunks/[root-of-the-server]__933e6077._.js +2 -2
- package/dist/web/.next/server/chunks/[root-of-the-server]__949bb248._.js +1 -1
- package/dist/web/.next/server/chunks/[root-of-the-server]__aa814a86._.js +1 -1
- package/dist/web/.next/server/chunks/[root-of-the-server]__ad64e04f._.js +2 -2
- package/dist/web/.next/server/chunks/[root-of-the-server]__b90bbd70._.js +1 -1
- package/dist/web/.next/server/chunks/{[root-of-the-server]__4244617a._.js → [root-of-the-server]__b9e7d34c._.js} +4 -4
- package/dist/web/.next/server/chunks/[root-of-the-server]__baa99257._.js +1 -1
- package/dist/web/.next/server/chunks/[root-of-the-server]__bbb4b3ac._.js +1 -1
- package/dist/web/.next/server/chunks/[root-of-the-server]__bc55c42a._.js +2 -2
- package/dist/web/.next/server/chunks/[root-of-the-server]__bf286c26._.js +1 -1
- package/dist/web/.next/server/chunks/[root-of-the-server]__d219e3f0._.js +18 -0
- package/dist/web/.next/server/chunks/[root-of-the-server]__d38c7a96._.js +1 -1
- package/dist/web/.next/server/chunks/[root-of-the-server]__e88a19d2._.js +1 -1
- package/dist/web/.next/server/chunks/[root-of-the-server]__e9b0e744._.js +1 -1
- package/dist/web/.next/server/chunks/[root-of-the-server]__f1fe18e6._.js +2 -2
- package/dist/web/.next/server/chunks/[root-of-the-server]__f4124388._.js +3 -0
- package/dist/web/.next/server/chunks/[root-of-the-server]__f59af2bc._.js +1 -1
- package/dist/web/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_ffc6c790.js +4 -4
- package/dist/web/.next/server/chunks/src_677020aa._.js +1 -1
- package/dist/web/.next/server/chunks/src_lib_scheduler_ts_03988e3e._.js +1 -1
- package/dist/web/.next/server/chunks/src_lib_scheduler_ts_7120457c._.js +1 -1
- package/dist/web/.next/server/chunks/ssr/src_lib_registry_ts_2fc87c9c._.js +1 -1
- package/dist/web/.next/server/pages/404.html +1 -1
- package/dist/web/.next/server/pages/500.html +2 -2
- package/dist/web/src/app/api/bridge/[...path]/route.ts +3 -1
- package/dist/web/src/app/api/dashboard/route.ts +2 -1
- package/dist/web/src/app/api/search/route.ts +2 -1
- package/dist/web/src/app/api/transcribe/route.ts +6 -1
- package/dist/web/src/lib/paths.ts +21 -0
- package/dist/web/src/lib/registry.ts +2 -1
- package/dist/web/src/lib/scheduler.ts +130 -30
- package/dist/web/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/templates/kanban-seed.json +1 -1
- package/dist/web/.next/server/chunks/[root-of-the-server]__289ec56e._.js +0 -3
- package/dist/web/.next/server/chunks/[root-of-the-server]__543058c2._.js +0 -18
- package/dist/web/.next/server/chunks/[root-of-the-server]__949d814c._.js +0 -3
- package/dist/web/.next/server/chunks/[root-of-the-server]__ce429522._.js +0 -3
- /package/dist/web/.next/static/{awhR2yoOiaXMMqO3AHnAQ → sQ8knGS1B967Ng9HqMKOG}/_buildManifest.js +0 -0
- /package/dist/web/.next/static/{awhR2yoOiaXMMqO3AHnAQ → sQ8knGS1B967Ng9HqMKOG}/_clientMiddlewareManifest.json +0 -0
- /package/dist/web/.next/static/{awhR2yoOiaXMMqO3AHnAQ → sQ8knGS1B967Ng9HqMKOG}/_ssgManifest.js +0 -0
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
<!DOCTYPE html><!--
|
|
2
|
-
@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}</style><h1 class="next-error-h1" style="display:inline-block;margin:0 20px 0 0;padding-right:23px;font-size:24px;font-weight:500;vertical-align:top">500</h1><div style="display:inline-block"><h2 style="font-size:14px;font-weight:400;line-height:28px">Internal Server Error.</h2></div></div></div><!--$--><!--/$--><script src="/_next/static/chunks/5f95863347fe47d9.js" id="_R_" async=""></script><script>(self.__next_f=self.__next_f||[]).push([0])</script><script>self.__next_f.push([1,"1:\"$Sreact.fragment\"\n2:I[39756,[\"/_next/static/chunks/ff1a16fafef87110.js\",\"/_next/static/chunks/e24d6768d85df882.js\"],\"default\"]\n3:I[37457,[\"/_next/static/chunks/ff1a16fafef87110.js\",\"/_next/static/chunks/e24d6768d85df882.js\"],\"default\"]\n4:I[97367,[\"/_next/static/chunks/ff1a16fafef87110.js\",\"/_next/static/chunks/e24d6768d85df882.js\"],\"OutletBoundary\"]\n5:\"$Sreact.suspense\"\n7:I[97367,[\"/_next/static/chunks/ff1a16fafef87110.js\",\"/_next/static/chunks/e24d6768d85df882.js\"],\"ViewportBoundary\"]\n9:I[97367,[\"/_next/static/chunks/ff1a16fafef87110.js\",\"/_next/static/chunks/e24d6768d85df882.js\"],\"MetadataBoundary\"]\nb:I[68027,[\"/_next/static/chunks/ff1a16fafef87110.js\",\"/_next/static/chunks/e24d6768d85df882.js\"],\"default\"]\n"])</script><script>self.__next_f.push([1,"0:{\"P\":null,\"b\":\"
|
|
1
|
+
<!DOCTYPE html><!--sQ8knGS1B967Ng9HqMKOG--><html id="__next_error__"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="preload" as="script" fetchPriority="low" href="/_next/static/chunks/5f95863347fe47d9.js"/><script src="/_next/static/chunks/7de9141b1af425c3.js" async=""></script><script src="/_next/static/chunks/ef0dc6461e9250d8.js" async=""></script><script src="/_next/static/chunks/turbopack-4964158913ad5e72.js" async=""></script><script src="/_next/static/chunks/ff1a16fafef87110.js" async=""></script><script src="/_next/static/chunks/e24d6768d85df882.js" async=""></script><meta name="next-size-adjust" content=""/><title>500: Internal Server Error.</title><script src="/_next/static/chunks/a6dad97d9634a72d.js" noModule=""></script></head><body><div hidden=""><!--$--><!--/$--></div><div style="font-family:system-ui,"Segoe UI",Roboto,Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji";height:100vh;text-align:center;display:flex;flex-direction:column;align-items:center;justify-content:center"><div style="line-height:48px"><style>body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}
|
|
2
|
+
@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}</style><h1 class="next-error-h1" style="display:inline-block;margin:0 20px 0 0;padding-right:23px;font-size:24px;font-weight:500;vertical-align:top">500</h1><div style="display:inline-block"><h2 style="font-size:14px;font-weight:400;line-height:28px">Internal Server Error.</h2></div></div></div><!--$--><!--/$--><script src="/_next/static/chunks/5f95863347fe47d9.js" id="_R_" async=""></script><script>(self.__next_f=self.__next_f||[]).push([0])</script><script>self.__next_f.push([1,"1:\"$Sreact.fragment\"\n2:I[39756,[\"/_next/static/chunks/ff1a16fafef87110.js\",\"/_next/static/chunks/e24d6768d85df882.js\"],\"default\"]\n3:I[37457,[\"/_next/static/chunks/ff1a16fafef87110.js\",\"/_next/static/chunks/e24d6768d85df882.js\"],\"default\"]\n4:I[97367,[\"/_next/static/chunks/ff1a16fafef87110.js\",\"/_next/static/chunks/e24d6768d85df882.js\"],\"OutletBoundary\"]\n5:\"$Sreact.suspense\"\n7:I[97367,[\"/_next/static/chunks/ff1a16fafef87110.js\",\"/_next/static/chunks/e24d6768d85df882.js\"],\"ViewportBoundary\"]\n9:I[97367,[\"/_next/static/chunks/ff1a16fafef87110.js\",\"/_next/static/chunks/e24d6768d85df882.js\"],\"MetadataBoundary\"]\nb:I[68027,[\"/_next/static/chunks/ff1a16fafef87110.js\",\"/_next/static/chunks/e24d6768d85df882.js\"],\"default\"]\n"])</script><script>self.__next_f.push([1,"0:{\"P\":null,\"b\":\"sQ8knGS1B967Ng9HqMKOG\",\"c\":[\"\",\"_global-error\"],\"q\":\"\",\"i\":false,\"f\":[[[\"\",{\"children\":[\"__PAGE__\",{}]}],[[\"$\",\"$1\",\"c\",{\"children\":[null,[\"$\",\"$L2\",null,{\"parallelRouterKey\":\"children\",\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$L3\",null,{}],\"templateStyles\":\"$undefined\",\"templateScripts\":\"$undefined\",\"notFound\":\"$undefined\",\"forbidden\":\"$undefined\",\"unauthorized\":\"$undefined\"}]]}],{\"children\":[[\"$\",\"$1\",\"c\",{\"children\":[[\"$\",\"html\",null,{\"id\":\"__next_error__\",\"children\":[[\"$\",\"head\",null,{\"children\":[\"$\",\"title\",null,{\"children\":\"500: Internal Server Error.\"}]}],[\"$\",\"body\",null,{\"children\":[\"$\",\"div\",null,{\"style\":{\"fontFamily\":\"system-ui,\\\"Segoe UI\\\",Roboto,Helvetica,Arial,sans-serif,\\\"Apple Color Emoji\\\",\\\"Segoe UI Emoji\\\"\",\"height\":\"100vh\",\"textAlign\":\"center\",\"display\":\"flex\",\"flexDirection\":\"column\",\"alignItems\":\"center\",\"justifyContent\":\"center\"},\"children\":[\"$\",\"div\",null,{\"style\":{\"lineHeight\":\"48px\"},\"children\":[[\"$\",\"style\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}\\n@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}\"}}],[\"$\",\"h1\",null,{\"className\":\"next-error-h1\",\"style\":{\"display\":\"inline-block\",\"margin\":\"0 20px 0 0\",\"paddingRight\":23,\"fontSize\":24,\"fontWeight\":500,\"verticalAlign\":\"top\"},\"children\":\"500\"}],[\"$\",\"div\",null,{\"style\":{\"display\":\"inline-block\"},\"children\":[\"$\",\"h2\",null,{\"style\":{\"fontSize\":14,\"fontWeight\":400,\"lineHeight\":\"28px\"},\"children\":\"Internal Server Error.\"}]}]]}]}]}]]}],[[\"$\",\"script\",\"script-0\",{\"src\":\"/_next/static/chunks/ff1a16fafef87110.js\",\"async\":true,\"nonce\":\"$undefined\"}],[\"$\",\"script\",\"script-1\",{\"src\":\"/_next/static/chunks/e24d6768d85df882.js\",\"async\":true,\"nonce\":\"$undefined\"}]],[\"$\",\"$L4\",null,{\"children\":[\"$\",\"$5\",null,{\"name\":\"Next.MetadataOutlet\",\"children\":\"$@6\"}]}]]}],{},null,false,false]},null,false,false],[\"$\",\"$1\",\"h\",{\"children\":[null,[\"$\",\"$L7\",null,{\"children\":\"$L8\"}],[\"$\",\"div\",null,{\"hidden\":true,\"children\":[\"$\",\"$L9\",null,{\"children\":[\"$\",\"$5\",null,{\"name\":\"Next.Metadata\",\"children\":\"$La\"}]}]}],[\"$\",\"meta\",null,{\"name\":\"next-size-adjust\",\"content\":\"\"}]]}],false]],\"m\":\"$undefined\",\"G\":[\"$b\",\"$undefined\"],\"S\":true}\n"])</script><script>self.__next_f.push([1,"8:[[\"$\",\"meta\",\"0\",{\"charSet\":\"utf-8\"}],[\"$\",\"meta\",\"1\",{\"name\":\"viewport\",\"content\":\"width=device-width, initial-scale=1\"}]]\n"])</script><script>self.__next_f.push([1,"6:null\na:[]\n"])</script></body></html>
|
|
@@ -3,7 +3,9 @@ import { NextRequest, NextResponse } from 'next/server';
|
|
|
3
3
|
// Prevent Next.js from caching or deduplicating SSE stream requests
|
|
4
4
|
export const dynamic = 'force-dynamic';
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
import { getBridgeUrl } from '@/lib/paths';
|
|
7
|
+
|
|
8
|
+
const BRIDGE_URL = getBridgeUrl();
|
|
7
9
|
|
|
8
10
|
export async function GET(
|
|
9
11
|
request: NextRequest,
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { NextResponse } from 'next/server';
|
|
2
2
|
import { loadDashboardData } from '@/lib/registry';
|
|
3
|
+
import { getBridgeUrl } from '@/lib/paths';
|
|
3
4
|
|
|
4
5
|
export const dynamic = 'force-dynamic';
|
|
5
6
|
|
|
6
|
-
const BRIDGE_URL =
|
|
7
|
+
const BRIDGE_URL = getBridgeUrl();
|
|
7
8
|
|
|
8
9
|
async function getBridgeSessions(): Promise<Record<string, number>> {
|
|
9
10
|
try {
|
|
@@ -12,6 +12,7 @@ import { NextRequest, NextResponse } from 'next/server';
|
|
|
12
12
|
import { promises as fs } from 'fs';
|
|
13
13
|
import path from 'path';
|
|
14
14
|
import { loadRegistry } from '@/lib/registry';
|
|
15
|
+
import { getBridgeUrl } from '@/lib/paths';
|
|
15
16
|
import type { KanbanBoard, KanbanCard, KanbanStage, SearchResult } from '@/lib/types';
|
|
16
17
|
|
|
17
18
|
export const dynamic = 'force-dynamic';
|
|
@@ -112,7 +113,7 @@ function searchCard(
|
|
|
112
113
|
* Returns cards that are currently being worked on (terminal output in last ~3s).
|
|
113
114
|
*/
|
|
114
115
|
async function getActiveSessionCards(registry: { projects: { id: string; name: string; path: string }[] }): Promise<SearchResult[]> {
|
|
115
|
-
const bridgeUrl =
|
|
116
|
+
const bridgeUrl = getBridgeUrl();
|
|
116
117
|
const results: SearchResult[] = [];
|
|
117
118
|
|
|
118
119
|
try {
|
|
@@ -19,7 +19,12 @@ async function loadEnv(): Promise<Record<string, string>> {
|
|
|
19
19
|
const match = line.match(/^([A-Z_]+)=(.+)$/);
|
|
20
20
|
if (match) {
|
|
21
21
|
envCache[match[1]] = match[2].trim();
|
|
22
|
-
|
|
22
|
+
// Don't leak port-specific vars into process.env — other modules
|
|
23
|
+
// (scheduler, bridge proxy) need their own port resolution logic.
|
|
24
|
+
const skipKeys = new Set(['BRIDGE_URL', 'BRIDGE_PORT', 'WEB_PORT', 'MESSAGING_SERVICE_PORT']);
|
|
25
|
+
if (!skipKeys.has(match[1]) && !process.env[match[1]]) {
|
|
26
|
+
process.env[match[1]] = envCache[match[1]];
|
|
27
|
+
}
|
|
23
28
|
}
|
|
24
29
|
}
|
|
25
30
|
} catch { /* no .env file */ }
|
|
@@ -64,3 +64,24 @@ export function getProjectPath(projectId: string, projectPath?: string): string
|
|
|
64
64
|
if (projectPath) return projectPath;
|
|
65
65
|
return path.join(getProjectsDir(), projectId.replace(/-/g, '_'));
|
|
66
66
|
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Get the bridge URL for server-side HTTP calls.
|
|
70
|
+
*
|
|
71
|
+
* Single source of truth. All server-side code that talks to the bridge
|
|
72
|
+
* should call this instead of reading process.env.BRIDGE_URL directly.
|
|
73
|
+
*
|
|
74
|
+
* In production, startup scripts (sly-start.sh, systemd, slycode CLI)
|
|
75
|
+
* set BRIDGE_URL in the shell environment before starting the web server.
|
|
76
|
+
*
|
|
77
|
+
* In dev, BRIDGE_URL is NOT set in the shell — the bridge runs on its
|
|
78
|
+
* hardcoded default (3004). But the parent .env has BRIDGE_URL=...7592
|
|
79
|
+
* for prod, and various .env loaders can leak that into process.env.
|
|
80
|
+
* So in dev we ignore process.env.BRIDGE_URL and use the known default.
|
|
81
|
+
*/
|
|
82
|
+
export function getBridgeUrl(): string {
|
|
83
|
+
if (process.env.NODE_ENV === 'production' && process.env.BRIDGE_URL) {
|
|
84
|
+
return process.env.BRIDGE_URL;
|
|
85
|
+
}
|
|
86
|
+
return 'http://127.0.0.1:3004';
|
|
87
|
+
}
|
|
@@ -29,6 +29,7 @@ import {
|
|
|
29
29
|
} from './asset-scanner';
|
|
30
30
|
import { getStoreAssets } from './store-scanner';
|
|
31
31
|
import { calculateHealthFromAssets } from './health-score';
|
|
32
|
+
import { getBridgeUrl } from './paths';
|
|
32
33
|
|
|
33
34
|
// Path to the registry file
|
|
34
35
|
// Resolution: SLYCODE_HOME → derive from cwd
|
|
@@ -306,7 +307,7 @@ export async function loadDashboardData(): Promise<DashboardData> {
|
|
|
306
307
|
}, 0);
|
|
307
308
|
|
|
308
309
|
// Fetch bridge session counts (best-effort, don't fail if bridge is down)
|
|
309
|
-
const bridgeUrl =
|
|
310
|
+
const bridgeUrl = getBridgeUrl();
|
|
310
311
|
try {
|
|
311
312
|
const resp = await fetch(`${bridgeUrl}/stats`, { signal: AbortSignal.timeout(2000) });
|
|
312
313
|
if (resp.ok) {
|
|
@@ -12,23 +12,17 @@
|
|
|
12
12
|
import { promises as fs } from 'fs';
|
|
13
13
|
import { readFileSync } from 'fs';
|
|
14
14
|
import path from 'path';
|
|
15
|
+
import os from 'os';
|
|
15
16
|
import { Cron } from 'croner';
|
|
16
17
|
import type { KanbanCard, KanbanBoard, AutomationConfig } from './types';
|
|
17
18
|
import { loadRegistry } from './registry';
|
|
18
19
|
import { cronToHumanReadable } from './cron-utils';
|
|
19
|
-
import { getSlycodeRoot } from './paths';
|
|
20
|
+
import { getSlycodeRoot, getBridgeUrl } from './paths';
|
|
20
21
|
|
|
21
22
|
/**
|
|
22
23
|
* Load env vars from the project root .env file if not already set.
|
|
23
24
|
* Next.js only auto-loads .env from web/, but our config lives in the parent.
|
|
24
|
-
*
|
|
25
|
-
* BRIDGE_URL and BRIDGE_PORT are excluded — these are port-dependent and differ
|
|
26
|
-
* between dev (3004) and prod (7592). In prod, sly-start.sh sets BRIDGE_URL in
|
|
27
|
-
* the shell environment. In dev, we fall back to localhost:3004. Loading them
|
|
28
|
-
* from .env would route dev scheduler requests to the prod bridge.
|
|
29
25
|
*/
|
|
30
|
-
const SKIP_ENV_KEYS = new Set(['BRIDGE_URL', 'BRIDGE_PORT']);
|
|
31
|
-
|
|
32
26
|
function loadParentEnv() {
|
|
33
27
|
try {
|
|
34
28
|
const envPath = path.join(getSlycodeRoot(), '.env');
|
|
@@ -40,7 +34,6 @@ function loadParentEnv() {
|
|
|
40
34
|
if (eqIdx < 0) continue;
|
|
41
35
|
const key = trimmed.slice(0, eqIdx).trim();
|
|
42
36
|
const value = trimmed.slice(eqIdx + 1).trim();
|
|
43
|
-
if (SKIP_ENV_KEYS.has(key)) continue;
|
|
44
37
|
if (!process.env[key]) {
|
|
45
38
|
process.env[key] = value;
|
|
46
39
|
}
|
|
@@ -50,12 +43,15 @@ function loadParentEnv() {
|
|
|
50
43
|
|
|
51
44
|
loadParentEnv();
|
|
52
45
|
|
|
53
|
-
const BRIDGE_URL =
|
|
46
|
+
const BRIDGE_URL = getBridgeUrl();
|
|
54
47
|
const CONFIGURED_TIMEZONE = process.env.TZ || 'UTC';
|
|
55
48
|
const CHECK_INTERVAL_MS = 30_000; // Check every 30 seconds
|
|
56
49
|
const GRACE_WINDOW_MS = 60_000; // Only catch up ticks missed within this window
|
|
57
50
|
const FETCH_TIMEOUT_MS = 10_000; // Timeout for bridge HTTP calls
|
|
58
51
|
|
|
52
|
+
const AUTOMATION_LOG_PATH = path.join(os.homedir(), '.slycode', 'logs', 'automation.log');
|
|
53
|
+
const AUTOMATION_LOG_MAX_BYTES = 1_000_000; // 1MB cap
|
|
54
|
+
|
|
59
55
|
// Fresh session path: simple liveness check after startup
|
|
60
56
|
const LIVENESS_CHECK_MS = 20_000; // Wait 20s then check if session is alive
|
|
61
57
|
|
|
@@ -77,6 +73,59 @@ async function fetchWithTimeout(url: string, opts?: RequestInit): Promise<Respon
|
|
|
77
73
|
}
|
|
78
74
|
}
|
|
79
75
|
|
|
76
|
+
/**
|
|
77
|
+
* Automation run log entry — one per automation execution.
|
|
78
|
+
*/
|
|
79
|
+
interface AutomationLogEntry {
|
|
80
|
+
timestamp: string;
|
|
81
|
+
cardId: string;
|
|
82
|
+
cardTitle: string;
|
|
83
|
+
projectId: string;
|
|
84
|
+
trigger: 'scheduled' | 'manual';
|
|
85
|
+
provider: string;
|
|
86
|
+
sessionName: string;
|
|
87
|
+
fresh: boolean;
|
|
88
|
+
bridgeRequest: { status: number; resumed?: boolean; pid?: number; error?: string } | null;
|
|
89
|
+
livenessCheck: { type: string; result: string; delayMs?: number; exitCode?: number; exitedAt?: string } | null;
|
|
90
|
+
outcome: 'success' | 'error';
|
|
91
|
+
error: string | null;
|
|
92
|
+
elapsedMs: number;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Append a JSON lines entry to the automation log.
|
|
97
|
+
* Rotates by dropping the oldest half when the file exceeds 1MB.
|
|
98
|
+
*/
|
|
99
|
+
async function writeAutomationLog(entry: AutomationLogEntry): Promise<void> {
|
|
100
|
+
try {
|
|
101
|
+
const dir = path.dirname(AUTOMATION_LOG_PATH);
|
|
102
|
+
await fs.mkdir(dir, { recursive: true });
|
|
103
|
+
|
|
104
|
+
const line = JSON.stringify(entry) + '\n';
|
|
105
|
+
await fs.appendFile(AUTOMATION_LOG_PATH, line);
|
|
106
|
+
|
|
107
|
+
// Check size and rotate if needed
|
|
108
|
+
try {
|
|
109
|
+
const stat = await fs.stat(AUTOMATION_LOG_PATH);
|
|
110
|
+
if (stat.size > AUTOMATION_LOG_MAX_BYTES) {
|
|
111
|
+
const content = await fs.readFile(AUTOMATION_LOG_PATH, 'utf-8');
|
|
112
|
+
const lines = content.trim().split('\n');
|
|
113
|
+
// Keep the newest half
|
|
114
|
+
const keep = lines.slice(Math.floor(lines.length / 2));
|
|
115
|
+
await fs.writeFile(AUTOMATION_LOG_PATH, keep.join('\n') + '\n');
|
|
116
|
+
}
|
|
117
|
+
} catch { /* rotation is best-effort */ }
|
|
118
|
+
} catch (err) {
|
|
119
|
+
console.error('[scheduler] Failed to write automation log:', err);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
interface LivenessResult {
|
|
124
|
+
status: 'running' | 'stopped' | 'unknown';
|
|
125
|
+
exitCode?: number;
|
|
126
|
+
exitedAt?: string;
|
|
127
|
+
}
|
|
128
|
+
|
|
80
129
|
/**
|
|
81
130
|
* Check if a session is alive after startup (used for fresh sessions).
|
|
82
131
|
*
|
|
@@ -84,17 +133,17 @@ async function fetchWithTimeout(url: string, opts?: RequestInit): Promise<Respon
|
|
|
84
133
|
* don't need to verify prompt delivery. We just need to confirm the session
|
|
85
134
|
* didn't crash during startup (e.g. auth failure, invalid config).
|
|
86
135
|
*/
|
|
87
|
-
async function checkSessionAlive(sessionName: string): Promise<
|
|
136
|
+
async function checkSessionAlive(sessionName: string): Promise<LivenessResult> {
|
|
88
137
|
await new Promise(r => setTimeout(r, LIVENESS_CHECK_MS));
|
|
89
138
|
try {
|
|
90
139
|
const res = await fetchWithTimeout(`${BRIDGE_URL}/sessions/${encodeURIComponent(sessionName)}`);
|
|
91
|
-
if (!res.ok) return 'unknown';
|
|
140
|
+
if (!res.ok) return { status: 'unknown' };
|
|
92
141
|
const data = await res.json();
|
|
93
|
-
if (data.status === 'stopped') return 'stopped';
|
|
94
|
-
if (data.status === 'running' || data.status === 'detached') return 'running';
|
|
95
|
-
return 'unknown';
|
|
142
|
+
if (data.status === 'stopped') return { status: 'stopped', exitCode: data.exitCode, exitedAt: data.exitedAt };
|
|
143
|
+
if (data.status === 'running' || data.status === 'detached') return { status: 'running' };
|
|
144
|
+
return { status: 'unknown' };
|
|
96
145
|
} catch {
|
|
97
|
-
return 'unknown';
|
|
146
|
+
return { status: 'unknown' };
|
|
98
147
|
}
|
|
99
148
|
}
|
|
100
149
|
|
|
@@ -357,6 +406,30 @@ export async function triggerAutomation(
|
|
|
357
406
|
}
|
|
358
407
|
|
|
359
408
|
const isFresh = config.freshSession || false;
|
|
409
|
+
const startTime = Date.now();
|
|
410
|
+
|
|
411
|
+
// Tracking for automation log
|
|
412
|
+
let bridgeRequestInfo: AutomationLogEntry['bridgeRequest'] = null;
|
|
413
|
+
let livenessInfo: AutomationLogEntry['livenessCheck'] = null;
|
|
414
|
+
|
|
415
|
+
const logAndReturn = async (result: KickoffResult): Promise<KickoffResult> => {
|
|
416
|
+
await writeAutomationLog({
|
|
417
|
+
timestamp: new Date().toISOString(),
|
|
418
|
+
cardId: card.id,
|
|
419
|
+
cardTitle: card.title,
|
|
420
|
+
projectId,
|
|
421
|
+
trigger: options.trigger,
|
|
422
|
+
provider,
|
|
423
|
+
sessionName,
|
|
424
|
+
fresh: isFresh,
|
|
425
|
+
bridgeRequest: bridgeRequestInfo,
|
|
426
|
+
livenessCheck: livenessInfo,
|
|
427
|
+
outcome: result.success ? 'success' : 'error',
|
|
428
|
+
error: result.error || null,
|
|
429
|
+
elapsedMs: Date.now() - startTime,
|
|
430
|
+
});
|
|
431
|
+
return result;
|
|
432
|
+
};
|
|
360
433
|
|
|
361
434
|
try {
|
|
362
435
|
console.log(`[scheduler] Creating session: ${sessionName} (fresh: ${isFresh}, provider: ${provider})`);
|
|
@@ -379,6 +452,7 @@ export async function triggerAutomation(
|
|
|
379
452
|
|
|
380
453
|
if (!createRes.ok && createRes.status === 409 && !isFresh) {
|
|
381
454
|
// Session exists and not fresh — try sending input directly (resume fallback)
|
|
455
|
+
bridgeRequestInfo = { status: 409 };
|
|
382
456
|
console.log(`[scheduler] Session ${sessionName} returned 409, sending prompt via input endpoint`);
|
|
383
457
|
const inputRes = await fetchWithTimeout(`${BRIDGE_URL}/sessions/${encodeURIComponent(sessionName)}/input`, {
|
|
384
458
|
method: 'POST',
|
|
@@ -386,12 +460,22 @@ export async function triggerAutomation(
|
|
|
386
460
|
body: JSON.stringify({ data: fullPrompt + '\r' }),
|
|
387
461
|
});
|
|
388
462
|
if (!inputRes.ok) {
|
|
389
|
-
|
|
463
|
+
const body = await inputRes.text();
|
|
464
|
+
return logAndReturn({ cardId: card.id, projectId, success: false, error: `Input failed (${inputRes.status}): ${body}` });
|
|
390
465
|
}
|
|
391
466
|
} else if (!createRes.ok) {
|
|
392
|
-
|
|
467
|
+
let errorDetail: string;
|
|
468
|
+
try {
|
|
469
|
+
const body = await createRes.json();
|
|
470
|
+
errorDetail = body.error || JSON.stringify(body);
|
|
471
|
+
} catch {
|
|
472
|
+
errorDetail = await createRes.text();
|
|
473
|
+
}
|
|
474
|
+
bridgeRequestInfo = { status: createRes.status, error: errorDetail };
|
|
475
|
+
return logAndReturn({ cardId: card.id, projectId, success: false, error: `Session create failed (${createRes.status}): ${errorDetail}` });
|
|
393
476
|
} else {
|
|
394
477
|
const createData = await createRes.json();
|
|
478
|
+
bridgeRequestInfo = { status: createRes.status, resumed: createData.resumed, pid: createData.pid };
|
|
395
479
|
console.log(`[scheduler] Session created: ${sessionName} (status: ${createData.status}, resumed: ${createData.resumed}, pid: ${createData.pid})`);
|
|
396
480
|
}
|
|
397
481
|
|
|
@@ -399,12 +483,20 @@ export async function triggerAutomation(
|
|
|
399
483
|
// Prompt was delivered via CLI args. Just verify the session didn't crash.
|
|
400
484
|
// No retry needed — OS guarantees prompt delivery.
|
|
401
485
|
if (isFresh) {
|
|
402
|
-
const
|
|
403
|
-
|
|
404
|
-
|
|
486
|
+
const liveness = await checkSessionAlive(sessionName);
|
|
487
|
+
livenessInfo = { type: 'checkSessionAlive', result: liveness.status, delayMs: LIVENESS_CHECK_MS, exitCode: liveness.exitCode, exitedAt: liveness.exitedAt };
|
|
488
|
+
if (liveness.status === 'stopped') {
|
|
489
|
+
// Exit code 0 means the session completed normally — not a failure.
|
|
490
|
+
// Fast automations can finish within the liveness check window.
|
|
491
|
+
if (liveness.exitCode === 0) {
|
|
492
|
+
return logAndReturn({ cardId: card.id, projectId, success: true, sessionName });
|
|
493
|
+
}
|
|
494
|
+
const exitDetail = liveness.exitCode !== undefined ? ` (exit code ${liveness.exitCode})` : '';
|
|
495
|
+
const aliveDetail = liveness.exitedAt ? `, alive ${((new Date(liveness.exitedAt).getTime() - startTime) / 1000).toFixed(1)}s` : '';
|
|
496
|
+
return logAndReturn({ cardId: card.id, projectId, success: false, sessionName, error: `Session stopped during startup${exitDetail}${aliveDetail}` });
|
|
405
497
|
}
|
|
406
498
|
// 'running' or 'unknown' — session is alive (or bridge is slow). Either way, prompt was delivered.
|
|
407
|
-
return { cardId: card.id, projectId, success: true, sessionName };
|
|
499
|
+
return logAndReturn({ cardId: card.id, projectId, success: true, sessionName });
|
|
408
500
|
}
|
|
409
501
|
|
|
410
502
|
// --- Resume session path ---
|
|
@@ -412,7 +504,8 @@ export async function triggerAutomation(
|
|
|
412
504
|
// so we check for activity and retry if needed.
|
|
413
505
|
const active = await waitForActivity(sessionName);
|
|
414
506
|
if (active) {
|
|
415
|
-
|
|
507
|
+
livenessInfo = { type: 'waitForActivity', result: 'active' };
|
|
508
|
+
return logAndReturn({ cardId: card.id, projectId, success: true, sessionName });
|
|
416
509
|
}
|
|
417
510
|
|
|
418
511
|
// No activity detected — retry by re-sending prompt via bracketed paste
|
|
@@ -436,13 +529,15 @@ export async function triggerAutomation(
|
|
|
436
529
|
|
|
437
530
|
const retryActive = await waitForActivity(sessionName);
|
|
438
531
|
if (retryActive) {
|
|
439
|
-
|
|
532
|
+
livenessInfo = { type: 'waitForActivity', result: 'active (retry)' };
|
|
533
|
+
return logAndReturn({ cardId: card.id, projectId, success: true, sessionName });
|
|
440
534
|
}
|
|
441
535
|
}
|
|
442
536
|
|
|
443
|
-
|
|
537
|
+
livenessInfo = { type: 'waitForActivity', result: 'inactive after retry' };
|
|
538
|
+
return logAndReturn({ cardId: card.id, projectId, success: false, sessionName, error: 'No activity detected after retry' });
|
|
444
539
|
} catch (err) {
|
|
445
|
-
return { cardId: card.id, projectId, success: false, sessionName, error: (err as Error).message };
|
|
540
|
+
return logAndReturn({ cardId: card.id, projectId, success: false, sessionName, error: (err as Error).message });
|
|
446
541
|
}
|
|
447
542
|
}
|
|
448
543
|
|
|
@@ -478,12 +573,17 @@ export async function updateCardAutomation(
|
|
|
478
573
|
}
|
|
479
574
|
|
|
480
575
|
/**
|
|
481
|
-
* Send error notification via messaging
|
|
576
|
+
* Send error notification via messaging with actionable detail.
|
|
482
577
|
*/
|
|
483
|
-
async function sendErrorNotification(cardTitle: string, error: string): Promise<void> {
|
|
578
|
+
async function sendErrorNotification(cardTitle: string, error: string, sessionName?: string): Promise<void> {
|
|
484
579
|
try {
|
|
485
580
|
const { execSync } = await import('child_process');
|
|
486
|
-
|
|
581
|
+
const lines = [`Automation failed: ${cardTitle}`];
|
|
582
|
+
if (sessionName) lines.push(`Session: ${sessionName}`);
|
|
583
|
+
lines.push(`Error: ${error}`);
|
|
584
|
+
lines.push(`Log: ~/.slycode/logs/automation.log`);
|
|
585
|
+
const msg = lines.join('\n').replace(/"/g, '\\"');
|
|
586
|
+
execSync(`sly-messaging send "${msg}"`, {
|
|
487
587
|
timeout: 10_000,
|
|
488
588
|
stdio: 'pipe',
|
|
489
589
|
});
|
|
@@ -560,7 +660,7 @@ async function checkAutomations(): Promise<void> {
|
|
|
560
660
|
result.error.includes('No automation config')
|
|
561
661
|
);
|
|
562
662
|
if (isHardFailure) {
|
|
563
|
-
await sendErrorNotification(card.title, result.error || 'Unknown error');
|
|
663
|
+
await sendErrorNotification(card.title, result.error || 'Unknown error', result.sessionName);
|
|
564
664
|
} else {
|
|
565
665
|
console.log(`[scheduler] Soft failure for ${card.id}, skipping notification: ${result.error}`);
|
|
566
666
|
}
|