agent-office 0.4.5 → 0.4.7
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/README.md +48 -21
- package/dist/cli.js +103 -4
- package/dist/commands/communicator.js +582 -0
- package/dist/commands/serve.d.ts +3 -0
- package/dist/commands/serve.js +30 -3
- package/dist/commands/task-board.d.ts +29 -0
- package/dist/commands/task-board.js +251 -0
- package/dist/commands/worker.d.ts +2 -1
- package/dist/commands/worker.js +7 -3
- package/dist/db/index.d.ts +23 -0
- package/dist/db/postgresql-storage.d.ts +17 -1
- package/dist/db/postgresql-storage.js +175 -0
- package/dist/db/sqlite-storage.d.ts +17 -1
- package/dist/db/sqlite-storage.js +241 -0
- package/dist/db/storage-base.d.ts +17 -1
- package/dist/db/storage.d.ts +17 -1
- package/dist/lib/pi-coding-server.d.ts +20 -0
- package/dist/lib/pi-coding-server.js +162 -0
- package/dist/manage/app.js +6 -1
- package/dist/manage/components/CronRequests.d.ts +8 -0
- package/dist/manage/components/CronRequests.js +181 -0
- package/dist/manage/hooks/useApi.d.ts +22 -0
- package/dist/manage/hooks/useApi.js +18 -0
- package/dist/server/routes.js +184 -35
- package/package.json +3 -1
|
@@ -361,6 +361,208 @@ export class AgentOfficeSqliteStorage extends AgentOfficeStorageBase {
|
|
|
361
361
|
`);
|
|
362
362
|
stmt.run(cronJobId, executedAt.toISOString(), success ? 1 : 0, errorMessage ?? null);
|
|
363
363
|
}
|
|
364
|
+
// Cron Requests
|
|
365
|
+
async listCronRequests(filters) {
|
|
366
|
+
let query = `
|
|
367
|
+
SELECT id, name, session_name, schedule, timezone, message, status, requested_at, reviewed_at, reviewed_by, reviewer_notes
|
|
368
|
+
FROM cron_requests
|
|
369
|
+
WHERE 1=1
|
|
370
|
+
`;
|
|
371
|
+
const params = [];
|
|
372
|
+
if (filters?.status) {
|
|
373
|
+
query += ` AND status = ?`;
|
|
374
|
+
params.push(filters.status);
|
|
375
|
+
}
|
|
376
|
+
if (filters?.sessionName) {
|
|
377
|
+
query += ` AND session_name = ?`;
|
|
378
|
+
params.push(filters.sessionName);
|
|
379
|
+
}
|
|
380
|
+
query += ` ORDER BY requested_at DESC`;
|
|
381
|
+
const stmt = this.db.prepare(query);
|
|
382
|
+
const rows = stmt.all(...params);
|
|
383
|
+
return rows.map(row => ({
|
|
384
|
+
...row,
|
|
385
|
+
requested_at: new Date(row.requested_at),
|
|
386
|
+
reviewed_at: row.reviewed_at ? new Date(row.reviewed_at) : null,
|
|
387
|
+
}));
|
|
388
|
+
}
|
|
389
|
+
async getCronRequestById(id) {
|
|
390
|
+
const stmt = this.db.prepare(`
|
|
391
|
+
SELECT id, name, session_name, schedule, timezone, message, status, requested_at, reviewed_at, reviewed_by, reviewer_notes
|
|
392
|
+
FROM cron_requests WHERE id = ?
|
|
393
|
+
`);
|
|
394
|
+
const row = stmt.get(id);
|
|
395
|
+
if (!row)
|
|
396
|
+
return null;
|
|
397
|
+
return {
|
|
398
|
+
...row,
|
|
399
|
+
requested_at: new Date(row.requested_at),
|
|
400
|
+
reviewed_at: row.reviewed_at ? new Date(row.reviewed_at) : null,
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
async createCronRequest(name, sessionName, schedule, timezone, message) {
|
|
404
|
+
const stmt = this.db.prepare(`
|
|
405
|
+
INSERT INTO cron_requests (name, session_name, schedule, timezone, message)
|
|
406
|
+
VALUES (?, ?, ?, ?, ?)
|
|
407
|
+
RETURNING id, name, session_name, schedule, timezone, message, status, requested_at, reviewed_at, reviewed_by, reviewer_notes
|
|
408
|
+
`);
|
|
409
|
+
const row = stmt.get(name, sessionName, schedule, timezone, message);
|
|
410
|
+
return {
|
|
411
|
+
...row,
|
|
412
|
+
requested_at: new Date(row.requested_at),
|
|
413
|
+
reviewed_at: row.reviewed_at ? new Date(row.reviewed_at) : null,
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
async updateCronRequestStatus(id, status, reviewedBy, reviewerNotes) {
|
|
417
|
+
const stmt = this.db.prepare(`
|
|
418
|
+
UPDATE cron_requests
|
|
419
|
+
SET status = ?, reviewed_at = ?, reviewed_by = ?, reviewer_notes = ?
|
|
420
|
+
WHERE id = ?
|
|
421
|
+
RETURNING id, name, session_name, schedule, timezone, message, status, requested_at, reviewed_at, reviewed_by, reviewer_notes
|
|
422
|
+
`);
|
|
423
|
+
const row = stmt.get(status, new Date().toISOString(), reviewedBy, reviewerNotes ?? null, id);
|
|
424
|
+
if (!row)
|
|
425
|
+
return null;
|
|
426
|
+
return {
|
|
427
|
+
...row,
|
|
428
|
+
requested_at: new Date(row.requested_at),
|
|
429
|
+
reviewed_at: row.reviewed_at ? new Date(row.reviewed_at) : null,
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
// Tasks
|
|
433
|
+
async listTasks() {
|
|
434
|
+
const stmt = this.db.prepare(`
|
|
435
|
+
SELECT id, title, description, assignee, column_name, dependencies, created_at, updated_at
|
|
436
|
+
FROM tasks
|
|
437
|
+
ORDER BY created_at DESC
|
|
438
|
+
`);
|
|
439
|
+
const rows = stmt.all();
|
|
440
|
+
return rows.map(row => ({
|
|
441
|
+
id: row.id,
|
|
442
|
+
title: row.title,
|
|
443
|
+
description: row.description,
|
|
444
|
+
assignee: row.assignee,
|
|
445
|
+
column: row.column_name,
|
|
446
|
+
dependencies: JSON.parse(row.dependencies),
|
|
447
|
+
created_at: new Date(row.created_at),
|
|
448
|
+
updated_at: new Date(row.updated_at),
|
|
449
|
+
}));
|
|
450
|
+
}
|
|
451
|
+
async getTaskById(id) {
|
|
452
|
+
const stmt = this.db.prepare(`
|
|
453
|
+
SELECT id, title, description, assignee, column_name, dependencies, created_at, updated_at
|
|
454
|
+
FROM tasks WHERE id = ?
|
|
455
|
+
`);
|
|
456
|
+
const row = stmt.get(id);
|
|
457
|
+
if (!row)
|
|
458
|
+
return null;
|
|
459
|
+
return {
|
|
460
|
+
id: row.id,
|
|
461
|
+
title: row.title,
|
|
462
|
+
description: row.description,
|
|
463
|
+
assignee: row.assignee,
|
|
464
|
+
column: row.column_name,
|
|
465
|
+
dependencies: JSON.parse(row.dependencies),
|
|
466
|
+
created_at: new Date(row.created_at),
|
|
467
|
+
updated_at: new Date(row.updated_at),
|
|
468
|
+
};
|
|
469
|
+
}
|
|
470
|
+
async createTask(title, description, assignee, column, dependencies) {
|
|
471
|
+
const now = new Date().toISOString();
|
|
472
|
+
const stmt = this.db.prepare(`
|
|
473
|
+
INSERT INTO tasks (title, description, assignee, column_name, dependencies, created_at, updated_at)
|
|
474
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
475
|
+
RETURNING id, title, description, assignee, column_name, dependencies, created_at, updated_at
|
|
476
|
+
`);
|
|
477
|
+
const row = stmt.get(title, description, assignee, column, JSON.stringify(dependencies), now, now);
|
|
478
|
+
return {
|
|
479
|
+
id: row.id,
|
|
480
|
+
title: row.title,
|
|
481
|
+
description: row.description,
|
|
482
|
+
assignee: row.assignee,
|
|
483
|
+
column: row.column_name,
|
|
484
|
+
dependencies: JSON.parse(row.dependencies),
|
|
485
|
+
created_at: new Date(row.created_at),
|
|
486
|
+
updated_at: new Date(row.updated_at),
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
async updateTask(id, updates) {
|
|
490
|
+
const setParts = [];
|
|
491
|
+
const values = [];
|
|
492
|
+
if (updates.title !== undefined) {
|
|
493
|
+
setParts.push('title = ?');
|
|
494
|
+
values.push(updates.title);
|
|
495
|
+
}
|
|
496
|
+
if (updates.description !== undefined) {
|
|
497
|
+
setParts.push('description = ?');
|
|
498
|
+
values.push(updates.description);
|
|
499
|
+
}
|
|
500
|
+
if (updates.assignee !== undefined) {
|
|
501
|
+
setParts.push('assignee = ?');
|
|
502
|
+
values.push(updates.assignee);
|
|
503
|
+
}
|
|
504
|
+
if (updates.column !== undefined) {
|
|
505
|
+
setParts.push('column_name = ?');
|
|
506
|
+
values.push(updates.column);
|
|
507
|
+
}
|
|
508
|
+
if (updates.dependencies !== undefined) {
|
|
509
|
+
setParts.push('dependencies = ?');
|
|
510
|
+
values.push(JSON.stringify(updates.dependencies));
|
|
511
|
+
}
|
|
512
|
+
if (setParts.length === 0)
|
|
513
|
+
return this.getTaskById(id);
|
|
514
|
+
setParts.push('updated_at = ?');
|
|
515
|
+
values.push(new Date().toISOString());
|
|
516
|
+
const sql = `UPDATE tasks SET ${setParts.join(', ')} WHERE id = ? RETURNING id, title, description, assignee, column_name, dependencies, created_at, updated_at`;
|
|
517
|
+
values.push(id);
|
|
518
|
+
const stmt = this.db.prepare(sql);
|
|
519
|
+
const row = stmt.get(...values);
|
|
520
|
+
if (!row)
|
|
521
|
+
return null;
|
|
522
|
+
return {
|
|
523
|
+
id: row.id,
|
|
524
|
+
title: row.title,
|
|
525
|
+
description: row.description,
|
|
526
|
+
assignee: row.assignee,
|
|
527
|
+
column: row.column_name,
|
|
528
|
+
dependencies: JSON.parse(row.dependencies),
|
|
529
|
+
created_at: new Date(row.created_at),
|
|
530
|
+
updated_at: new Date(row.updated_at),
|
|
531
|
+
};
|
|
532
|
+
}
|
|
533
|
+
async deleteTask(id) {
|
|
534
|
+
const stmt = this.db.prepare(`DELETE FROM tasks WHERE id = ?`);
|
|
535
|
+
stmt.run(id);
|
|
536
|
+
}
|
|
537
|
+
async searchTasks(query, filters) {
|
|
538
|
+
let sql = `
|
|
539
|
+
SELECT id, title, description, assignee, column_name, dependencies, created_at, updated_at
|
|
540
|
+
FROM tasks
|
|
541
|
+
WHERE (title LIKE ? OR description LIKE ?)
|
|
542
|
+
`;
|
|
543
|
+
const params = [`${query}%`, `${query}%`];
|
|
544
|
+
if (filters?.assignee) {
|
|
545
|
+
sql += ` AND assignee = ?`;
|
|
546
|
+
params.push(filters.assignee);
|
|
547
|
+
}
|
|
548
|
+
if (filters?.column) {
|
|
549
|
+
sql += ` AND column_name = ?`;
|
|
550
|
+
params.push(filters.column);
|
|
551
|
+
}
|
|
552
|
+
sql += ` ORDER BY created_at DESC`;
|
|
553
|
+
const stmt = this.db.prepare(sql);
|
|
554
|
+
const rows = stmt.all(...params);
|
|
555
|
+
return rows.map(row => ({
|
|
556
|
+
id: row.id,
|
|
557
|
+
title: row.title,
|
|
558
|
+
description: row.description,
|
|
559
|
+
assignee: row.assignee,
|
|
560
|
+
column: row.column_name,
|
|
561
|
+
dependencies: JSON.parse(row.dependencies),
|
|
562
|
+
created_at: new Date(row.created_at),
|
|
563
|
+
updated_at: new Date(row.updated_at),
|
|
564
|
+
}));
|
|
565
|
+
}
|
|
364
566
|
// Migrations
|
|
365
567
|
async runMigrations() {
|
|
366
568
|
const MIGRATIONS = [
|
|
@@ -488,6 +690,45 @@ export class AgentOfficeSqliteStorage extends AgentOfficeStorageBase {
|
|
|
488
690
|
name: "add_notified_to_messages",
|
|
489
691
|
sql: `
|
|
490
692
|
ALTER TABLE messages ADD COLUMN notified INTEGER NOT NULL DEFAULT 0;
|
|
693
|
+
`,
|
|
694
|
+
},
|
|
695
|
+
{
|
|
696
|
+
version: 10,
|
|
697
|
+
name: "create_tasks_table",
|
|
698
|
+
sql: `
|
|
699
|
+
CREATE TABLE IF NOT EXISTS tasks (
|
|
700
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
701
|
+
title TEXT NOT NULL,
|
|
702
|
+
description TEXT NOT NULL,
|
|
703
|
+
assignee TEXT,
|
|
704
|
+
column_name TEXT NOT NULL,
|
|
705
|
+
dependencies TEXT NOT NULL DEFAULT '[]',
|
|
706
|
+
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
707
|
+
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
708
|
+
);
|
|
709
|
+
CREATE INDEX IF NOT EXISTS idx_tasks_assignee ON tasks(assignee);
|
|
710
|
+
CREATE INDEX IF NOT EXISTS idx_tasks_column ON tasks(column_name);
|
|
711
|
+
`,
|
|
712
|
+
},
|
|
713
|
+
{
|
|
714
|
+
version: 11,
|
|
715
|
+
name: "create_cron_requests_table",
|
|
716
|
+
sql: `
|
|
717
|
+
CREATE TABLE IF NOT EXISTS cron_requests (
|
|
718
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
719
|
+
name TEXT NOT NULL,
|
|
720
|
+
session_name TEXT NOT NULL REFERENCES sessions(name) ON DELETE CASCADE,
|
|
721
|
+
schedule TEXT NOT NULL,
|
|
722
|
+
timezone TEXT,
|
|
723
|
+
message TEXT NOT NULL,
|
|
724
|
+
status TEXT NOT NULL DEFAULT 'pending',
|
|
725
|
+
requested_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
726
|
+
reviewed_at DATETIME,
|
|
727
|
+
reviewed_by TEXT,
|
|
728
|
+
reviewer_notes TEXT
|
|
729
|
+
);
|
|
730
|
+
CREATE INDEX IF NOT EXISTS idx_cron_requests_session_name ON cron_requests(session_name);
|
|
731
|
+
CREATE INDEX IF NOT EXISTS idx_cron_requests_status ON cron_requests(status);
|
|
491
732
|
`,
|
|
492
733
|
},
|
|
493
734
|
];
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AgentOfficeStorage, SessionRow, ConfigRow, MessageRow, CronJobRow, CronHistoryRow } from "./index.js";
|
|
1
|
+
import type { AgentOfficeStorage, SessionRow, ConfigRow, MessageRow, CronJobRow, CronHistoryRow, CronRequestRow, TaskRow } from "./index.js";
|
|
2
2
|
import type { WatchListener, WatchState, SenderInfo } from "./storage.js";
|
|
3
3
|
export { WatchListener, WatchState, SenderInfo };
|
|
4
4
|
export declare abstract class AgentOfficeStorageBase implements AgentOfficeStorage {
|
|
@@ -44,6 +44,22 @@ export declare abstract class AgentOfficeStorageBase implements AgentOfficeStora
|
|
|
44
44
|
abstract cronJobExistsForSession(name: string, sessionName: string): Promise<boolean>;
|
|
45
45
|
abstract listCronHistory(cronJobId: number, limit: number): Promise<CronHistoryRow[]>;
|
|
46
46
|
abstract createCronHistory(cronJobId: number, executedAt: Date, success: boolean, errorMessage?: string): Promise<void>;
|
|
47
|
+
abstract listCronRequests(filters?: {
|
|
48
|
+
status?: string;
|
|
49
|
+
sessionName?: string;
|
|
50
|
+
}): Promise<CronRequestRow[]>;
|
|
51
|
+
abstract getCronRequestById(id: number): Promise<CronRequestRow | null>;
|
|
52
|
+
abstract createCronRequest(name: string, sessionName: string, schedule: string, timezone: string | null, message: string): Promise<CronRequestRow>;
|
|
53
|
+
abstract updateCronRequestStatus(id: number, status: "approved" | "rejected", reviewedBy: string, reviewerNotes?: string): Promise<CronRequestRow | null>;
|
|
54
|
+
abstract listTasks(): Promise<TaskRow[]>;
|
|
55
|
+
abstract getTaskById(id: number): Promise<TaskRow | null>;
|
|
56
|
+
abstract createTask(title: string, description: string, assignee: string | null, column: string, dependencies: number[]): Promise<TaskRow>;
|
|
57
|
+
abstract updateTask(id: number, updates: Partial<Pick<TaskRow, 'title' | 'description' | 'assignee' | 'column' | 'dependencies'>>): Promise<TaskRow | null>;
|
|
58
|
+
abstract deleteTask(id: number): Promise<void>;
|
|
59
|
+
abstract searchTasks(query: string, filters?: {
|
|
60
|
+
assignee?: string;
|
|
61
|
+
column?: string;
|
|
62
|
+
}): Promise<TaskRow[]>;
|
|
47
63
|
abstract runMigrations(): Promise<void>;
|
|
48
64
|
abstract createMessageImpl(from: string, to: string, body: string): Promise<MessageRow>;
|
|
49
65
|
/**
|
package/dist/db/storage.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { SessionRow, ConfigRow, MessageRow, CronJobRow, CronHistoryRow } from "./index.js";
|
|
1
|
+
import type { SessionRow, ConfigRow, MessageRow, CronJobRow, CronHistoryRow, CronRequestRow } from "./index.js";
|
|
2
2
|
export interface SenderInfo {
|
|
3
3
|
lastSent: string;
|
|
4
4
|
}
|
|
@@ -54,5 +54,21 @@ export interface AgentOfficeStorage {
|
|
|
54
54
|
cronJobExistsForSession(name: string, sessionName: string): Promise<boolean>;
|
|
55
55
|
listCronHistory(cronJobId: number, limit: number): Promise<CronHistoryRow[]>;
|
|
56
56
|
createCronHistory(cronJobId: number, executedAt: Date, success: boolean, errorMessage?: string): Promise<void>;
|
|
57
|
+
listCronRequests(filters?: {
|
|
58
|
+
status?: string;
|
|
59
|
+
sessionName?: string;
|
|
60
|
+
}): Promise<CronRequestRow[]>;
|
|
61
|
+
getCronRequestById(id: number): Promise<CronRequestRow | null>;
|
|
62
|
+
createCronRequest(name: string, sessionName: string, schedule: string, timezone: string | null, message: string): Promise<CronRequestRow>;
|
|
63
|
+
updateCronRequestStatus(id: number, status: "approved" | "rejected", reviewedBy: string, reviewerNotes?: string): Promise<CronRequestRow | null>;
|
|
64
|
+
listTasks(): Promise<import("./index.js").TaskRow[]>;
|
|
65
|
+
getTaskById(id: number): Promise<import("./index.js").TaskRow | null>;
|
|
66
|
+
createTask(title: string, description: string, assignee: string | null, column: string, dependencies: number[]): Promise<import("./index.js").TaskRow>;
|
|
67
|
+
updateTask(id: number, updates: Partial<Pick<import("./index.js").TaskRow, 'title' | 'description' | 'assignee' | 'column' | 'dependencies'>>): Promise<import("./index.js").TaskRow | null>;
|
|
68
|
+
deleteTask(id: number): Promise<void>;
|
|
69
|
+
searchTasks(query: string, filters?: {
|
|
70
|
+
assignee?: string;
|
|
71
|
+
column?: string;
|
|
72
|
+
}): Promise<import("./index.js").TaskRow[]>;
|
|
57
73
|
runMigrations(): Promise<void>;
|
|
58
74
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { AgenticCodingServer, SessionMessage, AgentMode } from "./agentic-coding-server.js";
|
|
2
|
+
export declare class PiCodingServer implements AgenticCodingServer {
|
|
3
|
+
private sessions;
|
|
4
|
+
private authStorage;
|
|
5
|
+
private modelRegistry;
|
|
6
|
+
private selectedModel;
|
|
7
|
+
private vendor;
|
|
8
|
+
private modelName;
|
|
9
|
+
constructor(vendor: string, model: string, apiKey: string);
|
|
10
|
+
/** Shared creation logic (no duplication) */
|
|
11
|
+
private createSessionInternal;
|
|
12
|
+
/** Fallback helper: returns cached session OR creates new one with the exact provided ID */
|
|
13
|
+
private ensureSession;
|
|
14
|
+
createSession(_title: string): Promise<string>;
|
|
15
|
+
deleteSession(sessionID: string): Promise<void>;
|
|
16
|
+
sendMessage(sessionID: string, text: string, agent: string, system: string): Promise<void>;
|
|
17
|
+
getMessages(sessionID: string, limit?: number): Promise<SessionMessage[]>;
|
|
18
|
+
revertSession(sessionID: string, messageID: string): Promise<void>;
|
|
19
|
+
getAgentModes(): Promise<AgentMode[]>;
|
|
20
|
+
}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { createAgentSession, SessionManager, AuthStorage, ModelRegistry } from "@mariozechner/pi-coding-agent";
|
|
2
|
+
import { getModel } from "@mariozechner/pi-ai";
|
|
3
|
+
import crypto from "crypto";
|
|
4
|
+
import path from "path";
|
|
5
|
+
import fs from "fs/promises";
|
|
6
|
+
export class PiCodingServer {
|
|
7
|
+
sessions = new Map();
|
|
8
|
+
authStorage;
|
|
9
|
+
modelRegistry;
|
|
10
|
+
selectedModel;
|
|
11
|
+
vendor;
|
|
12
|
+
modelName;
|
|
13
|
+
constructor(vendor, model, apiKey) {
|
|
14
|
+
if (!vendor || !model || !apiKey?.trim()) {
|
|
15
|
+
throw new Error("vendor, model, and apiKey are all required and must not be empty");
|
|
16
|
+
}
|
|
17
|
+
this.vendor = vendor.trim().toLowerCase();
|
|
18
|
+
this.modelName = model.trim();
|
|
19
|
+
this.authStorage = AuthStorage.create();
|
|
20
|
+
this.authStorage.setRuntimeApiKey(this.vendor, apiKey);
|
|
21
|
+
this.modelRegistry = new ModelRegistry(this.authStorage);
|
|
22
|
+
this.selectedModel = getModel(this.vendor, this.modelName);
|
|
23
|
+
if (!this.selectedModel) {
|
|
24
|
+
throw new Error(`Model '${this.modelName}' not found for provider '${this.vendor}'. ` +
|
|
25
|
+
`Check spelling or run 'pi /model' in a terminal.`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/** Shared creation logic (no duplication) */
|
|
29
|
+
async createSessionInternal(sessionID) {
|
|
30
|
+
const workspace = path.join(process.cwd(), "workspaces", sessionID);
|
|
31
|
+
await fs.mkdir(workspace, { recursive: true });
|
|
32
|
+
const sessionManager = SessionManager.create(workspace);
|
|
33
|
+
const { session } = await createAgentSession({
|
|
34
|
+
cwd: workspace,
|
|
35
|
+
model: this.selectedModel,
|
|
36
|
+
thinkingLevel: "medium",
|
|
37
|
+
sessionManager,
|
|
38
|
+
authStorage: this.authStorage,
|
|
39
|
+
modelRegistry: this.modelRegistry,
|
|
40
|
+
});
|
|
41
|
+
return session;
|
|
42
|
+
}
|
|
43
|
+
/** Fallback helper: returns cached session OR creates new one with the exact provided ID */
|
|
44
|
+
async ensureSession(sessionID) {
|
|
45
|
+
let session = this.sessions.get(sessionID);
|
|
46
|
+
if (!session) {
|
|
47
|
+
session = await this.createSessionInternal(sessionID);
|
|
48
|
+
this.sessions.set(sessionID, session);
|
|
49
|
+
}
|
|
50
|
+
return session;
|
|
51
|
+
}
|
|
52
|
+
async createSession(_title) {
|
|
53
|
+
const sessionID = crypto.randomUUID();
|
|
54
|
+
const session = await this.createSessionInternal(sessionID);
|
|
55
|
+
this.sessions.set(sessionID, session);
|
|
56
|
+
return sessionID;
|
|
57
|
+
}
|
|
58
|
+
async deleteSession(sessionID) {
|
|
59
|
+
const session = this.sessions.get(sessionID);
|
|
60
|
+
if (session) {
|
|
61
|
+
session.dispose?.();
|
|
62
|
+
this.sessions.delete(sessionID);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
async sendMessage(sessionID, text, agent, system) {
|
|
66
|
+
const session = await this.ensureSession(sessionID);
|
|
67
|
+
const prefix = agent ? `[Message from coworker ${agent}]: ` : "";
|
|
68
|
+
const message = `${prefix}${text}`;
|
|
69
|
+
// Set the system prompt if provided
|
|
70
|
+
if (system && system.trim()) {
|
|
71
|
+
session.agent.setSystemPrompt(system);
|
|
72
|
+
}
|
|
73
|
+
// Create a promise that resolves when the agent finishes processing
|
|
74
|
+
const completionPromise = new Promise((resolve) => {
|
|
75
|
+
const unsubscribe = session.subscribe((event) => {
|
|
76
|
+
if (event.type === "agent_end") {
|
|
77
|
+
unsubscribe();
|
|
78
|
+
resolve();
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
// Timeout after 5 minutes in case agent never finishes
|
|
82
|
+
setTimeout(() => {
|
|
83
|
+
unsubscribe();
|
|
84
|
+
resolve();
|
|
85
|
+
}, 5 * 60 * 1000);
|
|
86
|
+
});
|
|
87
|
+
// Send the prompt
|
|
88
|
+
await session.prompt(message);
|
|
89
|
+
// Wait for processing to complete
|
|
90
|
+
await completionPromise;
|
|
91
|
+
}
|
|
92
|
+
async getMessages(sessionID, limit = 100) {
|
|
93
|
+
const session = await this.ensureSession(sessionID);
|
|
94
|
+
const msgs = session.messages || [];
|
|
95
|
+
return msgs.slice(-limit).map((m) => {
|
|
96
|
+
let role = "assistant";
|
|
97
|
+
let parts = [];
|
|
98
|
+
if (m.role === "user") {
|
|
99
|
+
role = "user";
|
|
100
|
+
// User message content can be string or array
|
|
101
|
+
if (typeof m.content === "string") {
|
|
102
|
+
parts = [{ type: "text", text: m.content }];
|
|
103
|
+
}
|
|
104
|
+
else if (Array.isArray(m.content)) {
|
|
105
|
+
parts = m.content.map((c) => {
|
|
106
|
+
if (c.type === "text")
|
|
107
|
+
return { type: "text", text: c.text };
|
|
108
|
+
return { type: c.type || "text", text: JSON.stringify(c) };
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
parts = [{ type: "text", text: JSON.stringify(m.content) }];
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
else if (m.role === "assistant") {
|
|
116
|
+
role = "assistant";
|
|
117
|
+
// Assistant message content is always an array of TextContent | ThinkingContent | ToolCall
|
|
118
|
+
if (Array.isArray(m.content)) {
|
|
119
|
+
parts = m.content.map((c) => {
|
|
120
|
+
if (c.type === "text")
|
|
121
|
+
return { type: "text", text: c.text };
|
|
122
|
+
if (c.type === "thinking")
|
|
123
|
+
return { type: "thinking", text: c.thinking };
|
|
124
|
+
if (c.type === "toolCall")
|
|
125
|
+
return { type: "toolCall", text: `[Tool: ${c.name}]` };
|
|
126
|
+
return { type: c.type || "text", text: JSON.stringify(c) };
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
else if (typeof m.content === "string") {
|
|
130
|
+
parts = [{ type: "text", text: m.content }];
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
// toolResult or other custom types
|
|
135
|
+
parts = [{ type: "text", text: JSON.stringify(m.content) }];
|
|
136
|
+
}
|
|
137
|
+
return {
|
|
138
|
+
id: m.id || m.entryId || crypto.randomUUID(),
|
|
139
|
+
role,
|
|
140
|
+
parts,
|
|
141
|
+
};
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
async revertSession(sessionID, messageID) {
|
|
145
|
+
const session = await this.ensureSession(sessionID);
|
|
146
|
+
try {
|
|
147
|
+
await session.navigateTree(messageID, { summarize: true });
|
|
148
|
+
}
|
|
149
|
+
catch (err) {
|
|
150
|
+
// Graceful: new/empty sessions can't revert → still succeed the call
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
async getAgentModes() {
|
|
154
|
+
return [
|
|
155
|
+
{
|
|
156
|
+
name: "coworker",
|
|
157
|
+
description: `${this.vendor.toUpperCase()} ${this.modelName} – fast agentic coding coworker`,
|
|
158
|
+
model: this.modelName,
|
|
159
|
+
},
|
|
160
|
+
];
|
|
161
|
+
}
|
|
162
|
+
}
|
package/dist/manage/app.js
CHANGED
|
@@ -10,15 +10,17 @@ import { MyMail } from "./components/MyMail.js";
|
|
|
10
10
|
import { SessionSidebar } from "./components/SessionSidebar.js";
|
|
11
11
|
import { MenuSelect } from "./components/MenuSelect.js";
|
|
12
12
|
import { CronList } from "./components/CronList.js";
|
|
13
|
+
import { CronRequests } from "./components/CronRequests.js";
|
|
13
14
|
const MENU_OPTIONS = [
|
|
14
15
|
{ label: "Send message", value: "send-message" },
|
|
15
16
|
{ label: "My mail", value: "my-mail" },
|
|
16
17
|
{ label: "Coworkers", value: "list" },
|
|
17
18
|
{ label: "Cron jobs", value: "cron" },
|
|
19
|
+
{ label: "Cron requests", value: "cron-requests" },
|
|
18
20
|
{ label: "My profile", value: "profile" },
|
|
19
21
|
{ label: "Quit", value: "quit" },
|
|
20
22
|
];
|
|
21
|
-
const SUB_SCREENS = ["list", "send-message", "my-mail", "profile", "cron"];
|
|
23
|
+
const SUB_SCREENS = ["list", "send-message", "my-mail", "profile", "cron", "cron-requests"];
|
|
22
24
|
const FOOTER_HINTS = {
|
|
23
25
|
connecting: "",
|
|
24
26
|
"auth-error": "",
|
|
@@ -28,6 +30,7 @@ const FOOTER_HINTS = {
|
|
|
28
30
|
"my-mail": "↑↓ select message · r reply · m mark read · a mark all read · s sent tab · Esc back",
|
|
29
31
|
"profile": "Enter submit · Esc back to menu",
|
|
30
32
|
cron: "↑↓ navigate · c create · d delete · e enable/disable · h history · Esc back",
|
|
33
|
+
"cron-requests": "↑↓ navigate · a approve · r reject · v view · f filter · Esc back",
|
|
31
34
|
};
|
|
32
35
|
function Header({ serverUrl, unreadCount }) {
|
|
33
36
|
return (_jsxs(Box, { borderStyle: "single", borderColor: "cyan", paddingX: 1, justifyContent: "space-between", children: [_jsx(Text, { bold: true, color: "cyan", children: "agent-office" }), _jsxs(Box, { gap: 2, children: [unreadCount > 0 && (_jsxs(Text, { color: "yellow", bold: true, children: ["\u2709 ", unreadCount, " unread"] })), _jsx(Text, { dimColor: true, children: serverUrl })] })] }));
|
|
@@ -117,6 +120,8 @@ export function App({ serverUrl, password }) {
|
|
|
117
120
|
return (_jsx(Box, { height: contentHeight, flexDirection: "column", paddingX: 2, paddingY: 1, children: _jsx(MyMail, { serverUrl: serverUrl, password: password, onBack: goBack, contentHeight: contentHeight - 2, onReply: (name) => { setReplyTo(name); setScreen("send-message"); } }) }));
|
|
118
121
|
case "cron":
|
|
119
122
|
return (_jsx(Box, { height: contentHeight, flexDirection: "column", paddingX: 2, paddingY: 1, children: _jsx(CronList, { serverUrl: serverUrl, password: password, onBack: goBack, contentHeight: contentHeight - 2 }) }));
|
|
123
|
+
case "cron-requests":
|
|
124
|
+
return (_jsx(Box, { height: contentHeight, flexDirection: "column", paddingX: 2, paddingY: 1, children: _jsx(CronRequests, { serverUrl: serverUrl, password: password, onBack: goBack, contentHeight: contentHeight - 2 }) }));
|
|
120
125
|
}
|
|
121
126
|
};
|
|
122
127
|
return (_jsxs(Box, { flexDirection: "column", width: termWidth, children: [_jsx(Header, { serverUrl: serverUrl, unreadCount: unreadCount }), renderContent(), screen !== "connecting" && (_jsx(Footer, { hint: FOOTER_HINTS[screen] }))] }));
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
interface CronRequestsProps {
|
|
2
|
+
serverUrl: string;
|
|
3
|
+
password: string;
|
|
4
|
+
onBack: () => void;
|
|
5
|
+
contentHeight: number;
|
|
6
|
+
}
|
|
7
|
+
export declare function CronRequests({ serverUrl, password, onBack, contentHeight }: CronRequestsProps): import("react/jsx-runtime").JSX.Element;
|
|
8
|
+
export {};
|