mirror of
https://github.com/garrytan/gstack.git
synced 2026-09-21 04:10:47 +02:00
implement six-skill gstack 2 runtime
This commit is contained in:
+3
-1
@@ -1,5 +1,5 @@
|
||||
---
|
||||
name: browse
|
||||
name: gstack-1-browse
|
||||
preamble-tier: 1
|
||||
version: 1.1.0
|
||||
description: Fast headless browser for QA testing and site dogfooding. (gstack)
|
||||
@@ -12,6 +12,8 @@ allowed-tools:
|
||||
- Read
|
||||
- AskUserQuestion
|
||||
|
||||
metadata:
|
||||
internal: true
|
||||
---
|
||||
<!-- AUTO-GENERATED from SKILL.md.tmpl — do not edit directly -->
|
||||
<!-- Regenerate: bun run gen:skill-docs -->
|
||||
|
||||
@@ -722,7 +722,7 @@ export class BrowserManager {
|
||||
this.consecutiveFailures = 0;
|
||||
}
|
||||
|
||||
async close() {
|
||||
async close(timeoutMs = 5000) {
|
||||
if (this.browser || (this.connectionMode === 'headed' && this.context)) {
|
||||
if (this.connectionMode === 'headed') {
|
||||
// Headed/persistent context mode: close the context (which closes the browser)
|
||||
@@ -730,14 +730,14 @@ export class BrowserManager {
|
||||
if (this.browser) this.browser.removeAllListeners('disconnected');
|
||||
await Promise.race([
|
||||
this.context ? this.context.close() : Promise.resolve(),
|
||||
new Promise(resolve => setTimeout(resolve, 5000)),
|
||||
new Promise(resolve => setTimeout(resolve, timeoutMs)),
|
||||
]).catch(() => {});
|
||||
} else {
|
||||
// Launched mode: close the browser we spawned
|
||||
this.browser.removeAllListeners('disconnected');
|
||||
await Promise.race([
|
||||
this.browser.close(),
|
||||
new Promise(resolve => setTimeout(resolve, 5000)),
|
||||
new Promise(resolve => setTimeout(resolve, timeoutMs)),
|
||||
]).catch(() => {});
|
||||
}
|
||||
this.browser = null;
|
||||
@@ -797,6 +797,11 @@ export class BrowserManager {
|
||||
const tabId = id ?? this.activeTabId;
|
||||
const page = this.pages.get(tabId);
|
||||
if (!page) throw new Error(`Tab ${tabId} not found`);
|
||||
// Capture before page.close(): Playwright may synchronously deliver the
|
||||
// close event, whose map cleanup changes activeTabId to 0. The public
|
||||
// closeTab promise still owns the invariant that closing the active last
|
||||
// tab leaves one usable blank tab.
|
||||
const wasActive = tabId === this.activeTabId;
|
||||
|
||||
await page.close();
|
||||
this.pages.delete(tabId);
|
||||
@@ -804,7 +809,7 @@ export class BrowserManager {
|
||||
this.tabOwnership.delete(tabId);
|
||||
|
||||
// Switch to another tab if we closed the active one
|
||||
if (tabId === this.activeTabId) {
|
||||
if (wasActive) {
|
||||
const remaining = [...this.pages.keys()];
|
||||
if (remaining.length > 0) {
|
||||
this.activeTabId = remaining[remaining.length - 1];
|
||||
@@ -1568,9 +1573,15 @@ export class BrowserManager {
|
||||
console.log('[browse] Handoff: extension not found — headed mode without side panel');
|
||||
}
|
||||
|
||||
const userDataDir = path.join(process.env.HOME || '/tmp', '.gstack', 'chromium-profile');
|
||||
const userDataDir = resolveChromiumProfile();
|
||||
fs.mkdirSync(userDataDir, { recursive: true });
|
||||
|
||||
// The handoff profile follows the same host-neutral resolution and
|
||||
// stale-lock cleanup contract as launchHeaded(). The current browser is
|
||||
// headless and does not own this persistent profile, so cleanup cannot
|
||||
// disrupt the live rollback path retained below.
|
||||
cleanSingletonLocks(userDataDir);
|
||||
|
||||
// T1: same automation-tell-stripping defaults as launchHeaded().
|
||||
// The handoff path (headless → headed re-launch) takes the same
|
||||
// anti-detection posture.
|
||||
|
||||
@@ -10,11 +10,15 @@
|
||||
* 1. Prefer node on PATH + a bundled JS entry at
|
||||
* browse/dist/security-sidecar.js (built by package.json's
|
||||
* build:security-sidecar script).
|
||||
* 2. Dev fallback: node + browse/src/security-sidecar-entry.ts via tsx
|
||||
* (only available in the source checkout, not the compiled install).
|
||||
* 3. If Node is missing or no entry resolves, return null. The /pty-inject-scan
|
||||
* 2. If Node is missing or no compiled entry resolves, return null. The
|
||||
* /pty-inject-scan
|
||||
* endpoint then responds with l4 { available: false } and the extension
|
||||
* degrades to WARN+confirm (D7).
|
||||
*
|
||||
* A plain-Node TypeScript fallback is intentionally not offered. It was not
|
||||
* executable on the supported Node 18 floor and, if partially executed by a
|
||||
* newer Node, could begin downloading local model weights before failing.
|
||||
* GStack 2 does not bundle that model runtime or its weights.
|
||||
*/
|
||||
|
||||
import { existsSync } from "fs";
|
||||
@@ -46,9 +50,6 @@ function browseRoot(): string {
|
||||
if (existsSync(join(candidate, "browse", "dist", "security-sidecar.js"))) {
|
||||
return candidate;
|
||||
}
|
||||
if (existsSync(join(candidate, "src", "security-sidecar-entry.ts"))) {
|
||||
return candidate;
|
||||
}
|
||||
const next = dirname(candidate);
|
||||
if (next === candidate) break;
|
||||
candidate = next;
|
||||
@@ -67,12 +68,5 @@ export function findSecuritySidecar(): SidecarLocation | null {
|
||||
return { node, entry: compiled, mode: "compiled" };
|
||||
}
|
||||
|
||||
// Dev fallback. Compiled installs won't have src/ on disk so this only
|
||||
// resolves when running from the source checkout.
|
||||
const devEntry = join(root, "src", "security-sidecar-entry.ts");
|
||||
if (existsSync(devEntry)) {
|
||||
return { node, entry: devEntry, mode: "dev" };
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -421,14 +421,17 @@ export async function handleMetaCommand(
|
||||
}
|
||||
|
||||
case 'stop': {
|
||||
await shutdown();
|
||||
// Return the acknowledgement before closing the listener. Shutting down
|
||||
// inline resets the CLI's fetch, which it reasonably interprets as a
|
||||
// crash and then restarts the daemon it was asked to stop.
|
||||
setTimeout(() => { void shutdown(); }, 25).unref?.();
|
||||
return 'Server stopped';
|
||||
}
|
||||
|
||||
case 'restart': {
|
||||
// Signal that we want a restart — the CLI will detect exit and restart
|
||||
console.log('[browse] Restart requested. Exiting for CLI to restart.');
|
||||
await shutdown();
|
||||
setTimeout(() => { void shutdown(); }, 25).unref?.();
|
||||
return 'Restarting...';
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user