@lobu/worker 7.0.0 → 7.2.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.
- package/dist/core/error-handler.d.ts +0 -4
- package/dist/core/error-handler.d.ts.map +1 -1
- package/dist/core/error-handler.js +4 -15
- package/dist/core/error-handler.js.map +1 -1
- package/dist/core/types.d.ts +19 -19
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js +0 -4
- package/dist/core/types.js.map +1 -1
- package/dist/core/workspace.d.ts +2 -11
- package/dist/core/workspace.d.ts.map +1 -1
- package/dist/core/workspace.js +14 -36
- package/dist/core/workspace.js.map +1 -1
- package/dist/embedded/just-bash-bootstrap.d.ts.map +1 -1
- package/dist/embedded/just-bash-bootstrap.js +34 -4
- package/dist/embedded/just-bash-bootstrap.js.map +1 -1
- package/dist/embedded/mcp-cli-commands.d.ts.map +1 -1
- package/dist/embedded/mcp-cli-commands.js +3 -38
- package/dist/embedded/mcp-cli-commands.js.map +1 -1
- package/dist/gateway/sse-client.d.ts.map +1 -1
- package/dist/gateway/sse-client.js +72 -10
- package/dist/gateway/sse-client.js.map +1 -1
- package/dist/gateway/types.d.ts +2 -0
- package/dist/gateway/types.d.ts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -24
- package/dist/index.js.map +1 -1
- package/dist/instructions/builder.d.ts.map +1 -1
- package/dist/instructions/builder.js +2 -1
- package/dist/instructions/builder.js.map +1 -1
- package/dist/openclaw/plugin-loader.d.ts.map +1 -1
- package/dist/openclaw/plugin-loader.js +8 -19
- package/dist/openclaw/plugin-loader.js.map +1 -1
- package/dist/openclaw/processor.d.ts.map +1 -1
- package/dist/openclaw/processor.js +2 -0
- package/dist/openclaw/processor.js.map +1 -1
- package/dist/openclaw/sandbox-leak.d.ts.map +1 -1
- package/dist/openclaw/sandbox-leak.js +1 -6
- package/dist/openclaw/sandbox-leak.js.map +1 -1
- package/dist/openclaw/session-context.d.ts.map +1 -1
- package/dist/openclaw/session-context.js +3 -0
- package/dist/openclaw/session-context.js.map +1 -1
- package/dist/openclaw/tool-policy.d.ts.map +1 -1
- package/dist/openclaw/tool-policy.js +5 -11
- package/dist/openclaw/tool-policy.js.map +1 -1
- package/dist/openclaw/transcript-snapshot.d.ts +88 -0
- package/dist/openclaw/transcript-snapshot.d.ts.map +1 -0
- package/dist/openclaw/transcript-snapshot.js +223 -0
- package/dist/openclaw/transcript-snapshot.js.map +1 -0
- package/dist/openclaw/worker.d.ts +14 -0
- package/dist/openclaw/worker.d.ts.map +1 -1
- package/dist/openclaw/worker.js +147 -10
- package/dist/openclaw/worker.js.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +3 -40
- package/dist/server.js.map +1 -1
- package/dist/shared/audio-provider-suggestions.d.ts.map +1 -1
- package/dist/shared/audio-provider-suggestions.js +4 -6
- package/dist/shared/audio-provider-suggestions.js.map +1 -1
- package/dist/shared/tool-implementations.d.ts.map +1 -1
- package/dist/shared/tool-implementations.js +62 -24
- package/dist/shared/tool-implementations.js.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/processor-harden.test.ts +6 -16
- package/src/__tests__/sse-client.test.ts +99 -0
- package/src/__tests__/transcript-snapshot.test.ts +275 -0
- package/src/core/error-handler.ts +5 -20
- package/src/core/types.ts +19 -35
- package/src/core/workspace.ts +22 -45
- package/src/embedded/just-bash-bootstrap.ts +36 -4
- package/src/embedded/mcp-cli-commands.ts +9 -6
- package/src/gateway/sse-client.ts +87 -22
- package/src/gateway/types.ts +15 -0
- package/src/index.ts +8 -26
- package/src/instructions/builder.ts +2 -3
- package/src/openclaw/plugin-loader.ts +15 -19
- package/src/openclaw/processor.ts +1 -0
- package/src/openclaw/sandbox-leak.ts +1 -6
- package/src/openclaw/session-context.ts +3 -0
- package/src/openclaw/tool-policy.ts +5 -12
- package/src/openclaw/transcript-snapshot.ts +238 -0
- package/src/openclaw/worker.ts +167 -13
- package/src/server.ts +1 -5
- package/src/shared/audio-provider-suggestions.ts +4 -6
- package/src/shared/tool-implementations.ts +57 -16
|
@@ -9,9 +9,11 @@ import {
|
|
|
9
9
|
extractTraceId,
|
|
10
10
|
flushTracing,
|
|
11
11
|
SpanStatusCode,
|
|
12
|
+
stripEnv,
|
|
12
13
|
} from "@lobu/core";
|
|
13
14
|
import { z } from "zod";
|
|
14
15
|
import type { WorkerConfig, WorkerExecutor } from "../core/types";
|
|
16
|
+
import { SENSITIVE_WORKER_ENV_KEYS } from "../shared/worker-env-keys";
|
|
15
17
|
import { HttpWorkerTransport } from "./gateway-integration";
|
|
16
18
|
import { MessageBatcher } from "./message-batcher";
|
|
17
19
|
import type { MessagePayload, QueuedMessage } from "./types";
|
|
@@ -79,20 +81,33 @@ const AgentOptionsSchema = z
|
|
|
79
81
|
.passthrough();
|
|
80
82
|
|
|
81
83
|
const JobEventSchema = z.object({
|
|
82
|
-
payload: z
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
84
|
+
payload: z
|
|
85
|
+
.object({
|
|
86
|
+
botId: z.string(),
|
|
87
|
+
userId: z.string(),
|
|
88
|
+
agentId: z.string(),
|
|
89
|
+
conversationId: z.string(),
|
|
90
|
+
platform: z.string(),
|
|
91
|
+
channelId: z.string(),
|
|
92
|
+
messageId: z.string(),
|
|
93
|
+
messageText: z.string(),
|
|
94
|
+
platformMetadata: PlatformMetadataSchema,
|
|
95
|
+
agentOptions: AgentOptionsSchema,
|
|
96
|
+
jobId: z.string().optional(),
|
|
97
|
+
teamId: z.string().optional(), // Optional for WhatsApp (top-level) and Slack (in platformMetadata)
|
|
98
|
+
// Threaded through from MessageConsumer's runs-queue claim. The worker
|
|
99
|
+
// asserts these in snapshot mode (LOBU_SESSION_STORE != "file") — see
|
|
100
|
+
// worker.ts:353-360. The default zod object mode strips unknown keys,
|
|
101
|
+
// which silently dropped these fields and broke every Telegram chat
|
|
102
|
+
// when snapshot mode became the default in PR #871. Declare them
|
|
103
|
+
// explicitly so they survive parsing, and `.passthrough()` keeps any
|
|
104
|
+
// future MessagePayload field (mcpConfig, nixConfig, egressConfig,
|
|
105
|
+
// preApprovedTools, exec* fields, organizationId, networkConfig...)
|
|
106
|
+
// from regressing the same way.
|
|
107
|
+
runId: z.number().optional(),
|
|
108
|
+
runJobToken: z.string().optional(),
|
|
109
|
+
})
|
|
110
|
+
.passthrough(),
|
|
96
111
|
processedIds: z.array(z.string()).optional(),
|
|
97
112
|
});
|
|
98
113
|
|
|
@@ -574,16 +589,27 @@ export class GatewayClient {
|
|
|
574
589
|
});
|
|
575
590
|
|
|
576
591
|
let completed = false;
|
|
592
|
+
let sigkillTimer: NodeJS.Timeout | null = null;
|
|
577
593
|
|
|
578
594
|
try {
|
|
579
|
-
//
|
|
595
|
+
// Strip the worker's own gateway credentials before handing the shell
|
|
596
|
+
// its env. An `exec` command is an arbitrary string from the gateway
|
|
597
|
+
// that ends up under `sh -c`; leaking WORKER_TOKEN / DISPATCHER_URL
|
|
598
|
+
// into that environment would let a malicious or buggy exec impersonate
|
|
599
|
+
// the worker against its own gateway. The bash-tool and just-bash
|
|
600
|
+
// spawners already apply the same filter (see openclaw/tools.ts and
|
|
601
|
+
// embedded/just-bash-bootstrap.ts) — keep parity here.
|
|
602
|
+
const baseEnv = stripEnv(process.env, SENSITIVE_WORKER_ENV_KEYS);
|
|
580
603
|
const proc = spawn("sh", ["-c", execCommand], {
|
|
581
604
|
cwd: workingDir,
|
|
582
|
-
env: { ...
|
|
605
|
+
env: { ...baseEnv, ...execEnv },
|
|
583
606
|
stdio: ["ignore", "pipe", "pipe"],
|
|
584
607
|
});
|
|
585
608
|
|
|
586
|
-
// Setup timeout
|
|
609
|
+
// Setup timeout. The SIGKILL escalation timer is tracked so the `close`
|
|
610
|
+
// handler can clear it when the child exits between SIGTERM and SIGKILL;
|
|
611
|
+
// otherwise the timer pins the event loop for an extra 5s after every
|
|
612
|
+
// timed-out exec and (worse) leaks if `close`/`error` never fires.
|
|
587
613
|
const timeoutId = setTimeout(() => {
|
|
588
614
|
if (!completed) {
|
|
589
615
|
logger.warn(
|
|
@@ -591,7 +617,8 @@ export class GatewayClient {
|
|
|
591
617
|
"Exec timeout reached, killing process"
|
|
592
618
|
);
|
|
593
619
|
proc.kill("SIGTERM");
|
|
594
|
-
setTimeout(() => {
|
|
620
|
+
sigkillTimer = setTimeout(() => {
|
|
621
|
+
sigkillTimer = null;
|
|
595
622
|
if (!completed) {
|
|
596
623
|
proc.kill("SIGKILL");
|
|
597
624
|
}
|
|
@@ -600,7 +627,7 @@ export class GatewayClient {
|
|
|
600
627
|
}, timeout);
|
|
601
628
|
|
|
602
629
|
// Stream stdout
|
|
603
|
-
|
|
630
|
+
const onStdout = (chunk: Buffer) => {
|
|
604
631
|
const content = chunk.toString();
|
|
605
632
|
transport.sendExecOutput(execId, "stdout", content).catch((err) => {
|
|
606
633
|
logger.error(
|
|
@@ -608,10 +635,11 @@ export class GatewayClient {
|
|
|
608
635
|
"Failed to send stdout"
|
|
609
636
|
);
|
|
610
637
|
});
|
|
611
|
-
}
|
|
638
|
+
};
|
|
639
|
+
proc.stdout?.on("data", onStdout);
|
|
612
640
|
|
|
613
641
|
// Stream stderr
|
|
614
|
-
|
|
642
|
+
const onStderr = (chunk: Buffer) => {
|
|
615
643
|
const content = chunk.toString();
|
|
616
644
|
transport.sendExecOutput(execId, "stderr", content).catch((err) => {
|
|
617
645
|
logger.error(
|
|
@@ -619,19 +647,34 @@ export class GatewayClient {
|
|
|
619
647
|
"Failed to send stderr"
|
|
620
648
|
);
|
|
621
649
|
});
|
|
622
|
-
}
|
|
650
|
+
};
|
|
651
|
+
proc.stderr?.on("data", onStderr);
|
|
623
652
|
|
|
624
653
|
// Wait for process to complete
|
|
625
654
|
const exitCode = await new Promise<number>((resolve, reject) => {
|
|
626
655
|
proc.on("close", (code) => {
|
|
627
656
|
completed = true;
|
|
628
657
|
clearTimeout(timeoutId);
|
|
658
|
+
if (sigkillTimer) {
|
|
659
|
+
clearTimeout(sigkillTimer);
|
|
660
|
+
sigkillTimer = null;
|
|
661
|
+
}
|
|
662
|
+
// Stop accepting late `data` events so a chunk buffered after exit
|
|
663
|
+
// can't fire `sendExecOutput` AFTER we've signalled completion.
|
|
664
|
+
proc.stdout?.removeListener("data", onStdout);
|
|
665
|
+
proc.stderr?.removeListener("data", onStderr);
|
|
629
666
|
resolve(code ?? 0);
|
|
630
667
|
});
|
|
631
668
|
|
|
632
669
|
proc.on("error", (error) => {
|
|
633
670
|
completed = true;
|
|
634
671
|
clearTimeout(timeoutId);
|
|
672
|
+
if (sigkillTimer) {
|
|
673
|
+
clearTimeout(sigkillTimer);
|
|
674
|
+
sigkillTimer = null;
|
|
675
|
+
}
|
|
676
|
+
proc.stdout?.removeListener("data", onStdout);
|
|
677
|
+
proc.stderr?.removeListener("data", onStderr);
|
|
635
678
|
reject(error);
|
|
636
679
|
});
|
|
637
680
|
});
|
|
@@ -663,6 +706,13 @@ export class GatewayClient {
|
|
|
663
706
|
|
|
664
707
|
logger.error({ traceId, execId, error: errorMessage }, "Exec failed");
|
|
665
708
|
} finally {
|
|
709
|
+
// Defensive: if we threw before `close`/`error` fired (e.g. transport
|
|
710
|
+
// throwing during sendExecOutput on a long-running child), make sure
|
|
711
|
+
// the SIGKILL escalation timer doesn't outlive this exec.
|
|
712
|
+
if (sigkillTimer) {
|
|
713
|
+
clearTimeout(sigkillTimer);
|
|
714
|
+
sigkillTimer = null;
|
|
715
|
+
}
|
|
666
716
|
this.currentJobId = undefined;
|
|
667
717
|
}
|
|
668
718
|
}
|
|
@@ -882,6 +932,21 @@ export class GatewayClient {
|
|
|
882
932
|
workspace: {
|
|
883
933
|
baseDirectory: process.env.WORKSPACE_DIR || "/workspace",
|
|
884
934
|
},
|
|
935
|
+
// Threaded through from MessageConsumer (set from the runs-queue
|
|
936
|
+
// claim's job.id). Used by cleanup() to attribute the snapshot to
|
|
937
|
+
// the correct run; codex P1#1 on PR #865.
|
|
938
|
+
runId:
|
|
939
|
+
typeof payload.runId === "number" && Number.isFinite(payload.runId)
|
|
940
|
+
? payload.runId
|
|
941
|
+
: undefined,
|
|
942
|
+
// Per-run JWT minted by MessageConsumer alongside runId. Worker
|
|
943
|
+
// uses this for the snapshot POST instead of the deployment-
|
|
944
|
+
// lifetime WORKER_TOKEN, so the gateway can enforce
|
|
945
|
+
// tokenData.runId === body.runId — codex round 2 finding A.
|
|
946
|
+
runJobToken:
|
|
947
|
+
typeof payload.runJobToken === "string" && payload.runJobToken
|
|
948
|
+
? payload.runJobToken
|
|
949
|
+
: undefined,
|
|
885
950
|
};
|
|
886
951
|
}
|
|
887
952
|
|
package/src/gateway/types.ts
CHANGED
|
@@ -41,6 +41,21 @@ export interface MessagePayload {
|
|
|
41
41
|
jobId?: string; // Optional job ID from gateway
|
|
42
42
|
teamId?: string; // Optional team ID (WhatsApp uses top-level, Slack uses platformMetadata)
|
|
43
43
|
|
|
44
|
+
// The runs.id of the row that dispatched this job. Set by the gateway
|
|
45
|
+
// MessageConsumer (stamped from the runs-queue claim's job.id) and
|
|
46
|
+
// threaded into WorkerConfig.runId. The worker's cleanup() uses it to
|
|
47
|
+
// attribute the agent_transcript_snapshot row to the correct run —
|
|
48
|
+
// see codex P1#1 on PR #865.
|
|
49
|
+
runId?: number;
|
|
50
|
+
|
|
51
|
+
// Per-run worker JWT bound to `runId` above. Minted by MessageConsumer
|
|
52
|
+
// and threaded into WorkerConfig.runJobToken. The worker uses THIS
|
|
53
|
+
// token (not the deployment-lifetime WORKER_TOKEN) when calling the
|
|
54
|
+
// snapshot endpoint, so the route's `tokenData.runId === body.runId`
|
|
55
|
+
// equality check can reject any cross-run impersonation — codex round
|
|
56
|
+
// 2 finding A on PR #865.
|
|
57
|
+
runJobToken?: string;
|
|
58
|
+
|
|
44
59
|
// Job type (default: "message")
|
|
45
60
|
jobType?: JobType;
|
|
46
61
|
|
package/src/index.ts
CHANGED
|
@@ -16,7 +16,6 @@ import { startWorkerHttpServer, stopWorkerHttpServer } from "./server";
|
|
|
16
16
|
* Main entry point for gateway-based persistent worker
|
|
17
17
|
*/
|
|
18
18
|
async function main() {
|
|
19
|
-
// Register global rejection/exception handlers early
|
|
20
19
|
process.on("unhandledRejection", (reason) => {
|
|
21
20
|
logger.error("Unhandled rejection:", reason);
|
|
22
21
|
process.exit(1);
|
|
@@ -29,10 +28,8 @@ async function main() {
|
|
|
29
28
|
|
|
30
29
|
logger.info("Starting worker...");
|
|
31
30
|
|
|
32
|
-
// Initialize Sentry for error tracking
|
|
33
31
|
await initSentry();
|
|
34
32
|
|
|
35
|
-
// Initialize OpenTelemetry tracing for distributed tracing
|
|
36
33
|
const otlpEndpoint = process.env.OTEL_EXPORTER_OTLP_ENDPOINT;
|
|
37
34
|
if (otlpEndpoint) {
|
|
38
35
|
initTracing({
|
|
@@ -42,16 +39,12 @@ async function main() {
|
|
|
42
39
|
logger.info(`Tracing initialized: lobu-worker -> ${otlpEndpoint}`);
|
|
43
40
|
}
|
|
44
41
|
|
|
45
|
-
// Discover and register available modules
|
|
46
42
|
await moduleRegistry.registerAvailableModules();
|
|
47
|
-
|
|
48
|
-
// Initialize all registered modules
|
|
49
43
|
await moduleRegistry.initAll();
|
|
50
44
|
logger.info("✅ Modules initialized");
|
|
51
45
|
|
|
52
46
|
logger.info("🔄 Starting in gateway mode (SSE/HTTP-based persistent worker)");
|
|
53
47
|
|
|
54
|
-
// Get user ID from environment
|
|
55
48
|
const userId = process.env.USER_ID;
|
|
56
49
|
|
|
57
50
|
if (!userId) {
|
|
@@ -62,7 +55,6 @@ async function main() {
|
|
|
62
55
|
}
|
|
63
56
|
|
|
64
57
|
try {
|
|
65
|
-
// Get required environment variables
|
|
66
58
|
const deploymentName = process.env.DEPLOYMENT_NAME;
|
|
67
59
|
const dispatcherUrl = process.env.DISPATCHER_URL;
|
|
68
60
|
const workerToken = process.env.WORKER_TOKEN;
|
|
@@ -80,11 +72,9 @@ async function main() {
|
|
|
80
72
|
process.exit(1);
|
|
81
73
|
}
|
|
82
74
|
|
|
83
|
-
// Start HTTP server before connecting to gateway
|
|
84
75
|
const httpPort = await startWorkerHttpServer();
|
|
85
76
|
logger.info(`Worker HTTP server started on port ${httpPort}`);
|
|
86
77
|
|
|
87
|
-
// Initialize gateway client directly
|
|
88
78
|
logger.info(`🚀 Starting Gateway-based Persistent Worker`);
|
|
89
79
|
logger.info(`- User ID: ${userId}`);
|
|
90
80
|
logger.info(`- Deployment: ${deploymentName}`);
|
|
@@ -100,33 +90,25 @@ async function main() {
|
|
|
100
90
|
|
|
101
91
|
// Register signal handlers before async operations
|
|
102
92
|
let isShuttingDown = false;
|
|
103
|
-
|
|
104
|
-
process.on("SIGTERM", async () => {
|
|
93
|
+
const shutdown = async (signal: NodeJS.Signals) => {
|
|
105
94
|
if (isShuttingDown) return;
|
|
106
95
|
isShuttingDown = true;
|
|
107
|
-
logger.info(
|
|
96
|
+
logger.info(`Received ${signal}, shutting down gateway worker...`);
|
|
108
97
|
await gatewayClient.stop();
|
|
109
98
|
await stopWorkerHttpServer();
|
|
110
99
|
process.exit(0);
|
|
111
|
-
}
|
|
100
|
+
};
|
|
112
101
|
|
|
113
|
-
process.on("
|
|
114
|
-
|
|
115
|
-
isShuttingDown = true;
|
|
116
|
-
logger.info("Received SIGINT, shutting down gateway worker...");
|
|
117
|
-
await gatewayClient.stop();
|
|
118
|
-
await stopWorkerHttpServer();
|
|
119
|
-
process.exit(0);
|
|
120
|
-
});
|
|
102
|
+
process.on("SIGTERM", () => shutdown("SIGTERM"));
|
|
103
|
+
process.on("SIGINT", () => shutdown("SIGINT"));
|
|
121
104
|
|
|
122
105
|
logger.info("🔌 Connecting to dispatcher...");
|
|
123
106
|
await gatewayClient.start();
|
|
124
107
|
logger.info("✅ Gateway worker started successfully");
|
|
125
108
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
}); // Wait forever
|
|
109
|
+
await new Promise<never>(() => {
|
|
110
|
+
// Intentionally never resolves — block process until signal.
|
|
111
|
+
});
|
|
130
112
|
} catch (error) {
|
|
131
113
|
logger.error("❌ Gateway worker failed:", error);
|
|
132
114
|
process.exit(1);
|
|
@@ -20,10 +20,9 @@ export async function generateCustomInstructions(
|
|
|
20
20
|
context: InstructionContext
|
|
21
21
|
): Promise<string> {
|
|
22
22
|
try {
|
|
23
|
+
const sorted = [...providers].sort((a, b) => a.priority - b.priority);
|
|
23
24
|
const sections: string[] = [];
|
|
24
|
-
for (const provider of
|
|
25
|
-
(a, b) => a.priority - b.priority
|
|
26
|
-
)) {
|
|
25
|
+
for (const provider of sorted) {
|
|
27
26
|
const instructions = await provider.getInstructions(context);
|
|
28
27
|
if (instructions?.trim()) {
|
|
29
28
|
sections.push(instructions.trim());
|
|
@@ -99,16 +99,15 @@ async function loadSinglePlugin(
|
|
|
99
99
|
): Promise<LoadedPlugin | null> {
|
|
100
100
|
const { source, slot, config: pluginConfig } = config;
|
|
101
101
|
|
|
102
|
-
|
|
103
|
-
try {
|
|
104
|
-
mod = (await import(source)) as Record<string, unknown>;
|
|
105
|
-
} catch (err) {
|
|
102
|
+
const mod = await import(source).catch((err) => {
|
|
106
103
|
throw new Error(
|
|
107
104
|
`Cannot import "${source}": ${err instanceof Error ? err.message : String(err)}`
|
|
108
105
|
);
|
|
109
|
-
}
|
|
106
|
+
});
|
|
110
107
|
|
|
111
|
-
const pluginEntrypoint = resolvePluginEntrypoint(
|
|
108
|
+
const pluginEntrypoint = resolvePluginEntrypoint(
|
|
109
|
+
mod as Record<string, unknown>
|
|
110
|
+
);
|
|
112
111
|
if (!pluginEntrypoint) {
|
|
113
112
|
logger.warn(`Plugin "${source}" has no registerable entrypoint - skipping`);
|
|
114
113
|
return null;
|
|
@@ -224,22 +223,19 @@ function createShimApi(params: {
|
|
|
224
223
|
cwd,
|
|
225
224
|
} = params;
|
|
226
225
|
const noop = () => {
|
|
227
|
-
|
|
226
|
+
// No-op stub for shim plugin APIs that this loader does not implement.
|
|
228
227
|
};
|
|
229
228
|
|
|
229
|
+
const prefix = `[plugin:${extractPluginName(source)}]`;
|
|
230
230
|
const shimLogger = {
|
|
231
|
-
info(message: string, ...args: unknown[])
|
|
232
|
-
logger.info(
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
logger.
|
|
239
|
-
},
|
|
240
|
-
debug(message: string, ...args: unknown[]) {
|
|
241
|
-
logger.debug(`[plugin:${extractPluginName(source)}] ${message}`, ...args);
|
|
242
|
-
},
|
|
231
|
+
info: (message: string, ...args: unknown[]) =>
|
|
232
|
+
logger.info(`${prefix} ${message}`, ...args),
|
|
233
|
+
warn: (message: string, ...args: unknown[]) =>
|
|
234
|
+
logger.warn(`${prefix} ${message}`, ...args),
|
|
235
|
+
error: (message: string, ...args: unknown[]) =>
|
|
236
|
+
logger.error(`${prefix} ${message}`, ...args),
|
|
237
|
+
debug: (message: string, ...args: unknown[]) =>
|
|
238
|
+
logger.debug(`${prefix} ${message}`, ...args),
|
|
243
239
|
};
|
|
244
240
|
|
|
245
241
|
return {
|
|
@@ -89,12 +89,7 @@ export function checkSandboxLeak(
|
|
|
89
89
|
);
|
|
90
90
|
redacted = redacted.replace(
|
|
91
91
|
DELIVERY_PHRASE_RE,
|
|
92
|
-
|
|
93
|
-
// Reconstruct the phrase prefix (everything before the path) by
|
|
94
|
-
// re-matching on the original substring. Simpler: replace the whole
|
|
95
|
-
// match with a generic note.
|
|
96
|
-
return "[file was created but not uploaded — use `UploadUserFile` to deliver it]";
|
|
97
|
-
}
|
|
92
|
+
"[file was created but not uploaded — use `UploadUserFile` to deliver it]"
|
|
98
93
|
);
|
|
99
94
|
|
|
100
95
|
const note =
|
|
@@ -231,6 +231,9 @@ export async function getOpenClawSessionContext(
|
|
|
231
231
|
headers: {
|
|
232
232
|
Authorization: `Bearer ${workerToken}`,
|
|
233
233
|
},
|
|
234
|
+
// Session context is fetched once per turn; a stalled gateway here would
|
|
235
|
+
// otherwise hang the worker before the agent ever sees the prompt.
|
|
236
|
+
signal: AbortSignal.timeout(30_000),
|
|
234
237
|
});
|
|
235
238
|
|
|
236
239
|
if (!response.ok) {
|
|
@@ -86,10 +86,6 @@ export function isDirectPackageInstallCommand(command: string): boolean {
|
|
|
86
86
|
);
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
-
function normalizePattern(pattern: string): string {
|
|
90
|
-
return pattern.trim();
|
|
91
|
-
}
|
|
92
|
-
|
|
93
89
|
function normalizeToolName(name: string): string {
|
|
94
90
|
return name.trim().toLowerCase();
|
|
95
91
|
}
|
|
@@ -108,16 +104,13 @@ export function normalizeToolList(value?: string | string[]): string[] {
|
|
|
108
104
|
|
|
109
105
|
function parseBashFilter(pattern: string): string | null {
|
|
110
106
|
const match = pattern.match(/^Bash\(([^:]+):\*\)$/i);
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
}
|
|
114
|
-
const prefix = match[1]?.trim();
|
|
115
|
-
return prefix ? prefix : null;
|
|
107
|
+
const prefix = match?.[1]?.trim();
|
|
108
|
+
return prefix || null;
|
|
116
109
|
}
|
|
117
110
|
|
|
118
111
|
function matchesToolPattern(toolName: string, pattern: string): boolean {
|
|
119
112
|
const normalizedTool = normalizeToolName(toolName);
|
|
120
|
-
const normalizedPattern =
|
|
113
|
+
const normalizedPattern = pattern.trim();
|
|
121
114
|
const normalizedPatternLower = normalizedPattern.toLowerCase();
|
|
122
115
|
|
|
123
116
|
if (normalizedPattern === "*") {
|
|
@@ -145,11 +138,11 @@ export function buildToolPolicy(params: {
|
|
|
145
138
|
const mergedAllowed = [
|
|
146
139
|
...(toolsConfig?.allowedTools ?? []),
|
|
147
140
|
...allowedPatterns,
|
|
148
|
-
].map(
|
|
141
|
+
].map((p) => p.trim());
|
|
149
142
|
const mergedDenied = [
|
|
150
143
|
...(toolsConfig?.deniedTools ?? []),
|
|
151
144
|
...deniedPatterns,
|
|
152
|
-
].map(
|
|
145
|
+
].map((p) => p.trim());
|
|
153
146
|
|
|
154
147
|
const bashAllowPrefixes = mergedAllowed
|
|
155
148
|
.map((pattern) => parseBashFilter(pattern))
|