@2en/clawly-plugins 1.7.2 → 1.10.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/cron-hook.ts +23 -4
- package/gateway/memory.ts +45 -11
- package/index.ts +1 -0
- package/package.json +1 -1
- package/tools/clawly-send-message.ts +54 -0
- package/tools/index.ts +2 -0
package/cron-hook.ts
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
* before_tool_call hook for cron (action=add) — ensures delivery fields are
|
|
3
3
|
* always set correctly, even when the LLM omits them.
|
|
4
4
|
*
|
|
5
|
-
* Forces: delivery.mode = "none" (agent uses
|
|
5
|
+
* Forces: delivery.mode = "none" (agent uses clawly_send_message/push tools)
|
|
6
|
+
* Appends: delivery instructions to payload.message
|
|
6
7
|
* Patches: payload.kind "systemEvent" → "agentTurn"
|
|
7
8
|
*
|
|
8
9
|
* The cron tool name is "cron" (not "cron.create"). The LLM passes
|
|
@@ -18,15 +19,33 @@ function isRecord(v: unknown): v is UnknownRecord {
|
|
|
18
19
|
return typeof v === 'object' && v !== null && !Array.isArray(v)
|
|
19
20
|
}
|
|
20
21
|
|
|
22
|
+
const DELIVERY_SUFFIX = [
|
|
23
|
+
'',
|
|
24
|
+
'---',
|
|
25
|
+
'DELIVERY INSTRUCTIONS (mandatory):',
|
|
26
|
+
'When done, you MUST deliver your result to the user:',
|
|
27
|
+
'1. Call the clawly_send_message tool with a brief, natural summary of your result.',
|
|
28
|
+
'2. Call clawly_is_user_online to check if the user is online.',
|
|
29
|
+
'3. If offline, also call clawly_send_app_push with a short notification.',
|
|
30
|
+
].join('\n')
|
|
31
|
+
|
|
21
32
|
function patchJob(job: UnknownRecord): UnknownRecord {
|
|
22
33
|
const patched: UnknownRecord = {...job}
|
|
23
34
|
|
|
24
|
-
// Force delivery.mode = "none" — agent reports via
|
|
35
|
+
// Force delivery.mode = "none" — agent reports via tools explicitly
|
|
25
36
|
patched.delivery = {mode: 'none'}
|
|
26
37
|
|
|
38
|
+
// Append delivery instructions to payload.message
|
|
39
|
+
if (isRecord(job.payload) && typeof job.payload.message === 'string') {
|
|
40
|
+
patched.payload = {
|
|
41
|
+
...job.payload,
|
|
42
|
+
message: job.payload.message + DELIVERY_SUFFIX,
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
27
46
|
// Patch payload.kind: systemEvent → agentTurn
|
|
28
|
-
if (isRecord(
|
|
29
|
-
patched.payload = {...
|
|
47
|
+
if (isRecord(patched.payload) && (patched.payload as UnknownRecord).kind === 'systemEvent') {
|
|
48
|
+
patched.payload = {...(patched.payload as UnknownRecord), kind: 'agentTurn'}
|
|
30
49
|
}
|
|
31
50
|
|
|
32
51
|
return patched
|
package/gateway/memory.ts
CHANGED
|
@@ -37,21 +37,25 @@ function coercePluginConfig(api: PluginApi): Record<string, unknown> {
|
|
|
37
37
|
return isRecord(api.pluginConfig) ? api.pluginConfig : {}
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
/** Resolve the
|
|
41
|
-
function
|
|
40
|
+
/** Resolve the workspace root directory (without /memory suffix). */
|
|
41
|
+
function resolveWorkspaceRoot(api: PluginApi, profile?: string): string {
|
|
42
42
|
const cfg = coercePluginConfig(api)
|
|
43
43
|
const configPath = configString(cfg, 'memoryDir')
|
|
44
|
-
if (configPath) return configPath
|
|
44
|
+
if (configPath) return path.dirname(configPath) // strip /memory if configured
|
|
45
45
|
|
|
46
46
|
const baseDir =
|
|
47
47
|
process.env.OPENCLAW_WORKSPACE ?? path.join(os.homedir(), '.openclaw', 'workspace')
|
|
48
|
-
// Profile-aware: "main" or empty → default workspace, otherwise workspace-<profile>
|
|
49
48
|
if (profile && profile !== 'main') {
|
|
50
49
|
const parentDir = path.dirname(baseDir)
|
|
51
50
|
const baseName = path.basename(baseDir)
|
|
52
|
-
return path.join(parentDir, `${baseName}-${profile}
|
|
51
|
+
return path.join(parentDir, `${baseName}-${profile}`)
|
|
53
52
|
}
|
|
54
|
-
return
|
|
53
|
+
return baseDir
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Resolve the memory directory path. Memory lives under workspace (agents.defaults.workspace). */
|
|
57
|
+
function resolveMemoryDir(api: PluginApi, profile?: string): string {
|
|
58
|
+
return path.join(resolveWorkspaceRoot(api, profile), 'memory')
|
|
55
59
|
}
|
|
56
60
|
|
|
57
61
|
/** Check that a relative path is safe (no directory traversal). */
|
|
@@ -89,6 +93,21 @@ async function collectMdFiles(dir: string, baseDir: string, acc: string[] = []):
|
|
|
89
93
|
return acc
|
|
90
94
|
}
|
|
91
95
|
|
|
96
|
+
/** List only root-level .md files in a directory (non-recursive). */
|
|
97
|
+
async function listRootMdFiles(dir: string): Promise<string[]> {
|
|
98
|
+
let entries: fs.Dirent[]
|
|
99
|
+
try {
|
|
100
|
+
entries = await fs.readdir(dir, {withFileTypes: true})
|
|
101
|
+
} catch (err) {
|
|
102
|
+
if ((err as NodeJS.ErrnoException).code === 'ENOENT') return []
|
|
103
|
+
throw err
|
|
104
|
+
}
|
|
105
|
+
return entries
|
|
106
|
+
.filter((e) => e.isFile() && e.name.toLowerCase().endsWith('.md'))
|
|
107
|
+
.map((e) => e.name)
|
|
108
|
+
.sort()
|
|
109
|
+
}
|
|
110
|
+
|
|
92
111
|
export function registerMemoryBrowser(api: PluginApi) {
|
|
93
112
|
const memoryDir = resolveMemoryDir(api)
|
|
94
113
|
api.logger.info(`memory-browser: memory directory: ${memoryDir}`)
|
|
@@ -99,10 +118,19 @@ export function registerMemoryBrowser(api: PluginApi) {
|
|
|
99
118
|
api.registerGatewayMethod('memory-browser.list', async ({params, respond}) => {
|
|
100
119
|
try {
|
|
101
120
|
const profile = readString(params, 'profile')
|
|
102
|
-
const
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
121
|
+
const scope = readString(params, 'scope') ?? 'memory'
|
|
122
|
+
|
|
123
|
+
let files: string[]
|
|
124
|
+
if (scope === 'root') {
|
|
125
|
+
const wsRoot = resolveWorkspaceRoot(api, profile)
|
|
126
|
+
files = await listRootMdFiles(wsRoot)
|
|
127
|
+
api.logger.info(`memory-browser.list (root): ${files.length} files found in ${wsRoot}`)
|
|
128
|
+
} else {
|
|
129
|
+
const dir = profile ? resolveMemoryDir(api, profile) : memoryDir
|
|
130
|
+
files = await collectMdFiles(dir, dir)
|
|
131
|
+
files.sort()
|
|
132
|
+
api.logger.info(`memory-browser.list: ${files.length} files found`)
|
|
133
|
+
}
|
|
106
134
|
respond(true, {files})
|
|
107
135
|
} catch (err) {
|
|
108
136
|
api.logger.error(
|
|
@@ -121,7 +149,13 @@ export function registerMemoryBrowser(api: PluginApi) {
|
|
|
121
149
|
api.registerGatewayMethod('memory-browser.get', async ({params, respond}) => {
|
|
122
150
|
const relativePath = readString(params, 'path')
|
|
123
151
|
const profile = readString(params, 'profile')
|
|
124
|
-
const
|
|
152
|
+
const scope = readString(params, 'scope') ?? 'memory'
|
|
153
|
+
const dir =
|
|
154
|
+
scope === 'root'
|
|
155
|
+
? resolveWorkspaceRoot(api, profile)
|
|
156
|
+
: profile
|
|
157
|
+
? resolveMemoryDir(api, profile)
|
|
158
|
+
: memoryDir
|
|
125
159
|
if (!relativePath) {
|
|
126
160
|
respond(false, undefined, {
|
|
127
161
|
code: 'invalid_params',
|
package/index.ts
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
* Agent tools:
|
|
16
16
|
* - clawly_is_user_online — check if user's device is connected
|
|
17
17
|
* - clawly_send_app_push — send a push notification to user's device
|
|
18
|
+
* - clawly_send_message — send a message to user via main session agent
|
|
18
19
|
*
|
|
19
20
|
* Commands:
|
|
20
21
|
* - /clawly_echo — echo text back without LLM
|
package/package.json
CHANGED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent tool: clawly_send_message — send a message to the main session
|
|
3
|
+
* agent from an isolated context (e.g. cron job).
|
|
4
|
+
*
|
|
5
|
+
* Runs `openclaw agent --agent <agent> --message <message>` which delivers
|
|
6
|
+
* the text as a natural assistant response in the user's chat.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import {$} from 'zx'
|
|
10
|
+
import type {PluginApi} from '../index'
|
|
11
|
+
|
|
12
|
+
$.verbose = false
|
|
13
|
+
|
|
14
|
+
const TOOL_NAME = 'clawly_send_message'
|
|
15
|
+
|
|
16
|
+
const parameters: Record<string, unknown> = {
|
|
17
|
+
type: 'object',
|
|
18
|
+
required: ['message'],
|
|
19
|
+
properties: {
|
|
20
|
+
message: {type: 'string', description: 'The message to send to the user'},
|
|
21
|
+
agent: {type: 'string', description: 'Target agent ID (default: "clawly")'},
|
|
22
|
+
},
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function registerSendMessageTool(api: PluginApi) {
|
|
26
|
+
api.registerTool({
|
|
27
|
+
name: TOOL_NAME,
|
|
28
|
+
description:
|
|
29
|
+
'Send a message to the user via the main session agent. Use this from cron jobs or isolated sessions to deliver results to the user.',
|
|
30
|
+
parameters,
|
|
31
|
+
async execute(_toolCallId, params) {
|
|
32
|
+
const message = typeof params.message === 'string' ? params.message.trim() : ''
|
|
33
|
+
if (!message) {
|
|
34
|
+
return {content: [{type: 'text', text: JSON.stringify({error: 'message is required'})}]}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const agent = typeof params.agent === 'string' ? params.agent.trim() : 'clawly'
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
await $`openclaw agent --agent ${agent} --message ${message}`
|
|
41
|
+
api.logger.info(
|
|
42
|
+
`${TOOL_NAME}: delivered message (${message.length} chars) to agent ${agent}`,
|
|
43
|
+
)
|
|
44
|
+
return {content: [{type: 'text', text: JSON.stringify({sent: true})}]}
|
|
45
|
+
} catch (err) {
|
|
46
|
+
const msg = err instanceof Error ? err.message : String(err)
|
|
47
|
+
api.logger.error(`${TOOL_NAME}: failed — ${msg}`)
|
|
48
|
+
return {content: [{type: 'text', text: JSON.stringify({sent: false, error: msg})}]}
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
api.logger.info(`tool: registered ${TOOL_NAME} agent tool`)
|
|
54
|
+
}
|
package/tools/index.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import type {PluginApi} from '../index'
|
|
2
2
|
import {registerIsUserOnlineTool} from './clawly-is-user-online'
|
|
3
3
|
import {registerSendAppPushTool} from './clawly-send-app-push'
|
|
4
|
+
import {registerSendMessageTool} from './clawly-send-message'
|
|
4
5
|
|
|
5
6
|
export function registerTools(api: PluginApi) {
|
|
6
7
|
registerIsUserOnlineTool(api)
|
|
7
8
|
registerSendAppPushTool(api)
|
|
9
|
+
registerSendMessageTool(api)
|
|
8
10
|
}
|