@kodrunhq/opencode-autopilot 1.15.2 → 1.17.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.
Files changed (93) hide show
  1. package/bin/cli.ts +5 -0
  2. package/bin/inspect.ts +337 -0
  3. package/package.json +1 -1
  4. package/src/agents/autopilot.ts +7 -15
  5. package/src/config/index.ts +29 -0
  6. package/src/config/migrations.ts +196 -0
  7. package/src/config/v7.ts +45 -0
  8. package/src/config.ts +3 -3
  9. package/src/health/checks.ts +126 -4
  10. package/src/health/types.ts +1 -1
  11. package/src/index.ts +128 -13
  12. package/src/inspect/formatters.ts +225 -0
  13. package/src/inspect/repository.ts +882 -0
  14. package/src/kernel/database.ts +45 -0
  15. package/src/kernel/migrations.ts +62 -0
  16. package/src/kernel/repository.ts +571 -0
  17. package/src/kernel/schema.ts +122 -0
  18. package/src/kernel/transaction.ts +48 -0
  19. package/src/kernel/types.ts +65 -0
  20. package/src/logging/domains.ts +39 -0
  21. package/src/logging/forensic-writer.ts +177 -0
  22. package/src/logging/index.ts +4 -0
  23. package/src/logging/logger.ts +44 -0
  24. package/src/logging/performance.ts +59 -0
  25. package/src/logging/rotation.ts +261 -0
  26. package/src/logging/types.ts +33 -0
  27. package/src/memory/capture-utils.ts +149 -0
  28. package/src/memory/capture.ts +82 -67
  29. package/src/memory/database.ts +74 -12
  30. package/src/memory/decay.ts +11 -2
  31. package/src/memory/index.ts +17 -1
  32. package/src/memory/injector.ts +4 -1
  33. package/src/memory/lessons.ts +85 -0
  34. package/src/memory/observations.ts +177 -0
  35. package/src/memory/preferences.ts +718 -0
  36. package/src/memory/project-key.ts +6 -0
  37. package/src/memory/projects.ts +83 -0
  38. package/src/memory/repository.ts +52 -216
  39. package/src/memory/retrieval.ts +88 -170
  40. package/src/memory/schemas.ts +39 -7
  41. package/src/memory/types.ts +4 -0
  42. package/src/observability/context-display.ts +8 -0
  43. package/src/observability/event-handlers.ts +69 -20
  44. package/src/observability/event-store.ts +29 -1
  45. package/src/observability/forensic-log.ts +167 -0
  46. package/src/observability/forensic-schemas.ts +77 -0
  47. package/src/observability/forensic-types.ts +10 -0
  48. package/src/observability/index.ts +21 -27
  49. package/src/observability/log-reader.ts +161 -111
  50. package/src/observability/log-writer.ts +41 -83
  51. package/src/observability/retention.ts +2 -2
  52. package/src/observability/session-logger.ts +36 -57
  53. package/src/observability/summary-generator.ts +31 -19
  54. package/src/observability/types.ts +12 -24
  55. package/src/orchestrator/contracts/invariants.ts +14 -0
  56. package/src/orchestrator/contracts/legacy-result-adapter.ts +8 -20
  57. package/src/orchestrator/error-context.ts +24 -0
  58. package/src/orchestrator/fallback/event-handler.ts +47 -3
  59. package/src/orchestrator/handlers/architect.ts +2 -1
  60. package/src/orchestrator/handlers/build-utils.ts +118 -0
  61. package/src/orchestrator/handlers/build.ts +42 -219
  62. package/src/orchestrator/handlers/retrospective.ts +2 -2
  63. package/src/orchestrator/handlers/types.ts +0 -1
  64. package/src/orchestrator/lesson-memory.ts +36 -11
  65. package/src/orchestrator/orchestration-logger.ts +53 -24
  66. package/src/orchestrator/phase.ts +8 -4
  67. package/src/orchestrator/progress.ts +63 -0
  68. package/src/orchestrator/state.ts +79 -17
  69. package/src/projects/database.ts +47 -0
  70. package/src/projects/repository.ts +264 -0
  71. package/src/projects/resolve.ts +301 -0
  72. package/src/projects/schemas.ts +30 -0
  73. package/src/projects/types.ts +12 -0
  74. package/src/review/memory.ts +39 -11
  75. package/src/review/parse-findings.ts +116 -0
  76. package/src/review/pipeline.ts +3 -107
  77. package/src/review/selection.ts +38 -4
  78. package/src/scoring/time-provider.ts +23 -0
  79. package/src/tools/doctor.ts +28 -4
  80. package/src/tools/forensics.ts +7 -12
  81. package/src/tools/logs.ts +38 -11
  82. package/src/tools/memory-preferences.ts +157 -0
  83. package/src/tools/memory-status.ts +17 -96
  84. package/src/tools/orchestrate.ts +108 -90
  85. package/src/tools/pipeline-report.ts +3 -2
  86. package/src/tools/quick.ts +2 -2
  87. package/src/tools/replay.ts +42 -0
  88. package/src/tools/review.ts +46 -7
  89. package/src/tools/session-stats.ts +3 -2
  90. package/src/tools/summary.ts +43 -0
  91. package/src/utils/paths.ts +20 -1
  92. package/src/utils/random.ts +33 -0
  93. package/src/ux/session-summary.ts +56 -0
@@ -0,0 +1,122 @@
1
+ export const KERNEL_SCHEMA_VERSION = 1;
2
+
3
+ export const KERNEL_SCHEMA_STATEMENTS: readonly string[] = Object.freeze([
4
+ `CREATE TABLE IF NOT EXISTS pipeline_runs (
5
+ project_id TEXT NOT NULL,
6
+ run_id TEXT PRIMARY KEY,
7
+ schema_version INTEGER NOT NULL,
8
+ status TEXT NOT NULL,
9
+ current_phase TEXT,
10
+ idea TEXT NOT NULL,
11
+ state_revision INTEGER NOT NULL,
12
+ started_at TEXT NOT NULL,
13
+ last_updated_at TEXT NOT NULL,
14
+ failure_phase TEXT,
15
+ failure_agent TEXT,
16
+ failure_message TEXT,
17
+ last_successful_phase TEXT,
18
+ state_json TEXT NOT NULL,
19
+ FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE
20
+ )`,
21
+ `CREATE INDEX IF NOT EXISTS idx_pipeline_runs_project_updated_at ON pipeline_runs(project_id, last_updated_at DESC, run_id DESC)`,
22
+ `CREATE TABLE IF NOT EXISTS run_phases (
23
+ run_id TEXT NOT NULL,
24
+ phase_name TEXT NOT NULL,
25
+ status TEXT NOT NULL,
26
+ completed_at TEXT,
27
+ confidence TEXT,
28
+ PRIMARY KEY (run_id, phase_name),
29
+ FOREIGN KEY (run_id) REFERENCES pipeline_runs(run_id) ON DELETE CASCADE
30
+ )`,
31
+ `CREATE TABLE IF NOT EXISTS run_tasks (
32
+ run_id TEXT NOT NULL,
33
+ task_id INTEGER NOT NULL,
34
+ title TEXT NOT NULL,
35
+ status TEXT NOT NULL,
36
+ wave INTEGER NOT NULL,
37
+ depends_on_json TEXT NOT NULL,
38
+ attempt INTEGER NOT NULL,
39
+ strike INTEGER NOT NULL,
40
+ PRIMARY KEY (run_id, task_id),
41
+ FOREIGN KEY (run_id) REFERENCES pipeline_runs(run_id) ON DELETE CASCADE
42
+ )`,
43
+ `CREATE TABLE IF NOT EXISTS run_pending_dispatches (
44
+ run_id TEXT NOT NULL,
45
+ dispatch_id TEXT NOT NULL,
46
+ phase TEXT NOT NULL,
47
+ agent TEXT NOT NULL,
48
+ issued_at TEXT NOT NULL,
49
+ result_kind TEXT NOT NULL,
50
+ task_id INTEGER,
51
+ PRIMARY KEY (run_id, dispatch_id),
52
+ FOREIGN KEY (run_id) REFERENCES pipeline_runs(run_id) ON DELETE CASCADE
53
+ )`,
54
+ `CREATE TABLE IF NOT EXISTS run_processed_results (
55
+ run_id TEXT NOT NULL,
56
+ result_id TEXT NOT NULL,
57
+ PRIMARY KEY (run_id, result_id),
58
+ FOREIGN KEY (run_id) REFERENCES pipeline_runs(run_id) ON DELETE CASCADE
59
+ )`,
60
+ `CREATE TABLE IF NOT EXISTS active_review_state (
61
+ project_id TEXT PRIMARY KEY,
62
+ stage INTEGER NOT NULL,
63
+ scope TEXT NOT NULL,
64
+ started_at TEXT NOT NULL,
65
+ saved_at TEXT NOT NULL,
66
+ state_json TEXT NOT NULL,
67
+ FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE
68
+ )`,
69
+ `CREATE TABLE IF NOT EXISTS project_review_memory (
70
+ project_id TEXT PRIMARY KEY,
71
+ schema_version INTEGER NOT NULL,
72
+ last_reviewed_at TEXT,
73
+ state_json TEXT NOT NULL,
74
+ FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE
75
+ )`,
76
+ `CREATE TABLE IF NOT EXISTS project_lesson_memory (
77
+ project_id TEXT PRIMARY KEY,
78
+ schema_version INTEGER NOT NULL,
79
+ last_updated_at TEXT,
80
+ state_json TEXT NOT NULL,
81
+ FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE
82
+ )`,
83
+ `CREATE TABLE IF NOT EXISTS project_lessons (
84
+ lesson_id INTEGER PRIMARY KEY AUTOINCREMENT,
85
+ project_id TEXT NOT NULL,
86
+ content TEXT NOT NULL,
87
+ domain TEXT NOT NULL,
88
+ extracted_at TEXT NOT NULL,
89
+ source_phase TEXT NOT NULL,
90
+ last_updated_at TEXT,
91
+ FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE,
92
+ UNIQUE(project_id, extracted_at, domain, source_phase, content)
93
+ )`,
94
+ `CREATE INDEX IF NOT EXISTS idx_project_lessons_project_extracted_at ON project_lessons(project_id, extracted_at DESC, lesson_id DESC)`,
95
+ `CREATE INDEX IF NOT EXISTS idx_project_lessons_domain ON project_lessons(project_id, domain, extracted_at DESC, lesson_id DESC)`,
96
+ `CREATE TABLE IF NOT EXISTS forensic_events (
97
+ event_id INTEGER PRIMARY KEY AUTOINCREMENT,
98
+ project_id TEXT NOT NULL,
99
+ schema_version INTEGER NOT NULL,
100
+ timestamp TEXT NOT NULL,
101
+ project_root TEXT NOT NULL,
102
+ domain TEXT NOT NULL,
103
+ run_id TEXT,
104
+ session_id TEXT,
105
+ parent_session_id TEXT,
106
+ phase TEXT,
107
+ dispatch_id TEXT,
108
+ task_id INTEGER,
109
+ agent TEXT,
110
+ type TEXT NOT NULL,
111
+ code TEXT,
112
+ message TEXT,
113
+ payload_json TEXT NOT NULL,
114
+ FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE
115
+ )`,
116
+ `CREATE INDEX IF NOT EXISTS idx_forensic_events_timestamp ON forensic_events(timestamp, event_id)`,
117
+ `CREATE INDEX IF NOT EXISTS idx_forensic_events_project ON forensic_events(project_id, timestamp, event_id)`,
118
+ `CREATE INDEX IF NOT EXISTS idx_forensic_events_session ON forensic_events(session_id, timestamp, event_id)`,
119
+ `CREATE INDEX IF NOT EXISTS idx_forensic_events_run ON forensic_events(run_id, timestamp, event_id)`,
120
+ `CREATE INDEX IF NOT EXISTS idx_forensic_events_dispatch ON forensic_events(dispatch_id, timestamp, event_id)`,
121
+ `CREATE INDEX IF NOT EXISTS idx_forensic_events_type ON forensic_events(type, timestamp, event_id)`,
122
+ ]);
@@ -0,0 +1,48 @@
1
+ import type { Database } from "bun:sqlite";
2
+
3
+ export interface TransactionOptions {
4
+ maxRetries?: number;
5
+ backoffMs?: number;
6
+ useImmediate?: boolean;
7
+ }
8
+
9
+ export function withTransaction<T>(db: Database, fn: () => T, options: TransactionOptions = {}): T {
10
+ const maxRetries = options.maxRetries ?? 5;
11
+ const backoffMs = options.backoffMs ?? 100;
12
+ const useImmediate = options.useImmediate ?? true;
13
+
14
+ let attempts = 0;
15
+ while (true) {
16
+ try {
17
+ if (useImmediate) {
18
+ db.run("BEGIN IMMEDIATE");
19
+ try {
20
+ const result = fn();
21
+ db.run("COMMIT");
22
+ return result;
23
+ } catch (innerError) {
24
+ db.run("ROLLBACK");
25
+ throw innerError;
26
+ }
27
+ }
28
+
29
+ const transaction = db.transaction(fn);
30
+ return transaction();
31
+ } catch (error: unknown) {
32
+ const e = error as Error;
33
+ const isBusyError =
34
+ e.message &&
35
+ (e.message.includes("database is locked") ||
36
+ e.message.includes("SQLITE_BUSY") ||
37
+ e.message.includes("database table is locked"));
38
+
39
+ if (isBusyError && attempts < maxRetries) {
40
+ attempts++;
41
+ const waitTime = backoffMs * attempts;
42
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, waitTime);
43
+ continue;
44
+ }
45
+ throw error;
46
+ }
47
+ }
48
+ }
@@ -0,0 +1,65 @@
1
+ import type { ForensicEvent } from "../observability/forensic-types";
2
+ import type { PipelineState } from "../orchestrator/types";
3
+ import type { ReviewState } from "../review/types";
4
+
5
+ export const KERNEL_STATE_CONFLICT_CODE = "E_STATE_CONFLICT";
6
+
7
+ export interface PipelineRunRow {
8
+ readonly project_id: string;
9
+ readonly run_id: string;
10
+ readonly schema_version: number;
11
+ readonly status: PipelineState["status"];
12
+ readonly current_phase: string | null;
13
+ readonly idea: string;
14
+ readonly state_revision: number;
15
+ readonly started_at: string;
16
+ readonly last_updated_at: string;
17
+ readonly failure_phase: string | null;
18
+ readonly failure_agent: string | null;
19
+ readonly failure_message: string | null;
20
+ readonly last_successful_phase: string | null;
21
+ readonly state_json: string;
22
+ }
23
+
24
+ export interface ActiveReviewStateRow {
25
+ readonly project_id: string;
26
+ readonly stage: ReviewState["stage"];
27
+ readonly scope: string;
28
+ readonly started_at: string;
29
+ readonly saved_at: string;
30
+ readonly state_json: string;
31
+ }
32
+
33
+ export interface ProjectReviewMemoryRow {
34
+ readonly project_id: string;
35
+ readonly schema_version: number;
36
+ readonly last_reviewed_at: string | null;
37
+ readonly state_json: string;
38
+ }
39
+
40
+ export interface ProjectLessonMemoryRow {
41
+ readonly project_id: string;
42
+ readonly schema_version: number;
43
+ readonly last_updated_at: string | null;
44
+ readonly state_json: string;
45
+ }
46
+
47
+ export interface ForensicEventRow {
48
+ readonly event_id: number;
49
+ readonly project_id: string;
50
+ readonly schema_version: number;
51
+ readonly timestamp: string;
52
+ readonly project_root: string;
53
+ readonly domain: ForensicEvent["domain"];
54
+ readonly run_id: string | null;
55
+ readonly session_id: string | null;
56
+ readonly parent_session_id: string | null;
57
+ readonly phase: string | null;
58
+ readonly dispatch_id: string | null;
59
+ readonly task_id: number | null;
60
+ readonly agent: string | null;
61
+ readonly type: ForensicEvent["type"];
62
+ readonly code: string | null;
63
+ readonly message: string | null;
64
+ readonly payload_json: string;
65
+ }
@@ -0,0 +1,39 @@
1
+ import { createForensicSink } from "./forensic-writer";
2
+ import { BaseLogger } from "./logger";
3
+ import type { LogEntry, Logger, LogMetadata, LogSink } from "./types";
4
+
5
+ export class MultiplexSink implements LogSink {
6
+ constructor(private readonly sinks: readonly LogSink[]) {}
7
+
8
+ write(entry: LogEntry): void {
9
+ for (const sink of this.sinks) {
10
+ sink.write(entry);
11
+ }
12
+ }
13
+ }
14
+
15
+ let rootLogger: Logger | null = null;
16
+
17
+ export function initLoggers(projectRoot: string, sinks?: readonly LogSink[]): void {
18
+ const resolvedSinks = sinks ?? [createForensicSink(projectRoot)];
19
+ rootLogger = new BaseLogger(new MultiplexSink(resolvedSinks), { domain: "system" });
20
+ }
21
+
22
+ export function getLogger(domain: string, subsystem?: string): Logger {
23
+ if (!rootLogger) {
24
+ return new BaseLogger(
25
+ {
26
+ write(entry: LogEntry): void {
27
+ console.log(entry.level, entry.message);
28
+ },
29
+ },
30
+ compactMetadata(domain, subsystem),
31
+ );
32
+ }
33
+
34
+ return rootLogger.child(compactMetadata(domain, subsystem));
35
+ }
36
+
37
+ function compactMetadata(domain: string, subsystem?: string): LogMetadata {
38
+ return subsystem ? { domain, subsystem } : { domain };
39
+ }
@@ -0,0 +1,177 @@
1
+ import {
2
+ appendForensicEvent,
3
+ appendForensicEventForArtifactDir,
4
+ } from "../observability/forensic-log";
5
+ import type { ForensicEventDomain, ForensicEventType } from "../observability/forensic-types";
6
+ import type { LogEntry, LogSink } from "./types";
7
+
8
+ export function createForensicSinkForArtifactDir(artifactDir: string): LogSink {
9
+ return {
10
+ write(entry: LogEntry): void {
11
+ const {
12
+ domain,
13
+ operation,
14
+ runId,
15
+ sessionId,
16
+ parentSessionId,
17
+ phase,
18
+ dispatchId,
19
+ taskId,
20
+ agent,
21
+ code,
22
+ subsystem,
23
+ ...payload
24
+ } = entry.metadata;
25
+
26
+ let forensicDomain: ForensicEventDomain = "system";
27
+ if (
28
+ domain === "session" ||
29
+ domain === "orchestrator" ||
30
+ domain === "contract" ||
31
+ domain === "system" ||
32
+ domain === "review"
33
+ ) {
34
+ forensicDomain = domain;
35
+ }
36
+
37
+ let forensicType: ForensicEventType = "info";
38
+
39
+ if (operation && isValidForensicType(operation as string)) {
40
+ forensicType = operation as ForensicEventType;
41
+ } else {
42
+ switch (entry.level) {
43
+ case "ERROR":
44
+ forensicType = "error";
45
+ break;
46
+ case "WARN":
47
+ forensicType = "warning";
48
+ break;
49
+ case "INFO":
50
+ forensicType = "info";
51
+ break;
52
+ case "DEBUG":
53
+ forensicType = "debug";
54
+ break;
55
+ }
56
+ }
57
+
58
+ appendForensicEventForArtifactDir(artifactDir, {
59
+ timestamp: entry.timestamp,
60
+ domain: forensicDomain,
61
+ runId: (runId as string) ?? null,
62
+ sessionId: (sessionId as string) ?? null,
63
+ parentSessionId: (parentSessionId as string) ?? null,
64
+ phase: (phase as string) ?? null,
65
+ dispatchId: (dispatchId as string) ?? null,
66
+ taskId: (taskId as number) ?? null,
67
+ agent: (agent as string) ?? null,
68
+ type: forensicType,
69
+ code: (code as string) ?? null,
70
+ message: entry.message,
71
+ payload: {
72
+ ...payload,
73
+ ...(subsystem ? { subsystem } : {}),
74
+ } as Record<string, string | number | boolean | object | readonly unknown[] | null>,
75
+ });
76
+ },
77
+ };
78
+ }
79
+
80
+ export function createForensicSink(projectRoot: string): LogSink {
81
+ return {
82
+ write(entry: LogEntry): void {
83
+ const {
84
+ domain,
85
+ operation,
86
+ runId,
87
+ sessionId,
88
+ parentSessionId,
89
+ phase,
90
+ dispatchId,
91
+ taskId,
92
+ agent,
93
+ code,
94
+ subsystem,
95
+ ...payload
96
+ } = entry.metadata;
97
+
98
+ let forensicDomain: ForensicEventDomain = "system";
99
+ if (
100
+ domain === "session" ||
101
+ domain === "orchestrator" ||
102
+ domain === "contract" ||
103
+ domain === "system" ||
104
+ domain === "review"
105
+ ) {
106
+ forensicDomain = domain;
107
+ }
108
+
109
+ let forensicType: ForensicEventType = "info";
110
+
111
+ if (operation && isValidForensicType(operation as string)) {
112
+ forensicType = operation as ForensicEventType;
113
+ } else {
114
+ switch (entry.level) {
115
+ case "ERROR":
116
+ forensicType = "error";
117
+ break;
118
+ case "WARN":
119
+ forensicType = "warning";
120
+ break;
121
+ case "INFO":
122
+ forensicType = "info";
123
+ break;
124
+ case "DEBUG":
125
+ forensicType = "debug";
126
+ break;
127
+ }
128
+ }
129
+
130
+ appendForensicEvent(projectRoot, {
131
+ timestamp: entry.timestamp,
132
+ projectRoot,
133
+ domain: forensicDomain,
134
+ runId: (runId as string) ?? null,
135
+ sessionId: (sessionId as string) ?? null,
136
+ parentSessionId: (parentSessionId as string) ?? null,
137
+ phase: (phase as string) ?? null,
138
+ dispatchId: (dispatchId as string) ?? null,
139
+ taskId: (taskId as number) ?? null,
140
+ agent: (agent as string) ?? null,
141
+ type: forensicType,
142
+ code: (code as string) ?? null,
143
+ message: entry.message,
144
+ payload: {
145
+ ...payload,
146
+ ...(subsystem ? { subsystem } : {}),
147
+ } as Record<string, string | number | boolean | object | readonly unknown[] | null>,
148
+ });
149
+ },
150
+ };
151
+ }
152
+
153
+ function isValidForensicType(type: string): boolean {
154
+ const validTypes = [
155
+ "run_started",
156
+ "dispatch",
157
+ "dispatch_multi",
158
+ "result_applied",
159
+ "phase_transition",
160
+ "complete",
161
+ "decision",
162
+ "error",
163
+ "loop_detected",
164
+ "failure_recorded",
165
+ "warning",
166
+ "session_start",
167
+ "session_end",
168
+ "fallback",
169
+ "model_switch",
170
+ "context_warning",
171
+ "tool_complete",
172
+ "compacted",
173
+ "info",
174
+ "debug",
175
+ ];
176
+ return validTypes.includes(type);
177
+ }
@@ -0,0 +1,4 @@
1
+ export * from "./domains";
2
+ export * from "./forensic-writer";
3
+ export * from "./logger";
4
+ export * from "./types";
@@ -0,0 +1,44 @@
1
+ import type { LogEntry, Logger, LogLevel, LogMetadata, LogSink } from "./types";
2
+
3
+ export class BaseLogger implements Logger {
4
+ constructor(
5
+ private readonly sink: LogSink,
6
+ private readonly baseMetadata: LogMetadata,
7
+ ) {}
8
+
9
+ debug(message: string, metadata?: Partial<LogMetadata>): void {
10
+ this.log("DEBUG", message, metadata);
11
+ }
12
+
13
+ info(message: string, metadata?: Partial<LogMetadata>): void {
14
+ this.log("INFO", message, metadata);
15
+ }
16
+
17
+ warn(message: string, metadata?: Partial<LogMetadata>): void {
18
+ this.log("WARN", message, metadata);
19
+ }
20
+
21
+ error(message: string, metadata?: Partial<LogMetadata>): void {
22
+ this.log("ERROR", message, metadata);
23
+ }
24
+
25
+ child(metadata: Partial<LogMetadata>): Logger {
26
+ return new BaseLogger(this.sink, {
27
+ ...this.baseMetadata,
28
+ ...metadata,
29
+ });
30
+ }
31
+
32
+ private log(level: LogLevel, message: string, metadata?: Partial<LogMetadata>): void {
33
+ const entry: LogEntry = {
34
+ timestamp: new Date().toISOString(),
35
+ level,
36
+ message,
37
+ metadata: {
38
+ ...this.baseMetadata,
39
+ ...metadata,
40
+ },
41
+ };
42
+ this.sink.write(Object.freeze(entry));
43
+ }
44
+ }
@@ -0,0 +1,59 @@
1
+ import { getLogger } from "./domains";
2
+
3
+ function log() {
4
+ return getLogger("system", "performance");
5
+ }
6
+
7
+ export interface MemorySnapshot {
8
+ readonly rss: number;
9
+ readonly heapTotal: number;
10
+ readonly heapUsed: number;
11
+ readonly external: number;
12
+ readonly arrayBuffers: number;
13
+ }
14
+
15
+ export interface TimerHandle {
16
+ stop(metadata?: Record<string, unknown>): void;
17
+ }
18
+
19
+ export function recordMemoryUsage(): void {
20
+ const mem = process.memoryUsage();
21
+
22
+ const snapshot: MemorySnapshot = {
23
+ rss: mem.rss,
24
+ heapTotal: mem.heapTotal,
25
+ heapUsed: mem.heapUsed,
26
+ external: mem.external,
27
+ arrayBuffers: mem.arrayBuffers,
28
+ };
29
+
30
+ log().info("memory usage", {
31
+ operation: "memory_snapshot",
32
+ ...snapshot,
33
+ });
34
+ }
35
+
36
+ export function startTimer(operation: string): TimerHandle {
37
+ // performance.now() is monotonic and unaffected by system-clock adjustments
38
+ const startMs = performance.now();
39
+
40
+ return {
41
+ stop(metadata?: Record<string, unknown>): void {
42
+ const durationMs = performance.now() - startMs;
43
+
44
+ log().info("operation completed", {
45
+ operation,
46
+ durationMs,
47
+ ...metadata,
48
+ });
49
+ },
50
+ };
51
+ }
52
+
53
+ export function recordAgentResponseTime(agent: string, durationMs: number): void {
54
+ log().info("agent response time", {
55
+ operation: "agent_response_time",
56
+ agent,
57
+ durationMs,
58
+ });
59
+ }