@rubytech/create-realagent 1.0.614 → 1.0.616
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/index.js +42 -8
- package/package.json +1 -1
- package/payload/platform/config/brand.json +4 -0
- package/payload/platform/lib/mcp-stderr-tee/dist/index.d.ts +23 -13
- package/payload/platform/lib/mcp-stderr-tee/dist/index.d.ts.map +1 -1
- package/payload/platform/lib/mcp-stderr-tee/dist/index.js +86 -89
- package/payload/platform/lib/mcp-stderr-tee/dist/index.js.map +1 -1
- package/payload/platform/lib/mcp-stderr-tee/src/index.ts +86 -101
- package/payload/platform/plugins/admin/mcp/dist/index.js +33 -2
- package/payload/platform/plugins/admin/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/admin/mcp/dist/lib/review-tools.d.ts.map +1 -1
- package/payload/platform/plugins/admin/mcp/dist/lib/review-tools.js +2 -0
- package/payload/platform/plugins/admin/mcp/dist/lib/review-tools.js.map +1 -1
- package/payload/platform/plugins/admin/skills/stream-log-review/SKILL.md +22 -8
- package/payload/platform/plugins/cloudflare/PLUGIN.md +5 -4
- package/payload/platform/plugins/cloudflare/mcp/__tests__/auth-binding.test.ts +196 -0
- package/payload/platform/plugins/cloudflare/mcp/__tests__/brand-load.test.ts +81 -0
- package/payload/platform/plugins/cloudflare/mcp/__tests__/manifest-scope.test.ts +65 -0
- package/payload/platform/plugins/cloudflare/mcp/__tests__/verify-scenario-0.test.ts +70 -0
- package/payload/platform/plugins/cloudflare/mcp/__tests__/verify-scenario-B.test.ts +124 -0
- package/payload/platform/plugins/cloudflare/mcp/dist/index.js +232 -183
- package/payload/platform/plugins/cloudflare/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/cloudflare/mcp/dist/lib/cloudflared.d.ts +181 -30
- package/payload/platform/plugins/cloudflare/mcp/dist/lib/cloudflared.d.ts.map +1 -1
- package/payload/platform/plugins/cloudflare/mcp/dist/lib/cloudflared.js +938 -154
- package/payload/platform/plugins/cloudflare/mcp/dist/lib/cloudflared.js.map +1 -1
- package/payload/platform/plugins/cloudflare/mcp/package.json +5 -2
- package/payload/platform/plugins/cloudflare/mcp/vitest.config.ts +10 -0
- package/payload/platform/plugins/cloudflare/references/setup-guide.md +32 -27
- package/payload/platform/plugins/cloudflare/skills/setup-tunnel/SKILL.md +25 -3
- package/payload/platform/plugins/docs/PLUGIN.md +2 -0
- package/payload/platform/plugins/docs/references/cloudflare.md +68 -0
- package/payload/platform/plugins/docs/references/plugins-guide.md +8 -6
- package/payload/platform/plugins/docs/references/troubleshooting.md +2 -0
- package/payload/platform/plugins/email/mcp/dist/lib/providers.d.ts +9 -2
- package/payload/platform/plugins/email/mcp/dist/lib/providers.d.ts.map +1 -1
- package/payload/platform/plugins/email/mcp/dist/lib/providers.js +545 -92
- package/payload/platform/plugins/email/mcp/dist/lib/providers.js.map +1 -1
- package/payload/platform/scripts/logs-read.sh +114 -54
- package/payload/platform/templates/agents/admin/IDENTITY.md +6 -0
- package/payload/platform/templates/agents/public/IDENTITY.md +1 -0
- package/payload/platform/templates/specialists/agents/content-producer.md +4 -0
- package/payload/platform/templates/specialists/agents/personal-assistant.md +16 -8
- package/payload/platform/templates/specialists/agents/project-manager.md +4 -0
- package/payload/platform/templates/specialists/agents/research-assistant.md +4 -0
- package/payload/server/server.js +714 -125
|
@@ -15,17 +15,22 @@
|
|
|
15
15
|
* │ │ │
|
|
16
16
|
* │ ├──► original stderr (consumed by Claude Code — opaque)
|
|
17
17
|
* │ │
|
|
18
|
-
* │ ├──► mcp-{name}-stderr-{YYYY-MM-DD}.log (raw chunks
|
|
18
|
+
* │ ├──► mcp-{name}-stderr-{YYYY-MM-DD}.log (raw chunks, per-plugin)
|
|
19
19
|
* │ │
|
|
20
|
-
* │ └──►
|
|
20
|
+
* │ └──► $STREAM_LOG_PATH (per-line, prefixed)
|
|
21
21
|
* │ "[<iso>] [mcp:{name}] <line>"
|
|
22
|
-
* │ rotates on date boundary
|
|
23
22
|
* └─────────────────────────┘
|
|
24
23
|
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
24
|
+
* $STREAM_LOG_PATH is set by the spawner (`getMcpServers` in `claude-agent.ts`)
|
|
25
|
+
* to the per-conversation stream log file. The MCP server code itself knows
|
|
26
|
+
* nothing about conversations — it just trusts the spawner's path. This is
|
|
27
|
+
* the scope boundary introduced by Task 532: the tee is attached per spawn,
|
|
28
|
+
* not per MCP-server process lifetime. Servers spawned for conversation A
|
|
29
|
+
* write to conversation A's file; servers for B write to B's file.
|
|
30
|
+
*
|
|
31
|
+
* Every decision is logged via `[mcp-tee-*]` markers on both the target file
|
|
32
|
+
* and the original stderr so an investigator can confirm from the stream log
|
|
33
|
+
* which tees were wired up, which were skipped, and why.
|
|
29
34
|
*/
|
|
30
35
|
|
|
31
36
|
import {
|
|
@@ -33,7 +38,7 @@ import {
|
|
|
33
38
|
createWriteStream,
|
|
34
39
|
type WriteStream,
|
|
35
40
|
} from "node:fs";
|
|
36
|
-
import { resolve } from "node:path";
|
|
41
|
+
import { resolve, dirname } from "node:path";
|
|
37
42
|
import { StringDecoder } from "node:string_decoder";
|
|
38
43
|
|
|
39
44
|
// Marker on process.stderr.write so repeat calls to initStderrTee() are
|
|
@@ -42,18 +47,23 @@ import { StringDecoder } from "node:string_decoder";
|
|
|
42
47
|
const INIT_MARKER = Symbol.for("maxy.mcpStderrTee.installed");
|
|
43
48
|
|
|
44
49
|
/**
|
|
45
|
-
* Patch process.stderr.write to tee to
|
|
46
|
-
*
|
|
50
|
+
* Patch process.stderr.write to tee to:
|
|
51
|
+
* 1. Per-server raw log (`mcp-<serverName>-stderr-<date>.log` under LOG_DIR).
|
|
52
|
+
* 2. The per-conversation stream log at `STREAM_LOG_PATH` — per-line, prefixed.
|
|
53
|
+
* 3. The original stderr (consumed by Claude Code) — always preserved.
|
|
47
54
|
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
55
|
+
* LOG_DIR absent → skip per-server raw file (dev mode / tool discovery).
|
|
56
|
+
* STREAM_LOG_PATH absent → skip stream-log destination (older spawner or
|
|
57
|
+
* standalone invocation). In both absent cases, stderr works unchanged —
|
|
58
|
+
* a `[mcp-tee-skip]` marker is written to original stderr so the skip is
|
|
59
|
+
* visible to journalctl-level readers.
|
|
50
60
|
*
|
|
51
|
-
* Safe to call once at MCP server module load.
|
|
52
|
-
*
|
|
61
|
+
* Safe to call once at MCP server module load. Refused on second call to
|
|
62
|
+
* prevent stacking patches.
|
|
53
63
|
*/
|
|
54
64
|
export function initStderrTee(serverName: string): void {
|
|
55
65
|
const logDir = process.env.LOG_DIR;
|
|
56
|
-
|
|
66
|
+
const streamLogPath = process.env.STREAM_LOG_PATH;
|
|
57
67
|
|
|
58
68
|
// Refuse repeat patches — stacking them would double-log every chunk
|
|
59
69
|
// and corrupt the re-entrancy guard (originalWrite would capture the
|
|
@@ -65,83 +75,61 @@ export function initStderrTee(serverName: string): void {
|
|
|
65
75
|
// re-entering the patched writer (which would recurse on a tee failure).
|
|
66
76
|
const originalWrite = process.stderr.write.bind(process.stderr);
|
|
67
77
|
|
|
68
|
-
|
|
69
|
-
|
|
78
|
+
const tsPrefix = () => `[${new Date().toISOString()}]`;
|
|
79
|
+
const skipTee = (reason: string, destination: string) => {
|
|
80
|
+
originalWrite(`${tsPrefix()} [platform] [mcp-tee-skip] server=${serverName} destination=${destination} reason=${JSON.stringify(reason)}\n`);
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
// --- Destination 1: per-server raw file (optional, existing Task 362 behaviour)
|
|
70
84
|
let perServerStream: WriteStream | undefined;
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
perServerStream.on("error", (err) => {
|
|
79
|
-
originalWrite(
|
|
80
|
-
`[${new Date().toISOString()}] [platform] mcp stream tee per-server write error server=${serverName} reason=${err.message}\n`,
|
|
85
|
+
if (logDir) {
|
|
86
|
+
try {
|
|
87
|
+
mkdirSync(logDir, { recursive: true });
|
|
88
|
+
const date = new Date().toISOString().slice(0, 10);
|
|
89
|
+
perServerStream = createWriteStream(
|
|
90
|
+
resolve(logDir, `mcp-${serverName}-stderr-${date}.log`),
|
|
91
|
+
{ flags: "a" },
|
|
81
92
|
);
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
93
|
+
perServerStream.on("error", (err) => {
|
|
94
|
+
originalWrite(`${tsPrefix()} [platform] [mcp-tee-error] server=${serverName} destination=per-server reason=${JSON.stringify(err.message)}\n`);
|
|
95
|
+
});
|
|
96
|
+
} catch (err) {
|
|
97
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
98
|
+
skipTee(msg, "per-server");
|
|
99
|
+
perServerStream = undefined;
|
|
100
|
+
}
|
|
101
|
+
} else {
|
|
102
|
+
skipTee("LOG_DIR not set", "per-server");
|
|
91
103
|
}
|
|
92
104
|
|
|
93
|
-
//
|
|
94
|
-
// a long-lived MCP server started yesterday routes today's output into
|
|
95
|
-
// today's stream-log file, matching the platform's rotation semantics.
|
|
105
|
+
// --- Destination 2: per-conversation stream log (Task 532)
|
|
96
106
|
let streamLogStream: WriteStream | undefined;
|
|
97
|
-
|
|
98
|
-
let streamLogDisabledReason: string | undefined;
|
|
99
|
-
|
|
100
|
-
const openStreamLog = (): WriteStream | undefined => {
|
|
101
|
-
const today = new Date().toISOString().slice(0, 10);
|
|
102
|
-
if (streamLogStream && streamLogDate === today) return streamLogStream;
|
|
103
|
-
|
|
104
|
-
// Date boundary crossed (or first open). Close the old stream before
|
|
105
|
-
// opening the new one so the file descriptor is released. Also clear
|
|
106
|
-
// any prior disabled reason — the previous failure may have been
|
|
107
|
-
// transient (EMFILE, ENOSPC), and the new day's path is fresh.
|
|
108
|
-
if (streamLogStream && streamLogDate !== today) {
|
|
109
|
-
try { streamLogStream.end(); } catch { /* ignore */ }
|
|
110
|
-
streamLogStream = undefined;
|
|
111
|
-
streamLogDisabledReason = undefined;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
if (streamLogDisabledReason) return undefined;
|
|
115
|
-
|
|
116
|
-
const path = resolve(logDir, `claude-agent-stream-${today}.log`);
|
|
107
|
+
if (streamLogPath) {
|
|
117
108
|
try {
|
|
118
|
-
|
|
109
|
+
mkdirSync(dirname(streamLogPath), { recursive: true });
|
|
110
|
+
const s = createWriteStream(streamLogPath, { flags: "a" });
|
|
119
111
|
s.on("error", (err) => {
|
|
120
|
-
originalWrite(
|
|
121
|
-
`[${new Date().toISOString()}] [platform] mcp stream tee stream-log write error server=${serverName} reason=${err.message}\n`,
|
|
122
|
-
);
|
|
112
|
+
originalWrite(`${tsPrefix()} [platform] [mcp-tee-error] server=${serverName} destination=stream-log reason=${JSON.stringify(err.message)}\n`);
|
|
123
113
|
});
|
|
124
114
|
streamLogStream = s;
|
|
125
|
-
|
|
126
|
-
//
|
|
127
|
-
|
|
128
|
-
s.write(
|
|
129
|
-
`[${new Date().toISOString()}] [platform] attaching mcp stream tee for server=${serverName} logPath=${path}\n`,
|
|
130
|
-
);
|
|
131
|
-
return s;
|
|
115
|
+
// Attach marker lands in the stream log itself so investigators can
|
|
116
|
+
// confirm per-conversation the tee was wired up.
|
|
117
|
+
s.write(`${tsPrefix()} [platform] [mcp-tee-attach] server=${serverName} streamLogPath=${streamLogPath}\n`);
|
|
132
118
|
} catch (err) {
|
|
133
119
|
const msg = err instanceof Error ? err.message : String(err);
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
originalWrite(line);
|
|
137
|
-
if (perServerStream && !perServerStream.destroyed) perServerStream.write(line);
|
|
138
|
-
return undefined;
|
|
120
|
+
skipTee(msg, "stream-log");
|
|
121
|
+
streamLogStream = undefined;
|
|
139
122
|
}
|
|
140
|
-
}
|
|
123
|
+
} else {
|
|
124
|
+
skipTee("STREAM_LOG_PATH not set", "stream-log");
|
|
125
|
+
}
|
|
141
126
|
|
|
142
|
-
//
|
|
143
|
-
//
|
|
144
|
-
|
|
127
|
+
// If neither tee target is available, leave stderr untouched — patching
|
|
128
|
+
// the writer to a no-op tee would still consume event-loop cycles on
|
|
129
|
+
// every write for zero observability gain.
|
|
130
|
+
if (!perServerStream && !streamLogStream) {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
145
133
|
|
|
146
134
|
// Line buffer — accumulates characters written to stderr so complete
|
|
147
135
|
// newline-terminated lines can be prefixed and emitted to the stream log.
|
|
@@ -156,6 +144,7 @@ export function initStderrTee(serverName: string): void {
|
|
|
156
144
|
const utf8 = new StringDecoder("utf8");
|
|
157
145
|
|
|
158
146
|
const emitCompleteLinesToStreamLog = (chunk: string): void => {
|
|
147
|
+
if (!streamLogStream) return;
|
|
159
148
|
lineBuffer += chunk;
|
|
160
149
|
let newlineIndex: number;
|
|
161
150
|
// eslint-disable-next-line no-cond-assign
|
|
@@ -163,11 +152,8 @@ export function initStderrTee(serverName: string): void {
|
|
|
163
152
|
const line = lineBuffer.slice(0, newlineIndex);
|
|
164
153
|
lineBuffer = lineBuffer.slice(newlineIndex + 1);
|
|
165
154
|
if (line.length === 0) continue; // skip blank lines
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
stream.write(
|
|
169
|
-
`[${new Date().toISOString()}] [mcp:${serverName}] ${line}\n`,
|
|
170
|
-
);
|
|
155
|
+
if (streamLogStream.destroyed || streamLogStream.writableEnded) continue;
|
|
156
|
+
streamLogStream.write(`${tsPrefix()} [mcp:${serverName}] ${line}\n`);
|
|
171
157
|
}
|
|
172
158
|
};
|
|
173
159
|
|
|
@@ -179,17 +165,17 @@ export function initStderrTee(serverName: string): void {
|
|
|
179
165
|
if (perServerStream && !perServerStream.destroyed) {
|
|
180
166
|
perServerStream.write(chunk);
|
|
181
167
|
}
|
|
182
|
-
// 2. Stream log — per-line, prefixed
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
168
|
+
// 2. Stream log — per-line, prefixed.
|
|
169
|
+
if (streamLogStream) {
|
|
170
|
+
try {
|
|
171
|
+
const text = typeof chunk === "string"
|
|
172
|
+
? chunk
|
|
173
|
+
: utf8.write(Buffer.from(chunk));
|
|
174
|
+
emitCompleteLinesToStreamLog(text);
|
|
175
|
+
} catch (err) {
|
|
176
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
177
|
+
originalWrite(`${tsPrefix()} [platform] [mcp-tee-emit-error] server=${serverName} reason=${JSON.stringify(msg)}\n`);
|
|
178
|
+
}
|
|
193
179
|
}
|
|
194
180
|
// 3. Original stderr — Claude Code still gets what it's always got.
|
|
195
181
|
return (originalWrite as (chunk: string | Uint8Array, ...a: unknown[]) => boolean)(chunk, ...args);
|
|
@@ -204,12 +190,11 @@ export function initStderrTee(serverName: string): void {
|
|
|
204
190
|
// shutdown anyway. This hook exists for the rare caller that uses
|
|
205
191
|
// process.stderr.write without a trailing newline.
|
|
206
192
|
process.on("beforeExit", () => {
|
|
207
|
-
if (
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
);
|
|
193
|
+
if (streamLogStream && !streamLogStream.destroyed && !streamLogStream.writableEnded) {
|
|
194
|
+
if (lineBuffer.length > 0) {
|
|
195
|
+
streamLogStream.write(`${tsPrefix()} [mcp:${serverName}] ${lineBuffer}\n`);
|
|
196
|
+
}
|
|
197
|
+
streamLogStream.write(`${tsPrefix()} [platform] [mcp-tee-detach] server=${serverName} reason=process-before-exit\n`);
|
|
213
198
|
}
|
|
214
199
|
lineBuffer = "";
|
|
215
200
|
});
|
|
@@ -938,13 +938,44 @@ server.tool("agent-list", "List all public (non-admin) agents with their full co
|
|
|
938
938
|
};
|
|
939
939
|
}
|
|
940
940
|
});
|
|
941
|
-
server.tool("logs-read", "Read recent logs. type=system: raw Claude stream-json. type=session: SSE events sent to client. type=error: Claude subprocess stderr. type=heartbeat: platform event dispatcher (check-due-events cron). type=public: public agent diagnostic log
|
|
941
|
+
server.tool("logs-read", "Read recent logs. Task 532: the stream logs (type=system/error/session/public) are now per-conversation — pass `conversationId` to retrieve a single conversation's log from first [spawn] to final [process-exit]. type=system: raw Claude stream-json + agent events + [tool-wait]/[tool-wait-diag]/[tool-wait-proc] telemetry + MCP server stderr via tee. type=session: SSE events sent to client. type=error: Claude subprocess stderr (raw — NODE_DEBUG HTTP/NET/UNDICI traces land in system via the stream tee, not here). type=heartbeat: platform event dispatcher (check-due-events cron). type=public: public agent diagnostic log. type=server: platform server log. type=mcp: MCP server stderr (per-plugin raw). type=vnc: VNC browser viewer lifecycle. type=review: log-review detector decisions. sessionKey: grep legacy sessionKey-tagged lines across all logs (useful for pre-Task-532 artefacts). When conversationId is provided, reads the single per-conversation file for the requested type (or dumps all type files for that conversationId if type is omitted).", {
|
|
942
942
|
type: z.enum(["system", "session", "error", "heartbeat", "public", "server", "mcp", "vnc", "review"]).optional(),
|
|
943
943
|
lines: z.number().optional(),
|
|
944
944
|
sessionKey: z.string().optional(),
|
|
945
|
-
|
|
945
|
+
conversationId: z.string().optional(),
|
|
946
|
+
}, async ({ type, lines = 50, sessionKey, conversationId }) => {
|
|
946
947
|
try {
|
|
947
948
|
const LOG_DIR = resolve(getAccountDir(), "logs");
|
|
949
|
+
// Task 532: conversationId mode — reads the exact per-conversation file.
|
|
950
|
+
// Precedes the sessionKey grep path because a conversationId is the
|
|
951
|
+
// tightest identity and answers single-conversation questions without
|
|
952
|
+
// a grep pass across every log file.
|
|
953
|
+
if (conversationId) {
|
|
954
|
+
if (!existsSync(LOG_DIR)) {
|
|
955
|
+
return { content: [{ type: "text", text: `Log directory does not exist: ${LOG_DIR}` }] };
|
|
956
|
+
}
|
|
957
|
+
const prefixMap = {
|
|
958
|
+
system: "claude-agent-stream",
|
|
959
|
+
error: "claude-agent-stderr",
|
|
960
|
+
session: "sse-events",
|
|
961
|
+
public: "public-agent-stream",
|
|
962
|
+
};
|
|
963
|
+
const resolvedType = type ?? "system";
|
|
964
|
+
const prefix = prefixMap[resolvedType];
|
|
965
|
+
if (!prefix) {
|
|
966
|
+
return {
|
|
967
|
+
content: [{ type: "text", text: `type=${resolvedType} is not per-conversation. Valid per-conversation types: system, error, session, public. For platform-scoped types (server, vnc, review, heartbeat, mcp) omit conversationId.` }],
|
|
968
|
+
isError: true,
|
|
969
|
+
};
|
|
970
|
+
}
|
|
971
|
+
const fileName = `${prefix}-${conversationId}.log`;
|
|
972
|
+
const filePath = resolve(LOG_DIR, fileName);
|
|
973
|
+
if (!existsSync(filePath)) {
|
|
974
|
+
return { content: [{ type: "text", text: `No log file found for conversationId=${conversationId} type=${resolvedType} at ${filePath}. If the conversation has not yet spawned a subprocess (first turn mid-init) the file will not exist.` }] };
|
|
975
|
+
}
|
|
976
|
+
const result = execFileSync("tail", ["-n", String(lines), filePath], { timeout: 5000 }).toString();
|
|
977
|
+
return { content: [{ type: "text", text: `# ${fileName}\n\n${result}` }] };
|
|
978
|
+
}
|
|
948
979
|
if (!existsSync(LOG_DIR)) {
|
|
949
980
|
return { content: [{ type: "text", text: `Log directory does not exist: ${LOG_DIR}` }] };
|
|
950
981
|
}
|