helloloop 0.9.1 → 0.10.0
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/.claude-plugin/plugin.json +1 -1
- package/README.md +230 -506
- package/hosts/claude/marketplace/plugins/helloloop/.claude-plugin/plugin.json +1 -1
- package/hosts/gemini/extension/gemini-extension.json +1 -1
- package/native/windows-hidden-shell-proxy/HelloLoopHiddenShellProxy.csproj +11 -0
- package/native/windows-hidden-shell-proxy/Program.cs +498 -0
- package/package.json +4 -2
- package/src/activity_projection.mjs +294 -0
- package/src/analyze_confirmation.mjs +3 -1
- package/src/analyzer.mjs +2 -1
- package/src/auto_execution_options.mjs +13 -0
- package/src/background_launch.mjs +73 -0
- package/src/cli.mjs +49 -1
- package/src/cli_analyze_command.mjs +9 -5
- package/src/cli_args.mjs +102 -37
- package/src/cli_command_handlers.mjs +44 -4
- package/src/cli_support.mjs +2 -0
- package/src/dashboard_command.mjs +371 -0
- package/src/dashboard_tui.mjs +289 -0
- package/src/dashboard_web.mjs +351 -0
- package/src/dashboard_web_client.mjs +167 -0
- package/src/dashboard_web_page.mjs +49 -0
- package/src/engine_event_parser_codex.mjs +167 -0
- package/src/engine_process_support.mjs +1 -0
- package/src/engine_selection.mjs +24 -0
- package/src/engine_selection_probe.mjs +10 -6
- package/src/engine_selection_settings.mjs +12 -19
- package/src/execution_interactivity.mjs +12 -0
- package/src/host_continuation.mjs +305 -0
- package/src/install_codex.mjs +20 -8
- package/src/install_shared.mjs +9 -0
- package/src/node_process_launch.mjs +28 -0
- package/src/process.mjs +2 -0
- package/src/runner_execute_task.mjs +4 -0
- package/src/runner_execution_support.mjs +69 -3
- package/src/runner_once.mjs +4 -0
- package/src/runner_status.mjs +63 -7
- package/src/runtime_engine_support.mjs +41 -4
- package/src/runtime_engine_task.mjs +7 -0
- package/src/runtime_settings.mjs +105 -0
- package/src/runtime_settings_loader.mjs +19 -0
- package/src/shell_invocation.mjs +227 -9
- package/src/supervisor_cli_support.mjs +3 -2
- package/src/supervisor_guardian.mjs +307 -0
- package/src/supervisor_runtime.mjs +138 -82
- package/src/supervisor_state.mjs +64 -0
- package/src/supervisor_watch.mjs +92 -48
- package/src/terminal_session_limits.mjs +1 -21
- package/src/windows_hidden_shell_proxy.mjs +405 -0
- package/src/workspace_registry.mjs +155 -0
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
|
|
5
|
+
import { ensureDir, nowIso, readJson, writeJson } from "./common.mjs";
|
|
6
|
+
|
|
7
|
+
const FINAL_SUPERVISOR_STATUSES = new Set(["completed", "failed", "stopped"]);
|
|
8
|
+
|
|
9
|
+
function activeSessionsRoot() {
|
|
10
|
+
return path.join(os.homedir(), ".helloloop", "runtime", "active-sessions");
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function knownWorkspacesRoot() {
|
|
14
|
+
return path.join(os.homedir(), ".helloloop", "runtime", "known-workspaces");
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function readJsonIfExists(filePath) {
|
|
18
|
+
if (!filePath || !fs.existsSync(filePath)) {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
try {
|
|
22
|
+
return readJson(filePath);
|
|
23
|
+
} catch {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function registryFileFor(sessionId) {
|
|
29
|
+
const safeId = String(sessionId || "session")
|
|
30
|
+
.trim()
|
|
31
|
+
.replace(/[^\w.-]+/gu, "_");
|
|
32
|
+
return path.join(activeSessionsRoot(), `${safeId}.json`);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function workspaceFileFor(repoRoot, configDirName = "") {
|
|
36
|
+
const key = `${String(repoRoot || "").trim()}|${String(configDirName || "").trim()}`;
|
|
37
|
+
const safeId = Buffer.from(key).toString("base64url");
|
|
38
|
+
return path.join(knownWorkspacesRoot(), `${safeId}.json`);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function isPidAlive(pid) {
|
|
42
|
+
const numericPid = Number(pid || 0);
|
|
43
|
+
if (!Number.isFinite(numericPid) || numericPid <= 0) {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
try {
|
|
47
|
+
process.kill(numericPid, 0);
|
|
48
|
+
return true;
|
|
49
|
+
} catch (error) {
|
|
50
|
+
return String(error?.code || "") === "EPERM";
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function isStaleEntry(entry) {
|
|
55
|
+
if (!entry?.sessionId) {
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
const supervisor = readJsonIfExists(entry.supervisorStateFile);
|
|
59
|
+
if (!supervisor) {
|
|
60
|
+
return !isPidAlive(entry.pid);
|
|
61
|
+
}
|
|
62
|
+
if (supervisor.sessionId && supervisor.sessionId !== entry.sessionId) {
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
if (FINAL_SUPERVISOR_STATUSES.has(String(supervisor.status || "").trim())) {
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
return !isPidAlive(supervisor.pid || entry.pid);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function registerActiveSession(entry = {}) {
|
|
72
|
+
ensureDir(activeSessionsRoot());
|
|
73
|
+
const filePath = registryFileFor(entry.sessionId);
|
|
74
|
+
const current = readJsonIfExists(filePath) || {};
|
|
75
|
+
writeJson(filePath, {
|
|
76
|
+
schemaVersion: 1,
|
|
77
|
+
...current,
|
|
78
|
+
...entry,
|
|
79
|
+
updatedAt: nowIso(),
|
|
80
|
+
});
|
|
81
|
+
registerKnownWorkspace(entry);
|
|
82
|
+
return filePath;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function unregisterActiveSession(sessionId) {
|
|
86
|
+
const filePath = registryFileFor(sessionId);
|
|
87
|
+
try {
|
|
88
|
+
fs.rmSync(filePath, { force: true });
|
|
89
|
+
} catch {
|
|
90
|
+
// ignore cleanup failures
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function listActiveSessionEntries() {
|
|
95
|
+
ensureDir(activeSessionsRoot());
|
|
96
|
+
const entries = [];
|
|
97
|
+
|
|
98
|
+
for (const item of fs.readdirSync(activeSessionsRoot(), { withFileTypes: true })) {
|
|
99
|
+
if (!item.isFile() || !item.name.endsWith(".json")) {
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
const filePath = path.join(activeSessionsRoot(), item.name);
|
|
103
|
+
const entry = readJsonIfExists(filePath);
|
|
104
|
+
if (!entry) {
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
if (isStaleEntry(entry)) {
|
|
108
|
+
unregisterActiveSession(entry.sessionId);
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
entries.push(entry);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return entries.sort((left, right) => String(right.updatedAt || "").localeCompare(String(left.updatedAt || "")));
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function registerKnownWorkspace(entry = {}) {
|
|
118
|
+
const repoRoot = String(entry.repoRoot || "").trim();
|
|
119
|
+
if (!repoRoot) {
|
|
120
|
+
return "";
|
|
121
|
+
}
|
|
122
|
+
ensureDir(knownWorkspacesRoot());
|
|
123
|
+
const filePath = workspaceFileFor(repoRoot, entry.configDirName);
|
|
124
|
+
const current = readJsonIfExists(filePath) || {};
|
|
125
|
+
writeJson(filePath, {
|
|
126
|
+
schemaVersion: 1,
|
|
127
|
+
...current,
|
|
128
|
+
repoRoot,
|
|
129
|
+
configDirName: String(entry.configDirName || "").trim(),
|
|
130
|
+
sessionId: String(entry.sessionId || current.sessionId || "").trim(),
|
|
131
|
+
command: String(entry.command || current.command || "").trim(),
|
|
132
|
+
lease: entry.lease || current.lease || null,
|
|
133
|
+
updatedAt: nowIso(),
|
|
134
|
+
});
|
|
135
|
+
return filePath;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export function listKnownWorkspaceEntries() {
|
|
139
|
+
ensureDir(knownWorkspacesRoot());
|
|
140
|
+
const entries = [];
|
|
141
|
+
|
|
142
|
+
for (const item of fs.readdirSync(knownWorkspacesRoot(), { withFileTypes: true })) {
|
|
143
|
+
if (!item.isFile() || !item.name.endsWith(".json")) {
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
const filePath = path.join(knownWorkspacesRoot(), item.name);
|
|
147
|
+
const entry = readJsonIfExists(filePath);
|
|
148
|
+
if (!entry?.repoRoot) {
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
entries.push(entry);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return entries.sort((left, right) => String(right.updatedAt || "").localeCompare(String(left.updatedAt || "")));
|
|
155
|
+
}
|