@2en/clawly-plugins 1.22.0 → 1.22.2

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.
@@ -72,19 +72,19 @@ async function withPluginBackup<T>(
72
72
  if (!stateDir) return operation()
73
73
 
74
74
  const extensionDir = path.join(stateDir, 'extensions', pluginId)
75
- const backupDir = `${extensionDir}.update-backup`
75
+ const backupZip = `${extensionDir}.update-backup.zip`
76
76
 
77
77
  // Back up only if there's a valid existing installation.
78
78
  // If backup fails, abort — a working plugin is more valuable than a risky update.
79
79
  const hasExisting = fs.existsSync(path.join(extensionDir, PLUGIN_SENTINEL))
80
80
  if (hasExisting) {
81
81
  try {
82
- if (fs.existsSync(backupDir)) await $`rm -rf ${backupDir}`
83
- await $`cp -a ${extensionDir} ${backupDir}`
82
+ if (fs.existsSync(backupZip)) fs.unlinkSync(backupZip)
83
+ const extDir = path.join(stateDir, 'extensions')
84
+ await $({cwd: extDir})`zip -rqy ${backupZip} ${pluginId}`
84
85
  api.logger.info(`plugins: backed up ${pluginId}`)
85
86
  } catch (err) {
86
- // Clean up partial backup before aborting
87
- if (fs.existsSync(backupDir)) await $`rm -rf ${backupDir}`.catch(() => {})
87
+ if (fs.existsSync(backupZip)) fs.unlinkSync(backupZip)
88
88
  throw new Error(
89
89
  `plugin backup failed, aborting update: ${err instanceof Error ? err.message : String(err)}`,
90
90
  )
@@ -104,17 +104,16 @@ async function withPluginBackup<T>(
104
104
  }
105
105
 
106
106
  // Clean backup on success
107
- if (fs.existsSync(backupDir)) {
108
- await $`rm -rf ${backupDir}`.catch(() => {})
109
- }
107
+ if (fs.existsSync(backupZip)) fs.unlinkSync(backupZip)
110
108
 
111
109
  return result
112
110
  } catch (err) {
113
- if (fs.existsSync(backupDir)) {
111
+ if (fs.existsSync(backupZip)) {
114
112
  api.logger.warn(`plugins: rolling back ${pluginId} from backup`)
115
113
  try {
116
114
  if (fs.existsSync(extensionDir)) await $`rm -rf ${extensionDir}`
117
- await $`mv ${backupDir} ${extensionDir}`
115
+ await $`unzip -qo ${backupZip} -d ${path.join(stateDir, 'extensions')}`
116
+ fs.unlinkSync(backupZip)
118
117
  api.logger.info(`plugins: rollback complete`)
119
118
  } catch (rollbackErr) {
120
119
  api.logger.error(
package/index.ts CHANGED
@@ -37,12 +37,28 @@ import {registerEmail} from './email'
37
37
  import {registerGateway} from './gateway'
38
38
  import {getGatewayConfig} from './gateway-fetch'
39
39
  import {setupModelGateway} from './model-gateway-setup'
40
- import {setupSession} from './session-setup'
41
40
  import {registerOutboundHook, registerOutboundHttpRoute, registerOutboundMethods} from './outbound'
41
+ import {setupSession} from './session-setup'
42
42
  import {registerSkillCommandRestore} from './skill-command-restore'
43
43
  import {registerTools} from './tools'
44
44
 
45
- type PluginRuntime = {
45
+ // ---------------------------------------------------------------------------
46
+ // Types aligned with OpenClaw plugin API (openclaw/src/plugins/types.ts)
47
+ // ---------------------------------------------------------------------------
48
+
49
+ export type PluginLogger = {
50
+ debug?: (message: string) => void
51
+ info: (message: string) => void
52
+ warn: (message: string) => void
53
+ error: (message: string) => void
54
+ }
55
+
56
+ export type PluginRuntime = {
57
+ version?: string
58
+ config?: {
59
+ loadConfig?: (...args: unknown[]) => unknown
60
+ writeConfigFile?: (...args: unknown[]) => unknown
61
+ }
46
62
  system?: {
47
63
  runCommandWithTimeout?: (
48
64
  argv: string[],
@@ -60,31 +76,43 @@ type PluginRuntime = {
60
76
  }
61
77
  }
62
78
 
79
+ export type GatewayRequestHandler = (opts: {
80
+ params: Record<string, unknown>
81
+ respond: (ok: boolean, payload?: unknown, error?: {code?: string; message?: string}) => void
82
+ }) => Promise<void> | void
83
+
84
+ export type PluginHookName =
85
+ | 'before_agent_start'
86
+ | 'agent_end'
87
+ | 'before_compaction'
88
+ | 'after_compaction'
89
+ | 'before_reset'
90
+ | 'message_received'
91
+ | 'message_sending'
92
+ | 'message_sent'
93
+ | 'before_tool_call'
94
+ | 'after_tool_call'
95
+ | 'tool_result_persist'
96
+ | 'session_start'
97
+ | 'session_end'
98
+ | 'gateway_start'
99
+ | 'gateway_stop'
100
+
63
101
  export type PluginApi = {
102
+ /** id from openclaw.plugin.json */
64
103
  id: string
104
+ /** name from openclaw.plugin.json */
65
105
  name: string
106
+ /** version from openclaw.plugin.json */
107
+ version?: string
108
+ /** description from openclaw.plugin.json */
109
+ description?: string
110
+ source?: string
111
+ config?: Record<string, unknown>
66
112
  pluginConfig?: Record<string, unknown>
67
- logger: {
68
- info: (msg: string) => void
69
- warn: (msg: string) => void
70
- error: (msg: string) => void
71
- }
72
113
  runtime: PluginRuntime
73
- registerGatewayMethod: (
74
- method: string,
75
- handler: (opts: {
76
- params: Record<string, unknown>
77
- respond: (ok: boolean, payload?: unknown, error?: {code?: string; message?: string}) => void
78
- }) => Promise<void> | void,
79
- ) => void
80
- on: (hookName: string, handler: (...args: any[]) => any, opts?: {priority?: number}) => void
81
- registerCommand: (cmd: {
82
- name: string
83
- description?: string
84
- acceptsArgs?: boolean
85
- requireAuth?: boolean
86
- handler: (ctx: {args?: string}) => Promise<{text: string}> | {text: string}
87
- }) => void
114
+ logger: PluginLogger
115
+ registerGatewayMethod: (method: string, handler: GatewayRequestHandler) => void
88
116
  registerTool: (
89
117
  tool: {
90
118
  name: string
@@ -97,7 +125,28 @@ export type PluginApi = {
97
125
  },
98
126
  opts?: {optional?: boolean},
99
127
  ) => void
100
- registerChannel: (registration: {plugin: any}) => void
128
+ registerHook?: (
129
+ events: string | string[],
130
+ handler: (...args: unknown[]) => unknown,
131
+ opts?: {name?: string; description?: string; register?: boolean},
132
+ ) => void
133
+ registerCommand: (cmd: {
134
+ name: string
135
+ description?: string
136
+ acceptsArgs?: boolean
137
+ requireAuth?: boolean
138
+ handler: (ctx: {
139
+ args?: string
140
+ [key: string]: unknown
141
+ }) => Promise<{text: string}> | {text: string}
142
+ }) => void
143
+ registerChannel: (registration: {plugin: unknown; dock?: unknown}) => void
144
+ registerHttpHandler?: (
145
+ handler: (
146
+ req: import('node:http').IncomingMessage,
147
+ res: import('node:http').ServerResponse,
148
+ ) => Promise<boolean> | boolean,
149
+ ) => void
101
150
  registerHttpRoute: (params: {
102
151
  path: string
103
152
  handler: (
@@ -105,6 +154,22 @@ export type PluginApi = {
105
154
  res: import('node:http').ServerResponse,
106
155
  ) => Promise<void> | void
107
156
  }) => void
157
+ registerCli?: (
158
+ registrar: (...args: unknown[]) => void | Promise<void>,
159
+ opts?: {commands?: string[]},
160
+ ) => void
161
+ registerService?: (service: {
162
+ id: string
163
+ start: (...args: unknown[]) => void | Promise<void>
164
+ stop?: (...args: unknown[]) => void | Promise<void>
165
+ }) => void
166
+ registerProvider?: (provider: Record<string, unknown>) => void
167
+ resolvePath?: (input: string) => string
168
+ on: (
169
+ hookName: PluginHookName | (string & {}),
170
+ handler: (...args: any[]) => any,
171
+ opts?: {priority?: number},
172
+ ) => void
108
173
  }
109
174
 
110
175
  export default {
@@ -36,9 +36,18 @@ describe('stripCliLogs', () => {
36
36
  expect(stripCliLogs('')).toBe('')
37
37
  })
38
38
 
39
- test('does not strip lines that merely contain a timestamp mid-line', () => {
40
- const input = 'some prefix 11:40:09 [x] y\n{"ok":true}'
41
- expect(stripCliLogs(input)).toBe('some prefix 11:40:09 [x] y\n{"ok":true}')
39
+ test('strips config warnings box between logs and JSON', () => {
40
+ const input = [
41
+ '04:49:48 [plugins] feishu_wiki: Registered feishu_wiki tool',
42
+ '│',
43
+ '◇ Config warnings ──────────────────────╮',
44
+ '│ │',
45
+ '│ - plugins.entries.clawly-plugins: ... │',
46
+ '│ │',
47
+ '├─────────────────────────────────────────╯',
48
+ '{"channel":"discord","requests":[]}',
49
+ ].join('\n')
50
+ expect(stripCliLogs(input)).toBe('{"channel":"discord","requests":[]}')
42
51
  })
43
52
 
44
53
  test('strips ANSI color codes from log lines', () => {
@@ -68,4 +77,9 @@ describe('stripCliLogs', () => {
68
77
  const input = '[{"id":"test"}]'
69
78
  expect(stripCliLogs(input)).toBe('[{"id":"test"}]')
70
79
  })
80
+
81
+ test('handles pretty-printed JSON array starting with lone bracket', () => {
82
+ const input = ['11:40:09 [plugins] loading', '[', ' {"id":"test"}', ']'].join('\n')
83
+ expect(stripCliLogs(input)).toBe('[\n {"id":"test"}\n]')
84
+ })
71
85
  })
@@ -8,18 +8,21 @@
8
8
  * \x1b[35m[plugins] tool: registered clawly_send_app_push agent tool
9
9
  * [{"id":"clawly-plugins", ...}]
10
10
  *
11
- * This function strips ANSI escape sequences, drops every line that looks
12
- * like a log prefix (`HH:MM:SS [tag] ...` or `[tag] ...`), and returns the
11
+ * It may also emit a "Config warnings" box using box-drawing characters
12
+ * (│, ◇, ├, ─, ╮, ╯) between log lines and the JSON body.
13
+ *
14
+ * This function strips ANSI escape sequences, drops every leading line that
15
+ * is not JSON (log prefixes, warning boxes, blank lines), and returns the
13
16
  * remaining text (the JSON body).
14
17
  */
15
18
 
16
19
  const ANSI_RE = /\x1b\[[0-9;]*m/g
17
- const LOG_LINE_RE = /^(\d{1,2}:\d{2}:\d{2} )?\[\w[\w-]*\] /
20
+ const JSON_START_RE = /^\{|^\[$|^\[[\s\][{"]/
18
21
 
19
22
  export function stripCliLogs(output: string): string {
20
23
  const cleaned = output.replace(ANSI_RE, '')
21
24
  const lines = cleaned.split('\n')
22
- const firstNonLog = lines.findIndex((line) => !LOG_LINE_RE.test(line))
23
- if (firstNonLog === -1) return ''
24
- return lines.slice(firstNonLog).join('\n').trim()
25
+ const firstJson = lines.findIndex((line) => JSON_START_RE.test(line))
26
+ if (firstJson === -1) return ''
27
+ return lines.slice(firstJson).join('\n').trim()
25
28
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@2en/clawly-plugins",
3
- "version": "1.22.0",
3
+ "version": "1.22.2",
4
4
  "module": "index.ts",
5
5
  "type": "module",
6
6
  "repository": {