assistme 0.7.0 → 0.8.2
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/{chunk-QHMIXIWO.js → chunk-A2NR7LCQ.js} +69 -9
- package/dist/chunk-IKYXC4RJ.js +3383 -0
- package/dist/index.js +1324 -7269
- package/dist/{job-runner-YM2NBIL3.js → job-runner-PECVS424.js} +1 -1
- package/dist/workers/entry.d.ts +1 -0
- package/dist/workers/entry.js +3280 -0
- package/package.json +3 -3
- package/src/agent/self-analyzer.ts +1 -1
- package/src/commands/monitor.ts +4 -6
- package/src/commands/start.ts +24 -17
- package/src/db/analysis-data.ts +4 -1
- package/src/db/session-log.ts +3 -2
- package/src/orchestrator.ts +492 -0
- package/src/utils/logger.ts +60 -10
- package/src/workers/base-handler.ts +94 -0
- package/src/workers/conversation.ts +74 -0
- package/src/workers/entry.ts +37 -0
- package/src/workers/index.ts +9 -0
- package/src/workers/manager.ts +506 -0
- package/src/workers/types.ts +61 -0
package/src/utils/logger.ts
CHANGED
|
@@ -1,12 +1,35 @@
|
|
|
1
1
|
import chalk from "chalk";
|
|
2
2
|
import { randomUUID } from "crypto";
|
|
3
|
+
import { readFileSync } from "fs";
|
|
4
|
+
import { join, dirname } from "path";
|
|
5
|
+
import { fileURLToPath } from "url";
|
|
3
6
|
|
|
4
7
|
export type LogLevel = "debug" | "info" | "warn" | "error";
|
|
5
|
-
export type LogHook = (logType: "stdout" | "stderr", message: string) => void;
|
|
8
|
+
export type LogHook = (logType: "stdout" | "stderr", message: string, conversationId?: string | null) => void;
|
|
9
|
+
export type LogMethod = "debug" | "info" | "success" | "warn" | "error" | "agent" | "tool" | "result";
|
|
10
|
+
export type LogTransport = (method: LogMethod, message: string, extra?: string) => void;
|
|
6
11
|
|
|
7
12
|
let currentLevel: LogLevel = "info";
|
|
8
13
|
let currentCorrelationId: string | null = null;
|
|
14
|
+
let currentConversationId: string | null = null;
|
|
9
15
|
let logHook: LogHook | null = null;
|
|
16
|
+
/** When set, ALL log output is sent via transport instead of console. Used by worker processes. */
|
|
17
|
+
let logTransport: LogTransport | null = null;
|
|
18
|
+
|
|
19
|
+
/** Package version, loaded once from package.json. */
|
|
20
|
+
const PKG_VERSION = (() => {
|
|
21
|
+
try {
|
|
22
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
23
|
+
// Works from both src/ and dist/
|
|
24
|
+
for (const rel of ["../package.json", "../../package.json"]) {
|
|
25
|
+
try {
|
|
26
|
+
const pkg = JSON.parse(readFileSync(join(__dirname, rel), "utf-8"));
|
|
27
|
+
if (pkg.version) return pkg.version as string;
|
|
28
|
+
} catch { /* try next */ }
|
|
29
|
+
}
|
|
30
|
+
} catch { /* fallback */ }
|
|
31
|
+
return "unknown";
|
|
32
|
+
})();
|
|
10
33
|
|
|
11
34
|
const LEVEL_ORDER: Record<LogLevel, number> = {
|
|
12
35
|
debug: 0,
|
|
@@ -27,6 +50,15 @@ export function setLogHook(hook: LogHook | null) {
|
|
|
27
50
|
logHook = hook;
|
|
28
51
|
}
|
|
29
52
|
|
|
53
|
+
/**
|
|
54
|
+
* Set a transport that replaces console output. Used by worker processes
|
|
55
|
+
* to forward all log output via IPC to the orchestrator.
|
|
56
|
+
* When set, log methods send structured data via transport instead of printing to console.
|
|
57
|
+
*/
|
|
58
|
+
export function setLogTransport(transport: LogTransport | null) {
|
|
59
|
+
logTransport = transport;
|
|
60
|
+
}
|
|
61
|
+
|
|
30
62
|
/**
|
|
31
63
|
* Set a correlation ID that will be included in all subsequent log messages.
|
|
32
64
|
* Call with null to clear.
|
|
@@ -35,6 +67,15 @@ export function setCorrelationId(id: string | null) {
|
|
|
35
67
|
currentCorrelationId = id;
|
|
36
68
|
}
|
|
37
69
|
|
|
70
|
+
/**
|
|
71
|
+
* Set the current conversation ID for log context.
|
|
72
|
+
* Passed to logHook so logs can be associated with a conversation.
|
|
73
|
+
* JS is single-threaded, so set before log calls and clear after.
|
|
74
|
+
*/
|
|
75
|
+
export function setLogConversationId(id: string | null) {
|
|
76
|
+
currentConversationId = id;
|
|
77
|
+
}
|
|
78
|
+
|
|
38
79
|
/**
|
|
39
80
|
* Generate a new short correlation ID (first 8 chars of UUID).
|
|
40
81
|
*/
|
|
@@ -58,55 +99,64 @@ function timestamp(): string {
|
|
|
58
99
|
|
|
59
100
|
function prefix(): string {
|
|
60
101
|
const ts = timestamp();
|
|
61
|
-
|
|
102
|
+
const base = `${ts} v${PKG_VERSION}`;
|
|
103
|
+
return currentCorrelationId ? `${base} ${currentCorrelationId}` : base;
|
|
62
104
|
}
|
|
63
105
|
|
|
64
106
|
export const log = {
|
|
65
107
|
debug(msg: string, ...args: unknown[]) {
|
|
66
108
|
if (shouldLog("debug")) {
|
|
67
109
|
const text = formatArgs(msg, args);
|
|
68
|
-
|
|
110
|
+
if (logTransport) { logTransport("debug", text); return; }
|
|
111
|
+
logHook?.("stdout", `[DEBUG] ${text}`, currentConversationId);
|
|
69
112
|
console.log(chalk.gray(`[${prefix()}] DEBUG`), msg, ...args);
|
|
70
113
|
}
|
|
71
114
|
},
|
|
72
115
|
info(msg: string, ...args: unknown[]) {
|
|
73
116
|
if (shouldLog("info")) {
|
|
74
117
|
const text = formatArgs(msg, args);
|
|
75
|
-
|
|
118
|
+
if (logTransport) { logTransport("info", text); return; }
|
|
119
|
+
logHook?.("stdout", text, currentConversationId);
|
|
76
120
|
console.log(chalk.blue(`[${prefix()}]`), msg, ...args);
|
|
77
121
|
}
|
|
78
122
|
},
|
|
79
123
|
success(msg: string, ...args: unknown[]) {
|
|
80
124
|
if (shouldLog("info")) {
|
|
81
125
|
const text = formatArgs(msg, args);
|
|
82
|
-
|
|
126
|
+
if (logTransport) { logTransport("success", text); return; }
|
|
127
|
+
logHook?.("stdout", `✓ ${text}`, currentConversationId);
|
|
83
128
|
console.log(chalk.green(`[${prefix()}] ✓`), msg, ...args);
|
|
84
129
|
}
|
|
85
130
|
},
|
|
86
131
|
warn(msg: string, ...args: unknown[]) {
|
|
87
132
|
if (shouldLog("warn")) {
|
|
88
133
|
const text = formatArgs(msg, args);
|
|
89
|
-
|
|
134
|
+
if (logTransport) { logTransport("warn", text); return; }
|
|
135
|
+
logHook?.("stderr", `[WARN] ${text}`, currentConversationId);
|
|
90
136
|
console.log(chalk.yellow(`[${prefix()}] WARN`), msg, ...args);
|
|
91
137
|
}
|
|
92
138
|
},
|
|
93
139
|
error(msg: string, ...args: unknown[]) {
|
|
94
140
|
if (shouldLog("error")) {
|
|
95
141
|
const text = formatArgs(msg, args);
|
|
96
|
-
|
|
142
|
+
if (logTransport) { logTransport("error", text); return; }
|
|
143
|
+
logHook?.("stderr", `[ERROR] ${text}`, currentConversationId);
|
|
97
144
|
console.error(chalk.red(`[${prefix()}] ERROR`), msg, ...args);
|
|
98
145
|
}
|
|
99
146
|
},
|
|
100
147
|
agent(msg: string) {
|
|
101
|
-
|
|
148
|
+
if (logTransport) { logTransport("agent", msg); return; }
|
|
149
|
+
logHook?.("stdout", `▸ ${msg}`, currentConversationId);
|
|
102
150
|
console.log(chalk.cyan(" ▸"), msg);
|
|
103
151
|
},
|
|
104
152
|
tool(name: string, msg: string) {
|
|
105
|
-
|
|
153
|
+
if (logTransport) { logTransport("tool", msg, name); return; }
|
|
154
|
+
logHook?.("stdout", `⚡ ${name}: ${msg}`, currentConversationId);
|
|
106
155
|
console.log(chalk.magenta(` ⚡ ${name}:`), msg);
|
|
107
156
|
},
|
|
108
157
|
result(msg: string) {
|
|
109
|
-
|
|
158
|
+
if (logTransport) { logTransport("result", msg); return; }
|
|
159
|
+
logHook?.("stdout", `← ${msg}`, currentConversationId);
|
|
110
160
|
console.log(chalk.green(" ←"), msg);
|
|
111
161
|
},
|
|
112
162
|
};
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base class for worker handlers running inside a forked child process.
|
|
3
|
+
*
|
|
4
|
+
* Handles IPC communication with the orchestrator, lifecycle management,
|
|
5
|
+
* and provides hooks for subclasses to implement specific logic.
|
|
6
|
+
*
|
|
7
|
+
* Installs a log transport so that all `log.*` calls in the worker process
|
|
8
|
+
* (including processor, event-hooks, skill-evaluator, etc.) are forwarded
|
|
9
|
+
* to the orchestrator via IPC for centralized logging and persistence.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type { OrchestratorMessage, WorkerMessage, WorkerConfig, WorkerType, LogMethod } from "./types.js";
|
|
13
|
+
import { setLogTransport } from "../utils/logger.js";
|
|
14
|
+
|
|
15
|
+
export abstract class BaseHandler {
|
|
16
|
+
protected config: WorkerConfig | null = null;
|
|
17
|
+
protected running = false;
|
|
18
|
+
|
|
19
|
+
abstract readonly workerType: WorkerType;
|
|
20
|
+
|
|
21
|
+
/** Initialize with config. Called once after fork. */
|
|
22
|
+
abstract onInit(config: WorkerConfig): Promise<void>;
|
|
23
|
+
|
|
24
|
+
/** Handle incoming messages from orchestrator. */
|
|
25
|
+
abstract onMessage(message: OrchestratorMessage): Promise<void>;
|
|
26
|
+
|
|
27
|
+
/** Cleanup before shutdown. */
|
|
28
|
+
abstract onShutdown(): Promise<void>;
|
|
29
|
+
|
|
30
|
+
/** Start listening for IPC messages from the orchestrator. */
|
|
31
|
+
start(): void {
|
|
32
|
+
this.running = true;
|
|
33
|
+
|
|
34
|
+
// Install log transport: forward all log.* calls to orchestrator via IPC
|
|
35
|
+
setLogTransport((method: LogMethod, message: string, extra?: string) => {
|
|
36
|
+
this.send({ type: "log_forward", method, message, extra });
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
process.on("message", async (raw: unknown) => {
|
|
40
|
+
const message = raw as OrchestratorMessage;
|
|
41
|
+
|
|
42
|
+
try {
|
|
43
|
+
switch (message.type) {
|
|
44
|
+
case "init":
|
|
45
|
+
this.config = message.config;
|
|
46
|
+
await this.onInit(message.config);
|
|
47
|
+
this.send({ type: "ready" });
|
|
48
|
+
break;
|
|
49
|
+
|
|
50
|
+
case "shutdown":
|
|
51
|
+
this.running = false;
|
|
52
|
+
setLogTransport(null);
|
|
53
|
+
await this.onShutdown();
|
|
54
|
+
process.exit(0);
|
|
55
|
+
break;
|
|
56
|
+
|
|
57
|
+
default:
|
|
58
|
+
await this.onMessage(message);
|
|
59
|
+
break;
|
|
60
|
+
}
|
|
61
|
+
} catch (err) {
|
|
62
|
+
this.send({
|
|
63
|
+
type: "error",
|
|
64
|
+
error: err instanceof Error ? err.message : String(err),
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// Graceful shutdown on signals
|
|
70
|
+
process.on("SIGTERM", async () => {
|
|
71
|
+
this.running = false;
|
|
72
|
+
await this.onShutdown();
|
|
73
|
+
process.exit(0);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
process.on("disconnect", () => {
|
|
77
|
+
// Parent process exited — shut down
|
|
78
|
+
this.running = false;
|
|
79
|
+
process.exit(0);
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Send a message to the orchestrator (main process). */
|
|
84
|
+
protected send(message: WorkerMessage): void {
|
|
85
|
+
if (process.send) {
|
|
86
|
+
process.send(message);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** Log via IPC (logs are displayed by the orchestrator). */
|
|
91
|
+
protected log(level: string, message: string): void {
|
|
92
|
+
this.send({ type: "log", level, message });
|
|
93
|
+
}
|
|
94
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Conversation worker handler.
|
|
3
|
+
*
|
|
4
|
+
* Runs inside a forked child process. Handles one conversation at a time,
|
|
5
|
+
* maintaining in-memory state (history cache, skill manager) across tasks
|
|
6
|
+
* within the same conversation.
|
|
7
|
+
*
|
|
8
|
+
* Note: HeartbeatEngine runs in the main process (orchestrator).
|
|
9
|
+
* The heartbeat MCP tools gracefully degrade when accessed from a worker
|
|
10
|
+
* (they return "not available in this context").
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { config as loadEnv } from "dotenv";
|
|
14
|
+
import type { OrchestratorMessage, WorkerConfig } from "./types.js";
|
|
15
|
+
import { BaseHandler } from "./base-handler.js";
|
|
16
|
+
import { TaskProcessor } from "../agent/processor.js";
|
|
17
|
+
import { getBrowser } from "../tools/browser.js";
|
|
18
|
+
|
|
19
|
+
loadEnv();
|
|
20
|
+
|
|
21
|
+
export class ConversationHandler extends BaseHandler {
|
|
22
|
+
readonly workerType = "conversation" as const;
|
|
23
|
+
private processor: TaskProcessor | null = null;
|
|
24
|
+
|
|
25
|
+
async onInit(config: WorkerConfig): Promise<void> {
|
|
26
|
+
this.processor = new TaskProcessor();
|
|
27
|
+
this.processor.setUserId(config.userId);
|
|
28
|
+
this.processor.setSessionId(config.sessionId);
|
|
29
|
+
// HeartbeatEngine is intentionally not set — it runs in the main process.
|
|
30
|
+
// The heartbeat MCP tools handle this gracefully (return "not available").
|
|
31
|
+
|
|
32
|
+
this.log(
|
|
33
|
+
"info",
|
|
34
|
+
`Conversation worker initialized (conversation: ${config.conversationId ?? "any"})`
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async onMessage(message: OrchestratorMessage): Promise<void> {
|
|
39
|
+
switch (message.type) {
|
|
40
|
+
case "process_task": {
|
|
41
|
+
const { task } = message;
|
|
42
|
+
this.log("info", `Processing task ${task.id.slice(0, 8)}...`);
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
await this.processor!.processTask(task);
|
|
46
|
+
this.send({ type: "task_completed", taskId: task.id });
|
|
47
|
+
} catch (err) {
|
|
48
|
+
this.send({
|
|
49
|
+
type: "task_failed",
|
|
50
|
+
taskId: task.id,
|
|
51
|
+
error: err instanceof Error ? err.message : String(err),
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
default:
|
|
58
|
+
this.log("warn", `Unknown message type: ${message.type}`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async onShutdown(): Promise<void> {
|
|
63
|
+
this.log("info", "Conversation worker shutting down");
|
|
64
|
+
|
|
65
|
+
try {
|
|
66
|
+
const browser = getBrowser();
|
|
67
|
+
if (browser.isConnected()) {
|
|
68
|
+
await browser.disconnect();
|
|
69
|
+
}
|
|
70
|
+
} catch {
|
|
71
|
+
// Ignore cleanup errors
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Worker process entry point.
|
|
5
|
+
*
|
|
6
|
+
* This file is forked by the WorkerManager. It reads the worker type
|
|
7
|
+
* from the command-line argument and starts the appropriate handler.
|
|
8
|
+
*
|
|
9
|
+
* Usage: node dist/workers/entry.js --type conversation
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type { WorkerType } from "./types.js";
|
|
13
|
+
import { ConversationHandler } from "./conversation.js";
|
|
14
|
+
import type { BaseHandler } from "./base-handler.js";
|
|
15
|
+
|
|
16
|
+
function getWorkerType(): WorkerType {
|
|
17
|
+
const typeIndex = process.argv.indexOf("--type");
|
|
18
|
+
if (typeIndex === -1 || !process.argv[typeIndex + 1]) {
|
|
19
|
+
console.error("Usage: worker --type <conversation>");
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
return process.argv[typeIndex + 1] as WorkerType;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function createHandler(type: WorkerType): BaseHandler {
|
|
26
|
+
switch (type) {
|
|
27
|
+
case "conversation":
|
|
28
|
+
return new ConversationHandler();
|
|
29
|
+
default:
|
|
30
|
+
console.error(`Unknown worker type: ${type}`);
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const type = getWorkerType();
|
|
36
|
+
const handler = createHandler(type);
|
|
37
|
+
handler.start();
|