@dexto/tools-lifecycle 1.6.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.
@@ -0,0 +1,28 @@
1
+ import { z } from 'zod';
2
+ import { Tool } from '@dexto/core';
3
+
4
+ declare const ViewLogsInputSchema: z.ZodObject<{
5
+ lines: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
6
+ query: z.ZodOptional<z.ZodString>;
7
+ level: z.ZodOptional<z.ZodUnion<[z.ZodEnum<["debug", "info", "warn", "error", "silly"]>, z.ZodArray<z.ZodEnum<["debug", "info", "warn", "error", "silly"]>, "many">]>>;
8
+ component: z.ZodOptional<z.ZodString>;
9
+ includeContext: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
10
+ }, "strict", z.ZodTypeAny, {
11
+ lines: number;
12
+ includeContext: boolean;
13
+ query?: string | undefined;
14
+ level?: "debug" | "info" | "warn" | "error" | "silly" | ("debug" | "info" | "warn" | "error" | "silly")[] | undefined;
15
+ component?: string | undefined;
16
+ }, {
17
+ lines?: number | undefined;
18
+ query?: string | undefined;
19
+ level?: "debug" | "info" | "warn" | "error" | "silly" | ("debug" | "info" | "warn" | "error" | "silly")[] | undefined;
20
+ component?: string | undefined;
21
+ includeContext?: boolean | undefined;
22
+ }>;
23
+ declare function createViewLogsTool(options: {
24
+ maxLogLines: number;
25
+ maxLogBytes: number;
26
+ }): Tool<typeof ViewLogsInputSchema>;
27
+
28
+ export { createViewLogsTool };
@@ -0,0 +1,149 @@
1
+ import * as fs from "node:fs/promises";
2
+ import { z } from "zod";
3
+ import { ToolError, defineTool } from "@dexto/core";
4
+ const LOG_LEVEL_VALUES = ["debug", "info", "warn", "error", "silly"];
5
+ const ViewLogsInputSchema = z.object({
6
+ lines: z.number().int().positive().optional().default(200).describe("Number of log lines to return from the end of the log file"),
7
+ query: z.string().optional().describe("Optional: filter logs by substring match (case-insensitive)"),
8
+ level: z.union([z.enum(LOG_LEVEL_VALUES), z.array(z.enum(LOG_LEVEL_VALUES))]).optional().describe("Optional: filter logs by level"),
9
+ component: z.string().optional().describe("Optional: filter logs by component"),
10
+ includeContext: z.boolean().optional().default(false).describe("Whether to include structured context for JSON log entries")
11
+ }).strict();
12
+ function isPlainObject(value) {
13
+ return typeof value === "object" && value !== null && !Array.isArray(value);
14
+ }
15
+ function tryParseLogEntry(line) {
16
+ try {
17
+ const parsed = JSON.parse(line);
18
+ if (!isPlainObject(parsed)) {
19
+ return null;
20
+ }
21
+ const level = parsed.level;
22
+ const message = parsed.message;
23
+ const timestamp = parsed.timestamp;
24
+ const component = parsed.component;
25
+ if (typeof level !== "string" || !LOG_LEVEL_VALUES.includes(level) || typeof message !== "string" || typeof timestamp !== "string" || typeof component !== "string") {
26
+ return null;
27
+ }
28
+ const agentId = parsed.agentId;
29
+ const sessionId = parsed.sessionId;
30
+ const toolCallId = parsed.toolCallId;
31
+ const context = parsed.context;
32
+ return {
33
+ level,
34
+ message,
35
+ timestamp,
36
+ component,
37
+ ...typeof agentId === "string" && { agentId },
38
+ ...typeof sessionId === "string" && { sessionId },
39
+ ...typeof toolCallId === "string" && { toolCallId },
40
+ ...isPlainObject(context) && { context }
41
+ };
42
+ } catch {
43
+ return null;
44
+ }
45
+ }
46
+ async function readTailBytes(filePath, maxBytes) {
47
+ const handle = await fs.open(filePath, "r");
48
+ try {
49
+ const stat = await handle.stat();
50
+ const start = Math.max(0, stat.size - maxBytes);
51
+ const length = stat.size - start;
52
+ const buffer = Buffer.alloc(length);
53
+ const { bytesRead } = await handle.read(buffer, 0, length, start);
54
+ return buffer.subarray(0, bytesRead).toString("utf8");
55
+ } finally {
56
+ await handle.close();
57
+ }
58
+ }
59
+ function createViewLogsTool(options) {
60
+ return defineTool({
61
+ id: "view_logs",
62
+ displayName: "View Logs",
63
+ description: "View this session log file (tail). Returns the most recent log lines for debugging. If file logging is not configured, returns a message instead.",
64
+ inputSchema: ViewLogsInputSchema,
65
+ async execute(parsed, context) {
66
+ const logFilePath = context.logger.getLogFilePath();
67
+ if (!logFilePath) {
68
+ return {
69
+ logFilePath: null,
70
+ lines: 0,
71
+ content: "",
72
+ message: "No log file is configured for this session."
73
+ };
74
+ }
75
+ const requestedLines = parsed.lines;
76
+ const maxLines = options.maxLogLines;
77
+ const linesToReturn = Math.min(requestedLines, maxLines);
78
+ const query = parsed.query?.trim();
79
+ const queryLower = query ? query.toLowerCase() : null;
80
+ const component = parsed.component?.trim();
81
+ const levelsInput = parsed.level;
82
+ const levels = typeof levelsInput === "string" ? /* @__PURE__ */ new Set([levelsInput]) : Array.isArray(levelsInput) ? new Set(levelsInput) : null;
83
+ let tailContent;
84
+ try {
85
+ tailContent = await readTailBytes(logFilePath, options.maxLogBytes);
86
+ } catch (error) {
87
+ throw ToolError.executionFailed(
88
+ "view_logs",
89
+ `Failed to read log file: ${error instanceof Error ? error.message : String(error)}`,
90
+ context.sessionId
91
+ );
92
+ }
93
+ const allLines = tailContent.split(/\r?\n/).map((l) => l.trimEnd()).filter((l) => l.trim().length > 0);
94
+ const candidates = allLines.map((line) => ({
95
+ raw: line,
96
+ entry: tryParseLogEntry(line)
97
+ }));
98
+ const filtered = candidates.filter(({ raw, entry }) => {
99
+ if (entry) {
100
+ if (levels && !levels.has(entry.level)) {
101
+ return false;
102
+ }
103
+ if (component && entry.component !== component) {
104
+ return false;
105
+ }
106
+ if (!queryLower) {
107
+ return true;
108
+ }
109
+ const contextText = entry.context ? JSON.stringify(entry.context) : "";
110
+ return entry.message.toLowerCase().includes(queryLower) || contextText.toLowerCase().includes(queryLower);
111
+ }
112
+ if (levels || component) {
113
+ return false;
114
+ }
115
+ if (!queryLower) {
116
+ return true;
117
+ }
118
+ return raw.toLowerCase().includes(queryLower);
119
+ });
120
+ const limited = filtered.slice(Math.max(0, filtered.length - linesToReturn));
121
+ const outputLines = limited.map((l) => l.raw);
122
+ const entries = limited.map(({ entry }) => entry).filter((entry) => entry !== null).map(
123
+ (entry) => parsed.includeContext ? entry : {
124
+ level: entry.level,
125
+ message: entry.message,
126
+ timestamp: entry.timestamp,
127
+ component: entry.component,
128
+ ...entry.agentId !== void 0 && { agentId: entry.agentId },
129
+ ...entry.sessionId !== void 0 && { sessionId: entry.sessionId },
130
+ ...entry.toolCallId !== void 0 && {
131
+ toolCallId: entry.toolCallId
132
+ }
133
+ }
134
+ );
135
+ return {
136
+ logFilePath,
137
+ lines: outputLines.length,
138
+ content: outputLines.join("\n"),
139
+ ...entries.length > 0 && { entries },
140
+ ...query !== void 0 && query.length > 0 && { query },
141
+ ...levelsInput !== void 0 && { level: levelsInput },
142
+ ...component !== void 0 && component.length > 0 && { component }
143
+ };
144
+ }
145
+ });
146
+ }
147
+ export {
148
+ createViewLogsTool
149
+ };
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "@dexto/tools-lifecycle",
3
+ "version": "1.6.0",
4
+ "description": "Lifecycle and self-observation tools for Dexto agents",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ }
13
+ },
14
+ "keywords": [
15
+ "dexto",
16
+ "tools",
17
+ "lifecycle",
18
+ "logs",
19
+ "memories"
20
+ ],
21
+ "dependencies": {
22
+ "zod": "^3.25.0",
23
+ "@dexto/agent-config": "1.6.0",
24
+ "@dexto/core": "1.6.0"
25
+ },
26
+ "devDependencies": {
27
+ "tsup": "^8.0.0",
28
+ "typescript": "^5.3.3",
29
+ "vitest": "^2.1.8"
30
+ },
31
+ "files": [
32
+ "dist",
33
+ "README.md"
34
+ ],
35
+ "scripts": {
36
+ "build": "tsup",
37
+ "typecheck": "tsc --noEmit",
38
+ "clean": "rm -rf dist"
39
+ }
40
+ }