glad-web 1.0.18 → 1.0.20

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.
@@ -33,11 +33,16 @@ function sendCompressedJson(req, res, payload) {
33
33
  res.type('application/json').send(body);
34
34
  }
35
35
 
36
+ function getSessionWorkingDirectory(session) {
37
+ return session.workingDir || (session.ptyManager && session.ptyManager.workingDir) || process.cwd();
38
+ }
39
+
36
40
  const { detectInstalledTools } = require('../ai-tools/detector');
37
41
  const logger = require('../utils/logger');
38
42
  const { JobStore } = require('../schedule/job-store');
39
43
  const JobRunner = require('../schedule/job-runner');
40
44
  const SchedulerService = require('../schedule/scheduler-service');
45
+ const { getClaudeRuntimeConfig } = require('../claude/config');
41
46
 
42
47
  async function webCommand(options) {
43
48
  const port = parseInt(options.port) || 3000;
@@ -85,6 +90,9 @@ async function webCommand(options) {
85
90
  sessionManager.on('output', ({ sessionId, data }) => {
86
91
  broadcastToSession(sessionId, { type: 'output', data });
87
92
  });
93
+ sessionManager.on('claude-event', ({ sessionId, event }) => {
94
+ broadcastToSession(sessionId, { type: 'claude-event', event });
95
+ });
88
96
  sessionManager.on('exit', ({ sessionId }) => {
89
97
  broadcastToSession(sessionId, { type: 'exit' });
90
98
  });
@@ -179,12 +187,16 @@ async function webCommand(options) {
179
187
  res.json(sessionManager.list());
180
188
  });
181
189
 
190
+ app.get('/api/claude-config', (req, res) => {
191
+ res.json({ success: true, config: getClaudeRuntimeConfig(process.env) });
192
+ });
193
+
182
194
  // API: Create a new PTY session
183
195
  app.post('/api/sessions', async (req, res) => {
184
196
  logger.debug(`API: POST /api/sessions - ${JSON.stringify(req.body)}`);
185
197
  try {
186
- const { toolKey, workingDirectory } = req.body;
187
- const session = sessionManager.create({ toolKey, workingDirectory });
198
+ const { toolKey, workingDirectory, claudeOptions } = req.body;
199
+ const session = sessionManager.create({ toolKey, workingDirectory, claudeOptions });
188
200
  res.json({ id: session.id });
189
201
  } catch (e) {
190
202
  logger.error(`API: POST /api/sessions failed: ${e.message}`);
@@ -217,6 +229,40 @@ async function webCommand(options) {
217
229
  res.json({ success: true });
218
230
  });
219
231
 
232
+ app.get('/api/sessions/:id/timed-inputs', (req, res) => {
233
+ const items = sessionManager.listTimedInputs(req.params.id);
234
+ if (!items) return res.status(404).json({ error: 'Session not found' });
235
+ res.json({ success: true, items });
236
+ });
237
+
238
+ app.post('/api/sessions/:id/timed-inputs', (req, res) => {
239
+ try {
240
+ const item = sessionManager.scheduleTimedInput(req.params.id, req.body || {});
241
+ if (!item) return res.status(404).json({ error: 'Session not found' });
242
+ res.json({ success: true, item });
243
+ } catch (e) {
244
+ res.status(e.statusCode || 500).json({ error: e.message });
245
+ }
246
+ });
247
+
248
+ app.patch('/api/sessions/:id/timed-inputs/:inputId', (req, res) => {
249
+ try {
250
+ const item = sessionManager.updateTimedInput(req.params.id, req.params.inputId, req.body || {});
251
+ if (item === null) return res.status(404).json({ error: 'Session not found' });
252
+ if (!item) return res.status(404).json({ error: 'Timed input not found' });
253
+ res.json({ success: true, item });
254
+ } catch (e) {
255
+ res.status(e.statusCode || 500).json({ error: e.message });
256
+ }
257
+ });
258
+
259
+ app.delete('/api/sessions/:id/timed-inputs/:inputId', (req, res) => {
260
+ const cancelled = sessionManager.cancelTimedInput(req.params.id, req.params.inputId);
261
+ if (cancelled === null) return res.status(404).json({ error: 'Session not found' });
262
+ if (!cancelled) return res.status(404).json({ error: 'Timed input not found' });
263
+ res.json({ success: true });
264
+ });
265
+
220
266
  // API: Delete/Kill session
221
267
  app.delete('/api/sessions/:id', (req, res) => {
222
268
  sessionManager.kill(req.params.id);
@@ -229,6 +275,32 @@ async function webCommand(options) {
229
275
  res.json({ success: true, diagnostics });
230
276
  });
231
277
 
278
+ app.get('/api/sessions/:id/claude-resume-sessions', (req, res) => {
279
+ const items = sessionManager.listClaudeResumeSessions(req.params.id);
280
+ if (!items) return res.status(404).json({ error: 'Claude session not found' });
281
+ res.json({ success: true, items });
282
+ });
283
+
284
+ app.patch('/api/sessions/:id/claude-settings', (req, res) => {
285
+ const state = sessionManager.updateClaudeSettings(req.params.id, req.body || {});
286
+ if (!state) return res.status(404).json({ error: 'Claude session not found' });
287
+ res.json({ success: true, state });
288
+ });
289
+
290
+ app.post('/api/sessions/:id/claude-abort', (req, res) => {
291
+ const success = sessionManager.abortClaude(req.params.id);
292
+ if (!success) return res.status(404).json({ error: 'Claude session not found or idle' });
293
+ res.json({ success: true });
294
+ });
295
+
296
+ app.post('/api/sessions/:id/claude-resume', (req, res) => {
297
+ const resumeSessionId = req.body && req.body.resumeSessionId;
298
+ if (!resumeSessionId) return res.status(400).json({ error: 'Missing resumeSessionId' });
299
+ const success = sessionManager.resumeClaude(req.params.id, resumeSessionId);
300
+ if (!success) return res.status(404).json({ error: 'Claude session not found' });
301
+ res.json({ success: true });
302
+ });
303
+
232
304
  app.post('/api/debug/client-log', (req, res) => {
233
305
  const { sessionId, event, payload } = req.body || {};
234
306
  sessionManager.logClientDebug(sessionId, event, payload);
@@ -240,7 +312,7 @@ async function webCommand(options) {
240
312
  const session = sessionManager.get(req.params.id);
241
313
  if (!session) return res.status(404).json({ error: 'Session not found' });
242
314
  const hash = req.params.hash;
243
- const result = await gitService.show(session.ptyManager.workingDir, hash);
315
+ const result = await gitService.show(getSessionWorkingDirectory(session), hash);
244
316
  res.json({ success: result.success, stdout: result.stdout, stderr: result.stderr });
245
317
  });
246
318
 
@@ -249,7 +321,7 @@ async function webCommand(options) {
249
321
  const session = sessionManager.get(req.params.id);
250
322
  if (!session) return res.status(404).json({ error: 'Session not found' });
251
323
  const hash = req.params.hash;
252
- const result = await gitService.nameRev(session.ptyManager.workingDir, hash);
324
+ const result = await gitService.nameRev(getSessionWorkingDirectory(session), hash);
253
325
  res.json({ success: result.success, stdout: result.stdout, stderr: result.stderr });
254
326
  });
255
327
 
@@ -257,7 +329,7 @@ async function webCommand(options) {
257
329
  app.get('/api/sessions/:id/git-log', async (req, res) => {
258
330
  const session = sessionManager.get(req.params.id);
259
331
  if (!session) return res.status(404).json({ error: 'Session not found' });
260
- const result = await gitService.log(session.ptyManager.workingDir, req.query.maxCount);
332
+ const result = await gitService.log(getSessionWorkingDirectory(session), req.query.maxCount);
261
333
  if (!result.success) {
262
334
  return res.status(500).json({ error: result.error, stderr: result.stderr });
263
335
  }
@@ -268,7 +340,7 @@ async function webCommand(options) {
268
340
  app.get('/api/sessions/:id/git-status', async (req, res) => {
269
341
  const session = sessionManager.get(req.params.id);
270
342
  if (!session) return res.status(404).json({ error: 'Session not found' });
271
- const result = await gitService.status(session.ptyManager.workingDir);
343
+ const result = await gitService.status(getSessionWorkingDirectory(session));
272
344
  if (!result.success) {
273
345
  return res.status(500).json({ error: result.error, stderr: result.stderr });
274
346
  }
@@ -280,7 +352,7 @@ async function webCommand(options) {
280
352
  const session = sessionManager.get(req.params.id);
281
353
  if (!session) return res.status(404).json({ error: 'Session not found' });
282
354
  const isStaged = req.query.staged === 'true';
283
- const result = await gitService.diffNumstat(session.ptyManager.workingDir, isStaged);
355
+ const result = await gitService.diffNumstat(getSessionWorkingDirectory(session), isStaged);
284
356
  res.json({ success: result.success, stdout: result.stdout, stderr: result.stderr });
285
357
  });
286
358
 
@@ -291,7 +363,7 @@ async function webCommand(options) {
291
363
  const isStaged = req.query.staged === 'true';
292
364
  const filePath = req.query.path;
293
365
  if (!filePath) return res.status(400).json({ error: 'Missing file path' });
294
- const result = await gitService.diffFile(session.ptyManager.workingDir, filePath, isStaged);
366
+ const result = await gitService.diffFile(getSessionWorkingDirectory(session), filePath, isStaged);
295
367
  res.json({ success: result.success, stdout: result.stdout, stderr: result.stderr });
296
368
  });
297
369
 
@@ -301,7 +373,7 @@ async function webCommand(options) {
301
373
  if (!session) return res.status(404).json({ error: 'Session not found' });
302
374
  const filePath = req.query.path || '';
303
375
  if (!filePath) return res.status(400).json({ error: 'Missing file path' });
304
- const cwd = session.ptyManager.workingDir || '';
376
+ const cwd = getSessionWorkingDirectory(session);
305
377
  try {
306
378
  const content = workspaceService.readFile(cwd, filePath);
307
379
  res.json({ success: true, content });
@@ -315,7 +387,7 @@ async function webCommand(options) {
315
387
  const session = sessionManager.get(req.params.id);
316
388
  if (!session) return res.status(404).json({ error: 'Session not found' });
317
389
  const dirPath = req.query.path || '';
318
- const cwd = session.ptyManager.workingDir || '';
390
+ const cwd = getSessionWorkingDirectory(session);
319
391
  try {
320
392
  const files = await workspaceService.listDirectory(cwd, dirPath);
321
393
  res.json({ success: true, files });
@@ -359,15 +431,20 @@ async function webCommand(options) {
359
431
  session.resizeOwner = ws;
360
432
  }
361
433
  sessionManager.logWsConnected(sessionId, req);
434
+
435
+ if (session.kind === 'claude-structured') {
436
+ ws.send(JSON.stringify({ type: 'claude-snapshot', snapshot: sessionManager.getClaudeSnapshot(sessionId) }));
437
+ }
362
438
 
363
439
  // Send catchup output. TUI tools may skip the raw circular buffer, so fall
364
440
  // back to the rendered/text history snapshot instead of reconnecting blank.
365
441
  const catchup = sessionManager.getCatchupOutput(sessionId);
366
- ws.needsTuiRedraw = ['antigravity', 'claude-code', 'codex', 'gemini'].includes(session.tool.key)
442
+ ws.needsTuiRedraw = session.kind !== 'claude-structured'
443
+ && ['antigravity', 'claude-code', 'codex'].includes(session.tool.key)
367
444
  && (isReconnect || (catchup && catchup.source === 'rendered-history'));
368
445
  if (ws.needsTuiRedraw) {
369
446
  ws.send(JSON.stringify({ type: 'reset' }));
370
- } else if (catchup && catchup.data) {
447
+ } else if (session.kind !== 'claude-structured' && catchup && catchup.data) {
371
448
  sessionManager.logWsCatchupOutput(sessionId, catchup);
372
449
  ws.send(JSON.stringify({ type: 'output', data: catchup.data }));
373
450
  }
@@ -378,6 +455,21 @@ async function webCommand(options) {
378
455
  if (payload.type === 'input') {
379
456
  session.write(payload.data);
380
457
  }
458
+ if (payload.type === 'claude-input') {
459
+ sessionManager.sendClaudeInput(sessionId, payload.text || '');
460
+ }
461
+ if (payload.type === 'claude-permission') {
462
+ sessionManager.respondClaudePermission(sessionId, payload.id, Boolean(payload.approved));
463
+ }
464
+ if (payload.type === 'claude-settings') {
465
+ sessionManager.updateClaudeSettings(sessionId, payload.settings || {});
466
+ }
467
+ if (payload.type === 'claude-abort') {
468
+ sessionManager.abortClaude(sessionId);
469
+ }
470
+ if (payload.type === 'claude-resume') {
471
+ sessionManager.resumeClaude(sessionId, payload.resumeSessionId || '');
472
+ }
381
473
  if (payload.type === 'resize' && session.resizeOwner === ws) {
382
474
  sessionManager.logWsResize(sessionId, payload.cols, payload.rows);
383
475
  if (ws.needsTuiRedraw) {