@getplumb/openclaw-plugin 0.1.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/LICENSE +21 -0
- package/README.md +45 -0
- package/dist/config.d.ts +14 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +7 -0
- package/dist/config.js.map +1 -0
- package/dist/error-logger.d.ts +17 -0
- package/dist/error-logger.d.ts.map +1 -0
- package/dist/error-logger.js +40 -0
- package/dist/error-logger.js.map +1 -0
- package/dist/hooks/post-exchange.d.ts +31 -0
- package/dist/hooks/post-exchange.d.ts.map +1 -0
- package/dist/hooks/post-exchange.js +52 -0
- package/dist/hooks/post-exchange.js.map +1 -0
- package/dist/hooks/pre-response.d.ts +31 -0
- package/dist/hooks/pre-response.d.ts.map +1 -0
- package/dist/hooks/pre-response.js +81 -0
- package/dist/hooks/pre-response.js.map +1 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +40 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp-client.d.ts +41 -0
- package/dist/mcp-client.d.ts.map +1 -0
- package/dist/mcp-client.js +64 -0
- package/dist/mcp-client.js.map +1 -0
- package/dist/nudge.d.ts +49 -0
- package/dist/nudge.d.ts.map +1 -0
- package/dist/nudge.js +97 -0
- package/dist/nudge.js.map +1 -0
- package/dist/plugin-module.d.ts +37 -0
- package/dist/plugin-module.d.ts.map +1 -0
- package/dist/plugin-module.js +43 -0
- package/dist/plugin-module.js.map +1 -0
- package/package.json +40 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Plumb Contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# @getplumb/openclaw-plugin
|
|
2
|
+
|
|
3
|
+
> Plumb memory plugin for [OpenClaw](https://openclaw.ai)
|
|
4
|
+
|
|
5
|
+
Automatically ingests conversations and injects relevant memory context into every AI response — no "remember this" commands needed.
|
|
6
|
+
|
|
7
|
+
## What it does
|
|
8
|
+
|
|
9
|
+
- **Auto-ingest:** every conversation turn is stored to your local Plumb DB after the response
|
|
10
|
+
- **Context injection:** relevant memory facts are injected into the system prompt before each response
|
|
11
|
+
- **Shadow mode:** try it without injection first — observe what would be injected
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install @getplumb/openclaw-plugin
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Then add to your `openclaw.json`:
|
|
20
|
+
|
|
21
|
+
```json
|
|
22
|
+
{
|
|
23
|
+
"plugins": {
|
|
24
|
+
"plumb": {
|
|
25
|
+
"package": "@getplumb/openclaw-plugin",
|
|
26
|
+
"dbPath": "~/.plumb/memory.db",
|
|
27
|
+
"userId": "your-user-id",
|
|
28
|
+
"shadowMode": false
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"slots": {
|
|
32
|
+
"memory": "plumb"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Links
|
|
38
|
+
|
|
39
|
+
- [Docs](https://docs.getplumb.dev)
|
|
40
|
+
- [GitHub](https://github.com/getplumb/plumb)
|
|
41
|
+
- [getplumb.dev](https://getplumb.dev)
|
|
42
|
+
|
|
43
|
+
## License
|
|
44
|
+
|
|
45
|
+
MIT
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface PlumbPluginConfig {
|
|
2
|
+
/** Path to the @plumb/mcp-server binary (e.g. node_modules/.bin/plumb-mcp). */
|
|
3
|
+
mcpServerPath: string;
|
|
4
|
+
/** User identifier for multi-user setups. */
|
|
5
|
+
userId: string;
|
|
6
|
+
/** Whether the plugin is active. */
|
|
7
|
+
enabled: boolean;
|
|
8
|
+
/** Path to the Plumb database file. If not provided, defaults to ~/.plumb/memory.db */
|
|
9
|
+
dbPath?: string;
|
|
10
|
+
/** If true: retrieve but don't inject (for validation). Default: false. */
|
|
11
|
+
shadowMode: boolean;
|
|
12
|
+
}
|
|
13
|
+
export declare const DEFAULT_CONFIG: PlumbPluginConfig;
|
|
14
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,iBAAiB;IAChC,+EAA+E;IAC/E,aAAa,EAAE,MAAM,CAAC;IAEtB,6CAA6C;IAC7C,MAAM,EAAE,MAAM,CAAC;IAEf,oCAAoC;IACpC,OAAO,EAAE,OAAO,CAAC;IAEjB,uFAAuF;IACvF,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB,2EAA2E;IAC3E,UAAU,EAAE,OAAO,CAAC;CACrB;AAED,eAAO,MAAM,cAAc,EAAE,iBAK5B,CAAC"}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAiBA,MAAM,CAAC,MAAM,cAAc,GAAsB;IAC/C,aAAa,EAAE,WAAW;IAC1B,MAAM,EAAE,SAAS;IACjB,OAAO,EAAE,IAAI;IACb,UAAU,EAAE,KAAK;CAClB,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export interface ErrorLogEntry {
|
|
2
|
+
timestamp: string;
|
|
3
|
+
type: string;
|
|
4
|
+
message: string;
|
|
5
|
+
stack?: string;
|
|
6
|
+
context?: Record<string, unknown>;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Appends an error entry to ~/.plumb/errors.log (or PLUMB_DB_PATH parent if set).
|
|
10
|
+
* Format: JSONL (one JSON object per line).
|
|
11
|
+
*
|
|
12
|
+
* This function never throws — if the write fails, it silently logs to console.error only.
|
|
13
|
+
*
|
|
14
|
+
* @param entry - Error log entry with timestamp, type, message, and optional stack/context
|
|
15
|
+
*/
|
|
16
|
+
export declare function appendError(entry: ErrorLogEntry): void;
|
|
17
|
+
//# sourceMappingURL=error-logger.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error-logger.d.ts","sourceRoot":"","sources":["../src/error-logger.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACnC;AAkBD;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,aAAa,GAAG,IAAI,CAetD"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { homedir } from 'node:os';
|
|
2
|
+
import { join, dirname } from 'node:path';
|
|
3
|
+
import { appendFileSync, mkdirSync } from 'node:fs';
|
|
4
|
+
/**
|
|
5
|
+
* Derives the error log path from the DB path (if set) or defaults to ~/.plumb/errors.log.
|
|
6
|
+
* If PLUMB_DB_PATH environment variable is set, we use its parent directory.
|
|
7
|
+
*/
|
|
8
|
+
function getErrorLogPath() {
|
|
9
|
+
const dbPath = process.env.PLUMB_DB_PATH;
|
|
10
|
+
if (dbPath) {
|
|
11
|
+
// Use the parent directory of the DB path
|
|
12
|
+
return join(dirname(dbPath), 'errors.log');
|
|
13
|
+
}
|
|
14
|
+
// Default: ~/.plumb/errors.log
|
|
15
|
+
return join(homedir(), '.plumb', 'errors.log');
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Appends an error entry to ~/.plumb/errors.log (or PLUMB_DB_PATH parent if set).
|
|
19
|
+
* Format: JSONL (one JSON object per line).
|
|
20
|
+
*
|
|
21
|
+
* This function never throws — if the write fails, it silently logs to console.error only.
|
|
22
|
+
*
|
|
23
|
+
* @param entry - Error log entry with timestamp, type, message, and optional stack/context
|
|
24
|
+
*/
|
|
25
|
+
export function appendError(entry) {
|
|
26
|
+
try {
|
|
27
|
+
const logPath = getErrorLogPath();
|
|
28
|
+
const logDir = dirname(logPath);
|
|
29
|
+
// Ensure directory exists
|
|
30
|
+
mkdirSync(logDir, { recursive: true });
|
|
31
|
+
// Write JSONL: one JSON object per line
|
|
32
|
+
const line = JSON.stringify(entry) + '\n';
|
|
33
|
+
appendFileSync(logPath, line, 'utf-8');
|
|
34
|
+
}
|
|
35
|
+
catch (err) {
|
|
36
|
+
// Non-blocking: if the log write itself fails, just console.error
|
|
37
|
+
console.error('[plumb/error-logger] Failed to write error log:', err);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=error-logger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error-logger.js","sourceRoot":"","sources":["../src/error-logger.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAUpD;;;GAGG;AACH,SAAS,eAAe;IACtB,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC;IAEzC,IAAI,MAAM,EAAE,CAAC;QACX,0CAA0C;QAC1C,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,YAAY,CAAC,CAAC;IAC7C,CAAC;IAED,+BAA+B;IAC/B,OAAO,IAAI,CAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,YAAY,CAAC,CAAC;AACjD,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,WAAW,CAAC,KAAoB;IAC9C,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,eAAe,EAAE,CAAC;QAClC,MAAM,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;QAEhC,0BAA0B;QAC1B,SAAS,CAAC,MAAM,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAEvC,wCAAwC;QACxC,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC;QAC1C,cAAc,CAAC,OAAO,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC;IACzC,CAAC;IAAC,OAAO,GAAY,EAAE,CAAC;QACtB,kEAAkE;QAClE,OAAO,CAAC,KAAK,CAAC,iDAAiD,EAAE,GAAG,CAAC,CAAC;IACxE,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { LocalStore } from '@getplumb/core';
|
|
2
|
+
type PluginHookLlmOutputEvent = {
|
|
3
|
+
runId: string;
|
|
4
|
+
sessionId: string;
|
|
5
|
+
provider: string;
|
|
6
|
+
model: string;
|
|
7
|
+
assistantTexts: string[];
|
|
8
|
+
usage?: {
|
|
9
|
+
input?: number;
|
|
10
|
+
output?: number;
|
|
11
|
+
cacheRead?: number;
|
|
12
|
+
cacheWrite?: number;
|
|
13
|
+
total?: number;
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
type PluginHookAgentContext = {
|
|
17
|
+
agentId?: string;
|
|
18
|
+
sessionKey?: string;
|
|
19
|
+
sessionId?: string;
|
|
20
|
+
workspaceDir?: string;
|
|
21
|
+
messageProvider?: string;
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Creates a hook handler that ingests every LLM exchange into the local store.
|
|
25
|
+
*
|
|
26
|
+
* Fire-and-forget pattern: returns void synchronously, ingests in background.
|
|
27
|
+
* Errors are caught and logged silently to avoid disrupting the agent flow.
|
|
28
|
+
*/
|
|
29
|
+
export declare function createPostExchangeHook(store: LocalStore, userId: string): (event: PluginHookLlmOutputEvent, ctx: PluginHookAgentContext) => void;
|
|
30
|
+
export {};
|
|
31
|
+
//# sourceMappingURL=post-exchange.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"post-exchange.d.ts","sourceRoot":"","sources":["../../src/hooks/post-exchange.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAIjD,KAAK,wBAAwB,GAAG;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,KAAK,CAAC,EAAE;QACN,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,CAAC;CACH,CAAC;AAEF,KAAK,sBAAsB,GAAG;IAC5B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,IAC9D,OAAO,wBAAwB,EAAE,KAAK,sBAAsB,KAAG,IAAI,CA4C5E"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { appendError } from '../error-logger.js';
|
|
2
|
+
/**
|
|
3
|
+
* Creates a hook handler that ingests every LLM exchange into the local store.
|
|
4
|
+
*
|
|
5
|
+
* Fire-and-forget pattern: returns void synchronously, ingests in background.
|
|
6
|
+
* Errors are caught and logged silently to avoid disrupting the agent flow.
|
|
7
|
+
*/
|
|
8
|
+
export function createPostExchangeHook(store, userId) {
|
|
9
|
+
return (event, ctx) => {
|
|
10
|
+
const exchange = {
|
|
11
|
+
id: crypto.randomUUID(),
|
|
12
|
+
userId,
|
|
13
|
+
sessionId: ctx.sessionId ?? event.sessionId ?? crypto.randomUUID(),
|
|
14
|
+
sessionLabel: ctx.sessionKey,
|
|
15
|
+
userMessage: event.prompt ?? '',
|
|
16
|
+
agentResponse: event.assistantTexts.join('\n'),
|
|
17
|
+
timestamp: new Date().toISOString(),
|
|
18
|
+
source: 'openclaw',
|
|
19
|
+
};
|
|
20
|
+
// Fire-and-forget ingest — never blocks the hook
|
|
21
|
+
(async () => {
|
|
22
|
+
try {
|
|
23
|
+
await store.ingest({
|
|
24
|
+
userMessage: exchange.userMessage,
|
|
25
|
+
agentResponse: exchange.agentResponse,
|
|
26
|
+
timestamp: new Date(exchange.timestamp),
|
|
27
|
+
source: exchange.source,
|
|
28
|
+
sessionId: exchange.sessionId,
|
|
29
|
+
...(exchange.sessionLabel && { sessionLabel: exchange.sessionLabel }),
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
catch (e) {
|
|
33
|
+
// Log error to ~/.plumb/errors.log for debugging
|
|
34
|
+
const errorEntry = {
|
|
35
|
+
timestamp: new Date().toISOString(),
|
|
36
|
+
type: 'ingest_error',
|
|
37
|
+
message: e instanceof Error ? e.message : String(e),
|
|
38
|
+
context: {
|
|
39
|
+
sessionId: exchange.sessionId,
|
|
40
|
+
userId,
|
|
41
|
+
source: exchange.source,
|
|
42
|
+
},
|
|
43
|
+
...(e instanceof Error && e.stack ? { stack: e.stack } : {}),
|
|
44
|
+
};
|
|
45
|
+
appendError(errorEntry);
|
|
46
|
+
// Also log to console for immediate visibility during development
|
|
47
|
+
console.debug('[plumb] ingest error:', e);
|
|
48
|
+
}
|
|
49
|
+
})();
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=post-exchange.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"post-exchange.js","sourceRoot":"","sources":["../../src/hooks/post-exchange.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AA0BjD;;;;;GAKG;AACH,MAAM,UAAU,sBAAsB,CAAC,KAAiB,EAAE,MAAc;IACtE,OAAO,CAAC,KAA+B,EAAE,GAA2B,EAAQ,EAAE;QAC5E,MAAM,QAAQ,GAAG;YACf,EAAE,EAAE,MAAM,CAAC,UAAU,EAAE;YACvB,MAAM;YACN,SAAS,EAAE,GAAG,CAAC,SAAS,IAAI,KAAK,CAAC,SAAS,IAAI,MAAM,CAAC,UAAU,EAAE;YAClE,YAAY,EAAE,GAAG,CAAC,UAAU;YAC5B,WAAW,EAAG,KAAa,CAAC,MAAM,IAAI,EAAE;YACxC,aAAa,EAAE,KAAK,CAAC,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC;YAC9C,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;YACnC,MAAM,EAAE,UAAmB;SAC5B,CAAC;QAEF,iDAAiD;QACjD,CAAC,KAAK,IAAI,EAAE;YACV,IAAI,CAAC;gBACH,MAAM,KAAK,CAAC,MAAM,CAAC;oBACjB,WAAW,EAAE,QAAQ,CAAC,WAAW;oBACjC,aAAa,EAAE,QAAQ,CAAC,aAAa;oBACrC,SAAS,EAAE,IAAI,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC;oBACvC,MAAM,EAAE,QAAQ,CAAC,MAAM;oBACvB,SAAS,EAAE,QAAQ,CAAC,SAAS;oBAC7B,GAAG,CAAC,QAAQ,CAAC,YAAY,IAAI,EAAE,YAAY,EAAE,QAAQ,CAAC,YAAY,EAAE,CAAC;iBACtE,CAAC,CAAC;YACL,CAAC;YAAC,OAAO,CAAU,EAAE,CAAC;gBACpB,iDAAiD;gBACjD,MAAM,UAAU,GAAG;oBACjB,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;oBACnC,IAAI,EAAE,cAAc;oBACpB,OAAO,EAAE,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;oBACnD,OAAO,EAAE;wBACP,SAAS,EAAE,QAAQ,CAAC,SAAS;wBAC7B,MAAM;wBACN,MAAM,EAAE,QAAQ,CAAC,MAAM;qBACxB;oBACD,GAAG,CAAC,CAAC,YAAY,KAAK,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;iBAC7D,CAAC;gBAEF,WAAW,CAAC,UAAU,CAAC,CAAC;gBAExB,kEAAkE;gBAClE,OAAO,CAAC,KAAK,CAAC,uBAAuB,EAAE,CAAC,CAAC,CAAC;YAC5C,CAAC;QACH,CAAC,CAAC,EAAE,CAAC;IACP,CAAC,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { LocalStore } from '@getplumb/core';
|
|
2
|
+
import type { NudgeManager } from '../nudge.js';
|
|
3
|
+
type PluginHookBeforePromptBuildEvent = {
|
|
4
|
+
prompt: string;
|
|
5
|
+
messages: unknown[];
|
|
6
|
+
};
|
|
7
|
+
type PluginHookBeforePromptBuildResult = {
|
|
8
|
+
systemPrompt?: string;
|
|
9
|
+
prependContext?: string;
|
|
10
|
+
};
|
|
11
|
+
type PluginHookAgentContext = {
|
|
12
|
+
agentId?: string;
|
|
13
|
+
sessionKey?: string;
|
|
14
|
+
sessionId?: string;
|
|
15
|
+
workspaceDir?: string;
|
|
16
|
+
messageProvider?: string;
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* Creates a hook handler that retrieves and injects memory context before each agent response.
|
|
20
|
+
*
|
|
21
|
+
* The hook queries the store with the incoming user message, formats the retrieved memories
|
|
22
|
+
* into a [PLUMB MEMORY] block, and prepends it to the system prompt.
|
|
23
|
+
*
|
|
24
|
+
* @param store LocalStore instance for memory retrieval
|
|
25
|
+
* @param nudgeManager NudgeManager instance for contextual upgrade nudges
|
|
26
|
+
* @param shadowMode If true, retrieves and logs what would be injected but doesn't actually inject
|
|
27
|
+
* @returns Hook handler for before_prompt_build event
|
|
28
|
+
*/
|
|
29
|
+
export declare function createPreResponseHook(store: LocalStore | null, nudgeManager: NudgeManager | null, shadowMode?: boolean): (event: PluginHookBeforePromptBuildEvent, ctx: PluginHookAgentContext) => Promise<PluginHookBeforePromptBuildResult | void>;
|
|
30
|
+
export {};
|
|
31
|
+
//# sourceMappingURL=pre-response.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pre-response.d.ts","sourceRoot":"","sources":["../../src/hooks/pre-response.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAGhD,KAAK,gCAAgC,GAAG;IACtC,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,OAAO,EAAE,CAAC;CACrB,CAAC;AAEF,KAAK,iCAAiC,GAAG;IACvC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB,CAAC;AAEF,KAAK,sBAAsB,GAAG;IAC5B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B,CAAC;AAIF;;;;;;;;;;GAUG;AACH,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,UAAU,GAAG,IAAI,EACxB,YAAY,EAAE,YAAY,GAAG,IAAI,EACjC,UAAU,UAAQ,IAGhB,OAAO,gCAAgC,EACvC,KAAK,sBAAsB,KAC1B,OAAO,CAAC,iCAAiC,GAAG,IAAI,CAAC,CAgFrD"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { buildMemoryContext, formatContextBlock } from '@getplumb/core';
|
|
2
|
+
const INJECTION_TIMEOUT_MS = 800;
|
|
3
|
+
/**
|
|
4
|
+
* Creates a hook handler that retrieves and injects memory context before each agent response.
|
|
5
|
+
*
|
|
6
|
+
* The hook queries the store with the incoming user message, formats the retrieved memories
|
|
7
|
+
* into a [PLUMB MEMORY] block, and prepends it to the system prompt.
|
|
8
|
+
*
|
|
9
|
+
* @param store LocalStore instance for memory retrieval
|
|
10
|
+
* @param nudgeManager NudgeManager instance for contextual upgrade nudges
|
|
11
|
+
* @param shadowMode If true, retrieves and logs what would be injected but doesn't actually inject
|
|
12
|
+
* @returns Hook handler for before_prompt_build event
|
|
13
|
+
*/
|
|
14
|
+
export function createPreResponseHook(store, nudgeManager, shadowMode = false) {
|
|
15
|
+
return async (event, ctx) => {
|
|
16
|
+
if (!store) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
let formattedContext = '';
|
|
20
|
+
let nudgeText = null;
|
|
21
|
+
try {
|
|
22
|
+
// Race between retrieval and timeout
|
|
23
|
+
const memoryContext = await Promise.race([
|
|
24
|
+
buildMemoryContext(event.prompt, store),
|
|
25
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), INJECTION_TIMEOUT_MS)),
|
|
26
|
+
]);
|
|
27
|
+
// Format the memory context into a prompt block
|
|
28
|
+
formattedContext = formatContextBlock(memoryContext);
|
|
29
|
+
}
|
|
30
|
+
catch (e) {
|
|
31
|
+
// Handle timeout silently — never slow down a response
|
|
32
|
+
if (e instanceof Error && e.message === 'timeout') {
|
|
33
|
+
console.warn('[plumb] memory retrieval timeout — skipping injection');
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
// Check for nudges
|
|
37
|
+
if (nudgeManager && ctx.sessionId) {
|
|
38
|
+
try {
|
|
39
|
+
// Check for 'second_integration' trigger
|
|
40
|
+
const secondIntegrationNudge = nudgeManager.checkSecondIntegration(store.db, store.userId, ctx.sessionId);
|
|
41
|
+
if (secondIntegrationNudge) {
|
|
42
|
+
nudgeText = secondIntegrationNudge;
|
|
43
|
+
// Record the nudge so it won't fire again
|
|
44
|
+
nudgeManager.recordNudge(store.db, 'second_integration');
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
catch (e) {
|
|
48
|
+
console.warn('[plumb] nudge check failed:', e);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
// Build the final block
|
|
52
|
+
let block = '';
|
|
53
|
+
if (formattedContext && formattedContext.trim()) {
|
|
54
|
+
block = formattedContext;
|
|
55
|
+
}
|
|
56
|
+
if (nudgeText) {
|
|
57
|
+
if (block) {
|
|
58
|
+
// Append nudge after memory results
|
|
59
|
+
block = `${block}\n\n---\n${nudgeText}`;
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
// Nudge is the sole content
|
|
63
|
+
block = nudgeText;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
// Skip injection if no content
|
|
67
|
+
if (!block || !block.trim()) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
// Wrap in [PLUMB MEMORY] delimiters
|
|
71
|
+
const finalBlock = `[PLUMB MEMORY]\n${block}\n[/PLUMB MEMORY]`;
|
|
72
|
+
// In shadow mode, log what would be injected but don't actually inject
|
|
73
|
+
if (shadowMode) {
|
|
74
|
+
console.debug('[plumb] shadow mode — would inject:', finalBlock.slice(0, 200));
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
// Return the block to be prepended to the system prompt
|
|
78
|
+
return { prependContext: finalBlock };
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=pre-response.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pre-response.js","sourceRoot":"","sources":["../../src/hooks/pre-response.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAuBxE,MAAM,oBAAoB,GAAG,GAAG,CAAC;AAEjC;;;;;;;;;;GAUG;AACH,MAAM,UAAU,qBAAqB,CACnC,KAAwB,EACxB,YAAiC,EACjC,UAAU,GAAG,KAAK;IAElB,OAAO,KAAK,EACV,KAAuC,EACvC,GAA2B,EACwB,EAAE;QACrD,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,OAAO;QACT,CAAC;QAED,IAAI,gBAAgB,GAAG,EAAE,CAAC;QAC1B,IAAI,SAAS,GAAkB,IAAI,CAAC;QAEpC,IAAI,CAAC;YACH,qCAAqC;YACrC,MAAM,aAAa,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC;gBACvC,kBAAkB,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC;gBACvC,IAAI,OAAO,CAAQ,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE,CAC/B,UAAU,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,SAAS,CAAC,CAAC,EAAE,oBAAoB,CAAC,CACrE;aACF,CAAC,CAAC;YAEH,gDAAgD;YAChD,gBAAgB,GAAG,kBAAkB,CAAC,aAAa,CAAC,CAAC;QACvD,CAAC;QAAC,OAAO,CAAU,EAAE,CAAC;YACpB,uDAAuD;YACvD,IAAI,CAAC,YAAY,KAAK,IAAI,CAAC,CAAC,OAAO,KAAK,SAAS,EAAE,CAAC;gBAClD,OAAO,CAAC,IAAI,CAAC,uDAAuD,CAAC,CAAC;YACxE,CAAC;QACH,CAAC;QAED,mBAAmB;QACnB,IAAI,YAAY,IAAI,GAAG,CAAC,SAAS,EAAE,CAAC;YAClC,IAAI,CAAC;gBACH,yCAAyC;gBACzC,MAAM,sBAAsB,GAAG,YAAY,CAAC,sBAAsB,CAChE,KAAK,CAAC,EAAE,EACR,KAAK,CAAC,MAAM,EACZ,GAAG,CAAC,SAAS,CACd,CAAC;gBAEF,IAAI,sBAAsB,EAAE,CAAC;oBAC3B,SAAS,GAAG,sBAAsB,CAAC;oBACnC,0CAA0C;oBAC1C,YAAY,CAAC,WAAW,CAAC,KAAK,CAAC,EAAE,EAAE,oBAAoB,CAAC,CAAC;gBAC3D,CAAC;YACH,CAAC;YAAC,OAAO,CAAU,EAAE,CAAC;gBACpB,OAAO,CAAC,IAAI,CAAC,6BAA6B,EAAE,CAAC,CAAC,CAAC;YACjD,CAAC;QACH,CAAC;QAED,wBAAwB;QACxB,IAAI,KAAK,GAAG,EAAE,CAAC;QAEf,IAAI,gBAAgB,IAAI,gBAAgB,CAAC,IAAI,EAAE,EAAE,CAAC;YAChD,KAAK,GAAG,gBAAgB,CAAC;QAC3B,CAAC;QAED,IAAI,SAAS,EAAE,CAAC;YACd,IAAI,KAAK,EAAE,CAAC;gBACV,oCAAoC;gBACpC,KAAK,GAAG,GAAG,KAAK,YAAY,SAAS,EAAE,CAAC;YAC1C,CAAC;iBAAM,CAAC;gBACN,4BAA4B;gBAC5B,KAAK,GAAG,SAAS,CAAC;YACpB,CAAC;QACH,CAAC;QAED,+BAA+B;QAC/B,IAAI,CAAC,KAAK,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,EAAE,CAAC;YAC5B,OAAO;QACT,CAAC;QAED,oCAAoC;QACpC,MAAM,UAAU,GAAG,mBAAmB,KAAK,mBAAmB,CAAC;QAE/D,uEAAuE;QACvE,IAAI,UAAU,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,qCAAqC,EAAE,UAAU,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;YAC/E,OAAO;QACT,CAAC;QAED,wDAAwD;QACxD,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,CAAC;IACxC,CAAC,CAAC;AACJ,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export { PlumbPluginConfig, DEFAULT_CONFIG } from './config.js';
|
|
2
|
+
export { PlumbMcpClient, MemorySearchResult, MemoryStoreResult, MemoryStatusResult, } from './mcp-client.js';
|
|
3
|
+
export { plugin } from './plugin-module.js';
|
|
4
|
+
export { createPostExchangeHook } from './hooks/post-exchange.js';
|
|
5
|
+
import type { PlumbPluginConfig } from './config.js';
|
|
6
|
+
import { PlumbMcpClient } from './mcp-client.js';
|
|
7
|
+
/**
|
|
8
|
+
* Main entry point for the Plumb OpenClaw plugin.
|
|
9
|
+
*
|
|
10
|
+
* Manages the lifecycle of the MCP client connection to the local
|
|
11
|
+
* plumb memory server. Pipeline hooks (ingest after exchange, inject
|
|
12
|
+
* before response) are implemented via plugin-module.ts (T-010) and
|
|
13
|
+
* will be enhanced in T-011.
|
|
14
|
+
*/
|
|
15
|
+
export declare class PlumbPlugin {
|
|
16
|
+
readonly config: PlumbPluginConfig;
|
|
17
|
+
private mcpClient;
|
|
18
|
+
constructor(config?: Partial<PlumbPluginConfig>);
|
|
19
|
+
/** Connect to the MCP server. No-op if disabled. */
|
|
20
|
+
start(): Promise<void>;
|
|
21
|
+
/** Get the underlying MCP client (null if not started or disabled). */
|
|
22
|
+
get client(): PlumbMcpClient | null;
|
|
23
|
+
/** Disconnect and clean up. */
|
|
24
|
+
stop(): Promise<void>;
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAChE,OAAO,EACL,cAAc,EACd,kBAAkB,EAClB,iBAAiB,EACjB,kBAAkB,GACnB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAC5C,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAElE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAErD,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAEjD;;;;;;;GAOG;AACH,qBAAa,WAAW;IACtB,QAAQ,CAAC,MAAM,EAAE,iBAAiB,CAAC;IACnC,OAAO,CAAC,SAAS,CAA+B;gBAEpC,MAAM,CAAC,EAAE,OAAO,CAAC,iBAAiB,CAAC;IAI/C,oDAAoD;IAC9C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAM5B,uEAAuE;IACvE,IAAI,MAAM,IAAI,cAAc,GAAG,IAAI,CAElC;IAED,+BAA+B;IACzB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;CAM5B"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export { DEFAULT_CONFIG } from './config.js';
|
|
2
|
+
export { PlumbMcpClient, } from './mcp-client.js';
|
|
3
|
+
export { plugin } from './plugin-module.js';
|
|
4
|
+
export { createPostExchangeHook } from './hooks/post-exchange.js';
|
|
5
|
+
import { DEFAULT_CONFIG } from './config.js';
|
|
6
|
+
import { PlumbMcpClient } from './mcp-client.js';
|
|
7
|
+
/**
|
|
8
|
+
* Main entry point for the Plumb OpenClaw plugin.
|
|
9
|
+
*
|
|
10
|
+
* Manages the lifecycle of the MCP client connection to the local
|
|
11
|
+
* plumb memory server. Pipeline hooks (ingest after exchange, inject
|
|
12
|
+
* before response) are implemented via plugin-module.ts (T-010) and
|
|
13
|
+
* will be enhanced in T-011.
|
|
14
|
+
*/
|
|
15
|
+
export class PlumbPlugin {
|
|
16
|
+
config;
|
|
17
|
+
mcpClient = null;
|
|
18
|
+
constructor(config) {
|
|
19
|
+
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
20
|
+
}
|
|
21
|
+
/** Connect to the MCP server. No-op if disabled. */
|
|
22
|
+
async start() {
|
|
23
|
+
if (!this.config.enabled)
|
|
24
|
+
return;
|
|
25
|
+
this.mcpClient = new PlumbMcpClient(this.config.mcpServerPath);
|
|
26
|
+
await this.mcpClient.connect();
|
|
27
|
+
}
|
|
28
|
+
/** Get the underlying MCP client (null if not started or disabled). */
|
|
29
|
+
get client() {
|
|
30
|
+
return this.mcpClient;
|
|
31
|
+
}
|
|
32
|
+
/** Disconnect and clean up. */
|
|
33
|
+
async stop() {
|
|
34
|
+
if (this.mcpClient) {
|
|
35
|
+
await this.mcpClient.close();
|
|
36
|
+
this.mcpClient = null;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAqB,cAAc,EAAE,MAAM,aAAa,CAAC;AAChE,OAAO,EACL,cAAc,GAIf,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAC5C,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAGlE,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAEjD;;;;;;;GAOG;AACH,MAAM,OAAO,WAAW;IACb,MAAM,CAAoB;IAC3B,SAAS,GAA0B,IAAI,CAAC;IAEhD,YAAY,MAAmC;QAC7C,IAAI,CAAC,MAAM,GAAG,EAAE,GAAG,cAAc,EAAE,GAAG,MAAM,EAAE,CAAC;IACjD,CAAC;IAED,oDAAoD;IACpD,KAAK,CAAC,KAAK;QACT,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO;YAAE,OAAO;QACjC,IAAI,CAAC,SAAS,GAAG,IAAI,cAAc,CAAC,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC;QAC/D,MAAM,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,CAAC;IACjC,CAAC;IAED,uEAAuE;IACvE,IAAI,MAAM;QACR,OAAO,IAAI,CAAC,SAAS,CAAC;IACxB,CAAC;IAED,+BAA+B;IAC/B,KAAK,CAAC,IAAI;QACR,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;YACnB,MAAM,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,CAAC;YAC7B,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC;QACxB,CAAC;IACH,CAAC;CACF"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/** A single search result returned by the MCP server's memory_search tool. */
|
|
2
|
+
export interface MemorySearchResult {
|
|
3
|
+
readonly fact: string;
|
|
4
|
+
readonly confidence: number;
|
|
5
|
+
readonly age_in_days: number;
|
|
6
|
+
readonly source_session_label?: string;
|
|
7
|
+
readonly layer: 'facts' | 'raw_log';
|
|
8
|
+
}
|
|
9
|
+
/** Result of a memory_store call. */
|
|
10
|
+
export interface MemoryStoreResult {
|
|
11
|
+
readonly fact_id: string;
|
|
12
|
+
}
|
|
13
|
+
/** Status shape returned by the MCP server (ISO string for dates). */
|
|
14
|
+
export interface MemoryStatusResult {
|
|
15
|
+
readonly factCount: number;
|
|
16
|
+
readonly rawLogCount: number;
|
|
17
|
+
readonly lastIngestion: string | null;
|
|
18
|
+
readonly storageBytes: number;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Thin wrapper around the MCP SDK Client that spawns the plumb MCP server
|
|
22
|
+
* as a child process via stdio and exposes typed helpers for the memory tools.
|
|
23
|
+
*/
|
|
24
|
+
export declare class PlumbMcpClient {
|
|
25
|
+
private client;
|
|
26
|
+
private transport;
|
|
27
|
+
private _connected;
|
|
28
|
+
constructor(mcpServerPath: string);
|
|
29
|
+
/** Spawn the MCP server and complete the handshake. */
|
|
30
|
+
connect(): Promise<void>;
|
|
31
|
+
get connected(): boolean;
|
|
32
|
+
/** Search memory for relevant facts / raw log entries. */
|
|
33
|
+
search(query: string, limit?: number): Promise<MemorySearchResult[]>;
|
|
34
|
+
/** Store a new piece of content in the memory layer. */
|
|
35
|
+
store(content: string, source: string): Promise<MemoryStoreResult>;
|
|
36
|
+
/** Get memory store statistics. */
|
|
37
|
+
status(): Promise<MemoryStatusResult>;
|
|
38
|
+
/** Disconnect from the MCP server and kill the child process. */
|
|
39
|
+
close(): Promise<void>;
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=mcp-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp-client.d.ts","sourceRoot":"","sources":["../src/mcp-client.ts"],"names":[],"mappings":"AAIA,8EAA8E;AAC9E,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,oBAAoB,CAAC,EAAE,MAAM,CAAC;IACvC,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,SAAS,CAAC;CACrC;AAED,qCAAqC;AACrC,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED,sEAAsE;AACtE,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IACtC,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;CAC/B;AAED;;;GAGG;AACH,qBAAa,cAAc;IACzB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,SAAS,CAAuB;IACxC,OAAO,CAAC,UAAU,CAAS;gBAEf,aAAa,EAAE,MAAM;IAWjC,uDAAuD;IACjD,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAK9B,IAAI,SAAS,IAAI,OAAO,CAEvB;IAED,0DAA0D;IACpD,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,EAAE,CAAC;IAS1E,wDAAwD;IAClD,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC;IASxE,mCAAmC;IAC7B,MAAM,IAAI,OAAO,CAAC,kBAAkB,CAAC;IAM3C,iEAAiE;IAC3D,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAI7B"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
2
|
+
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
|
|
3
|
+
/**
|
|
4
|
+
* Thin wrapper around the MCP SDK Client that spawns the plumb MCP server
|
|
5
|
+
* as a child process via stdio and exposes typed helpers for the memory tools.
|
|
6
|
+
*/
|
|
7
|
+
export class PlumbMcpClient {
|
|
8
|
+
client;
|
|
9
|
+
transport;
|
|
10
|
+
_connected = false;
|
|
11
|
+
constructor(mcpServerPath) {
|
|
12
|
+
this.client = new Client({ name: 'plumb-openclaw-plugin', version: '0.1.0' });
|
|
13
|
+
this.transport = new StdioClientTransport({
|
|
14
|
+
command: mcpServerPath,
|
|
15
|
+
stderr: 'pipe',
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
/** Spawn the MCP server and complete the handshake. */
|
|
19
|
+
async connect() {
|
|
20
|
+
await this.client.connect(this.transport);
|
|
21
|
+
this._connected = true;
|
|
22
|
+
}
|
|
23
|
+
get connected() {
|
|
24
|
+
return this._connected;
|
|
25
|
+
}
|
|
26
|
+
/** Search memory for relevant facts / raw log entries. */
|
|
27
|
+
async search(query, limit) {
|
|
28
|
+
const args = { query };
|
|
29
|
+
if (limit !== undefined)
|
|
30
|
+
args.limit = limit;
|
|
31
|
+
const result = await this.client.callTool({ name: 'memory_search', arguments: args });
|
|
32
|
+
const text = extractText(result);
|
|
33
|
+
return JSON.parse(text);
|
|
34
|
+
}
|
|
35
|
+
/** Store a new piece of content in the memory layer. */
|
|
36
|
+
async store(content, source) {
|
|
37
|
+
const result = await this.client.callTool({
|
|
38
|
+
name: 'memory_store',
|
|
39
|
+
arguments: { content, source },
|
|
40
|
+
});
|
|
41
|
+
const text = extractText(result);
|
|
42
|
+
return JSON.parse(text);
|
|
43
|
+
}
|
|
44
|
+
/** Get memory store statistics. */
|
|
45
|
+
async status() {
|
|
46
|
+
const result = await this.client.callTool({ name: 'memory_status', arguments: {} });
|
|
47
|
+
const text = extractText(result);
|
|
48
|
+
return JSON.parse(text);
|
|
49
|
+
}
|
|
50
|
+
/** Disconnect from the MCP server and kill the child process. */
|
|
51
|
+
async close() {
|
|
52
|
+
this._connected = false;
|
|
53
|
+
await this.transport.close();
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/** Extract the first text content block from an MCP tool result. */
|
|
57
|
+
function extractText(result) {
|
|
58
|
+
const content = result.content;
|
|
59
|
+
const block = content.find((c) => c.type === 'text');
|
|
60
|
+
if (!block?.text)
|
|
61
|
+
throw new Error('MCP tool returned no text content');
|
|
62
|
+
return block.text;
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=mcp-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp-client.js","sourceRoot":"","sources":["../src/mcp-client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AACnE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AAyBjF;;;GAGG;AACH,MAAM,OAAO,cAAc;IACjB,MAAM,CAAS;IACf,SAAS,CAAuB;IAChC,UAAU,GAAG,KAAK,CAAC;IAE3B,YAAY,aAAqB;QAC/B,IAAI,CAAC,MAAM,GAAG,IAAI,MAAM,CACtB,EAAE,IAAI,EAAE,uBAAuB,EAAE,OAAO,EAAE,OAAO,EAAE,CACpD,CAAC;QAEF,IAAI,CAAC,SAAS,GAAG,IAAI,oBAAoB,CAAC;YACxC,OAAO,EAAE,aAAa;YACtB,MAAM,EAAE,MAAM;SACf,CAAC,CAAC;IACL,CAAC;IAED,uDAAuD;IACvD,KAAK,CAAC,OAAO;QACX,MAAM,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QAC1C,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC;IACzB,CAAC;IAED,IAAI,SAAS;QACX,OAAO,IAAI,CAAC,UAAU,CAAC;IACzB,CAAC;IAED,0DAA0D;IAC1D,KAAK,CAAC,MAAM,CAAC,KAAa,EAAE,KAAc;QACxC,MAAM,IAAI,GAA4B,EAAE,KAAK,EAAE,CAAC;QAChD,IAAI,KAAK,KAAK,SAAS;YAAE,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QAE5C,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,eAAe,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACtF,MAAM,IAAI,GAAG,WAAW,CAAC,MAAM,CAAC,CAAC;QACjC,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAyB,CAAC;IAClD,CAAC;IAED,wDAAwD;IACxD,KAAK,CAAC,KAAK,CAAC,OAAe,EAAE,MAAc;QACzC,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC;YACxC,IAAI,EAAE,cAAc;YACpB,SAAS,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE;SAC/B,CAAC,CAAC;QACH,MAAM,IAAI,GAAG,WAAW,CAAC,MAAM,CAAC,CAAC;QACjC,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAsB,CAAC;IAC/C,CAAC;IAED,mCAAmC;IACnC,KAAK,CAAC,MAAM;QACV,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,eAAe,EAAE,SAAS,EAAE,EAAE,EAAE,CAAC,CAAC;QACpF,MAAM,IAAI,GAAG,WAAW,CAAC,MAAM,CAAC,CAAC;QACjC,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAuB,CAAC;IAChD,CAAC;IAED,iEAAiE;IACjE,KAAK,CAAC,KAAK;QACT,IAAI,CAAC,UAAU,GAAG,KAAK,CAAC;QACxB,MAAM,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,CAAC;IAC/B,CAAC;CACF;AAED,oEAAoE;AACpE,SAAS,WAAW,CAAC,MAA+C;IAClE,MAAM,OAAO,GAAG,MAAM,CAAC,OAAiD,CAAC;IACzE,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC;IACrD,IAAI,CAAC,KAAK,EAAE,IAAI;QAAE,MAAM,IAAI,KAAK,CAAC,mCAAmC,CAAC,CAAC;IACvE,OAAO,KAAK,CAAC,IAAI,CAAC;AACpB,CAAC"}
|
package/dist/nudge.d.ts
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
export type NudgeTriggerType = 'second_integration' | 'mcp_downtime';
|
|
2
|
+
type Database = any;
|
|
3
|
+
/**
|
|
4
|
+
* Manages one-time contextual upgrade nudges to the user.
|
|
5
|
+
* Each trigger type fires exactly once per install, tracked in the nudge_log table.
|
|
6
|
+
*/
|
|
7
|
+
export declare class NudgeManager {
|
|
8
|
+
/** Track which sessions have been seen this runtime to avoid redundant checks */
|
|
9
|
+
private seenSessions;
|
|
10
|
+
/** Track MCP downtime start timestamp (in-memory, not persisted) */
|
|
11
|
+
private mcpDowntimeStart;
|
|
12
|
+
/** Threshold in milliseconds for MCP downtime trigger (5 minutes) */
|
|
13
|
+
private readonly MCP_DOWNTIME_THRESHOLD_MS;
|
|
14
|
+
/**
|
|
15
|
+
* Check if a nudge should be fired for the given trigger type.
|
|
16
|
+
* Returns the nudge text if it should fire, null otherwise.
|
|
17
|
+
*/
|
|
18
|
+
checkTriggers(db: Database, triggerType: NudgeTriggerType): string | null;
|
|
19
|
+
/**
|
|
20
|
+
* Record that a nudge has been fired for the given trigger type.
|
|
21
|
+
* This ensures the trigger will never fire again.
|
|
22
|
+
*/
|
|
23
|
+
recordNudge(db: Database, triggerType: NudgeTriggerType): void;
|
|
24
|
+
/**
|
|
25
|
+
* Get the nudge text for a given trigger type.
|
|
26
|
+
* Tone: factual, helpful, not pushy. Surfaced through the agent's voice.
|
|
27
|
+
*/
|
|
28
|
+
getNudgeText(triggerType: NudgeTriggerType): string;
|
|
29
|
+
/**
|
|
30
|
+
* Check if the 'second_integration' trigger should fire.
|
|
31
|
+
* Counts distinct session_ids in raw_log for this userId.
|
|
32
|
+
* If >1 unique session has ingested data, that's a second integration.
|
|
33
|
+
*
|
|
34
|
+
* Only checks once per session to avoid redundant queries.
|
|
35
|
+
*/
|
|
36
|
+
checkSecondIntegration(db: Database, userId: string, currentSessionId: string): string | null;
|
|
37
|
+
/**
|
|
38
|
+
* Trigger the MCP downtime nudge.
|
|
39
|
+
* Called when MCP client connection fails.
|
|
40
|
+
* Tracks downtime duration in-memory; fires if >5 minutes.
|
|
41
|
+
*/
|
|
42
|
+
triggerMcpDowntime(db: Database): string | null;
|
|
43
|
+
/**
|
|
44
|
+
* Reset MCP downtime tracking (call when connection is restored).
|
|
45
|
+
*/
|
|
46
|
+
resetMcpDowntime(): void;
|
|
47
|
+
}
|
|
48
|
+
export {};
|
|
49
|
+
//# sourceMappingURL=nudge.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nudge.d.ts","sourceRoot":"","sources":["../src/nudge.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,gBAAgB,GAAG,oBAAoB,GAAG,cAAc,CAAC;AAIrE,KAAK,QAAQ,GAAG,GAAG,CAAC;AAEpB;;;GAGG;AACH,qBAAa,YAAY;IACvB,iFAAiF;IACjF,OAAO,CAAC,YAAY,CAA0B;IAE9C,oEAAoE;IACpE,OAAO,CAAC,gBAAgB,CAAuB;IAE/C,qEAAqE;IACrE,OAAO,CAAC,QAAQ,CAAC,yBAAyB,CAAiB;IAE3D;;;OAGG;IACH,aAAa,CAAC,EAAE,EAAE,QAAQ,EAAE,WAAW,EAAE,gBAAgB,GAAG,MAAM,GAAG,IAAI;IAezE;;;OAGG;IACH,WAAW,CAAC,EAAE,EAAE,QAAQ,EAAE,WAAW,EAAE,gBAAgB,GAAG,IAAI;IAO9D;;;OAGG;IACH,YAAY,CAAC,WAAW,EAAE,gBAAgB,GAAG,MAAM;IAWnD;;;;;;OAMG;IACH,sBAAsB,CACpB,EAAE,EAAE,QAAQ,EACZ,MAAM,EAAE,MAAM,EACd,gBAAgB,EAAE,MAAM,GACvB,MAAM,GAAG,IAAI;IAsBhB;;;;OAIG;IACH,kBAAkB,CAAC,EAAE,EAAE,QAAQ,GAAG,MAAM,GAAG,IAAI;IAmB/C;;OAEG;IACH,gBAAgB,IAAI,IAAI;CAGzB"}
|
package/dist/nudge.js
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Manages one-time contextual upgrade nudges to the user.
|
|
3
|
+
* Each trigger type fires exactly once per install, tracked in the nudge_log table.
|
|
4
|
+
*/
|
|
5
|
+
export class NudgeManager {
|
|
6
|
+
/** Track which sessions have been seen this runtime to avoid redundant checks */
|
|
7
|
+
seenSessions = new Set();
|
|
8
|
+
/** Track MCP downtime start timestamp (in-memory, not persisted) */
|
|
9
|
+
mcpDowntimeStart = null;
|
|
10
|
+
/** Threshold in milliseconds for MCP downtime trigger (5 minutes) */
|
|
11
|
+
MCP_DOWNTIME_THRESHOLD_MS = 5 * 60 * 1000;
|
|
12
|
+
/**
|
|
13
|
+
* Check if a nudge should be fired for the given trigger type.
|
|
14
|
+
* Returns the nudge text if it should fire, null otherwise.
|
|
15
|
+
*/
|
|
16
|
+
checkTriggers(db, triggerType) {
|
|
17
|
+
// Check if this trigger has already fired (check nudge_log table)
|
|
18
|
+
const existing = db.prepare(`SELECT id FROM nudge_log WHERE trigger_type = ? LIMIT 1`).get(triggerType);
|
|
19
|
+
if (existing) {
|
|
20
|
+
// This trigger has already fired — never fire again
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
// Trigger has not fired yet — return the nudge text
|
|
24
|
+
return this.getNudgeText(triggerType);
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Record that a nudge has been fired for the given trigger type.
|
|
28
|
+
* This ensures the trigger will never fire again.
|
|
29
|
+
*/
|
|
30
|
+
recordNudge(db, triggerType) {
|
|
31
|
+
const id = crypto.randomUUID();
|
|
32
|
+
db.prepare(`INSERT INTO nudge_log (id, trigger_type, fired_at) VALUES (?, ?, ?)`).run(id, triggerType, new Date().toISOString());
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Get the nudge text for a given trigger type.
|
|
36
|
+
* Tone: factual, helpful, not pushy. Surfaced through the agent's voice.
|
|
37
|
+
*/
|
|
38
|
+
getNudgeText(triggerType) {
|
|
39
|
+
switch (triggerType) {
|
|
40
|
+
case 'second_integration':
|
|
41
|
+
return "You've connected multiple integrations - if you want memory to sync across all of them automatically, Plumb hosted handles that: https://getplumb.dev/upgrade";
|
|
42
|
+
case 'mcp_downtime':
|
|
43
|
+
return 'Your local MCP server has been unreachable for a while - Plumb hosted runs in the cloud and stays available 24/7: https://getplumb.dev/upgrade';
|
|
44
|
+
default:
|
|
45
|
+
return '';
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Check if the 'second_integration' trigger should fire.
|
|
50
|
+
* Counts distinct session_ids in raw_log for this userId.
|
|
51
|
+
* If >1 unique session has ingested data, that's a second integration.
|
|
52
|
+
*
|
|
53
|
+
* Only checks once per session to avoid redundant queries.
|
|
54
|
+
*/
|
|
55
|
+
checkSecondIntegration(db, userId, currentSessionId) {
|
|
56
|
+
// Only check once per session
|
|
57
|
+
if (this.seenSessions.has(currentSessionId)) {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
this.seenSessions.add(currentSessionId);
|
|
61
|
+
// Count distinct session_ids in raw_log for this user
|
|
62
|
+
const result = db.prepare(`SELECT COUNT(DISTINCT session_id) as count FROM raw_log WHERE user_id = ?`).get(userId);
|
|
63
|
+
const sessionCount = result?.count ?? 0;
|
|
64
|
+
if (sessionCount > 1) {
|
|
65
|
+
// Multiple sessions detected — check if we should fire the nudge
|
|
66
|
+
return this.checkTriggers(db, 'second_integration');
|
|
67
|
+
}
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Trigger the MCP downtime nudge.
|
|
72
|
+
* Called when MCP client connection fails.
|
|
73
|
+
* Tracks downtime duration in-memory; fires if >5 minutes.
|
|
74
|
+
*/
|
|
75
|
+
triggerMcpDowntime(db) {
|
|
76
|
+
const now = Date.now();
|
|
77
|
+
// Start tracking downtime if not already started
|
|
78
|
+
if (this.mcpDowntimeStart === null) {
|
|
79
|
+
this.mcpDowntimeStart = now;
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
// Check if downtime exceeds threshold
|
|
83
|
+
const downtimeDuration = now - this.mcpDowntimeStart;
|
|
84
|
+
if (downtimeDuration >= this.MCP_DOWNTIME_THRESHOLD_MS) {
|
|
85
|
+
// Threshold exceeded — check if we should fire the nudge
|
|
86
|
+
return this.checkTriggers(db, 'mcp_downtime');
|
|
87
|
+
}
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Reset MCP downtime tracking (call when connection is restored).
|
|
92
|
+
*/
|
|
93
|
+
resetMcpDowntime() {
|
|
94
|
+
this.mcpDowntimeStart = null;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=nudge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nudge.js","sourceRoot":"","sources":["../src/nudge.ts"],"names":[],"mappings":"AAMA;;;GAGG;AACH,MAAM,OAAO,YAAY;IACvB,iFAAiF;IACzE,YAAY,GAAgB,IAAI,GAAG,EAAE,CAAC;IAE9C,oEAAoE;IAC5D,gBAAgB,GAAkB,IAAI,CAAC;IAE/C,qEAAqE;IACpD,yBAAyB,GAAG,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC;IAE3D;;;OAGG;IACH,aAAa,CAAC,EAAY,EAAE,WAA6B;QACvD,kEAAkE;QAClE,MAAM,QAAQ,GAAG,EAAE,CAAC,OAAO,CACzB,yDAAyD,CAC1D,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;QAEnB,IAAI,QAAQ,EAAE,CAAC;YACb,oDAAoD;YACpD,OAAO,IAAI,CAAC;QACd,CAAC;QAED,oDAAoD;QACpD,OAAO,IAAI,CAAC,YAAY,CAAC,WAAW,CAAC,CAAC;IACxC,CAAC;IAED;;;OAGG;IACH,WAAW,CAAC,EAAY,EAAE,WAA6B;QACrD,MAAM,EAAE,GAAG,MAAM,CAAC,UAAU,EAAE,CAAC;QAC/B,EAAE,CAAC,OAAO,CACR,qEAAqE,CACtE,CAAC,GAAG,CAAC,EAAE,EAAE,WAAW,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,CAAC;IACnD,CAAC;IAED;;;OAGG;IACH,YAAY,CAAC,WAA6B;QACxC,QAAQ,WAAW,EAAE,CAAC;YACpB,KAAK,oBAAoB;gBACvB,OAAO,+JAA+J,CAAC;YACzK,KAAK,cAAc;gBACjB,OAAO,gJAAgJ,CAAC;YAC1J;gBACE,OAAO,EAAE,CAAC;QACd,CAAC;IACH,CAAC;IAED;;;;;;OAMG;IACH,sBAAsB,CACpB,EAAY,EACZ,MAAc,EACd,gBAAwB;QAExB,8BAA8B;QAC9B,IAAI,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,gBAAgB,CAAC,EAAE,CAAC;YAC5C,OAAO,IAAI,CAAC;QACd,CAAC;QACD,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC;QAExC,sDAAsD;QACtD,MAAM,MAAM,GAAG,EAAE,CAAC,OAAO,CACvB,2EAA2E,CAC5E,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QAEd,MAAM,YAAY,GAAG,MAAM,EAAE,KAAK,IAAI,CAAC,CAAC;QAExC,IAAI,YAAY,GAAG,CAAC,EAAE,CAAC;YACrB,iEAAiE;YACjE,OAAO,IAAI,CAAC,aAAa,CAAC,EAAE,EAAE,oBAAoB,CAAC,CAAC;QACtD,CAAC;QAED,OAAO,IAAI,CAAC;IACd,CAAC;IAED;;;;OAIG;IACH,kBAAkB,CAAC,EAAY;QAC7B,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAEvB,iDAAiD;QACjD,IAAI,IAAI,CAAC,gBAAgB,KAAK,IAAI,EAAE,CAAC;YACnC,IAAI,CAAC,gBAAgB,GAAG,GAAG,CAAC;YAC5B,OAAO,IAAI,CAAC;QACd,CAAC;QAED,sCAAsC;QACtC,MAAM,gBAAgB,GAAG,GAAG,GAAG,IAAI,CAAC,gBAAgB,CAAC;QACrD,IAAI,gBAAgB,IAAI,IAAI,CAAC,yBAAyB,EAAE,CAAC;YACvD,yDAAyD;YACzD,OAAO,IAAI,CAAC,aAAa,CAAC,EAAE,EAAE,cAAc,CAAC,CAAC;QAChD,CAAC;QAED,OAAO,IAAI,CAAC;IACd,CAAC;IAED;;OAEG;IACH,gBAAgB;QACd,IAAI,CAAC,gBAAgB,GAAG,IAAI,CAAC;IAC/B,CAAC;CACF"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
type PluginLogger = {
|
|
2
|
+
debug?: (message: string) => void;
|
|
3
|
+
info: (message: string) => void;
|
|
4
|
+
warn: (message: string) => void;
|
|
5
|
+
error: (message: string) => void;
|
|
6
|
+
};
|
|
7
|
+
type PluginHookHandlerMap = {
|
|
8
|
+
llm_output: (event: any, ctx: any) => Promise<void> | void;
|
|
9
|
+
session_end: (event: any, ctx: any) => Promise<void> | void;
|
|
10
|
+
before_prompt_build: (event: any, ctx: any) => Promise<any> | void;
|
|
11
|
+
[key: string]: any;
|
|
12
|
+
};
|
|
13
|
+
type OpenClawPluginApi = {
|
|
14
|
+
id: string;
|
|
15
|
+
name: string;
|
|
16
|
+
pluginConfig?: Record<string, unknown>;
|
|
17
|
+
logger: PluginLogger;
|
|
18
|
+
on: <K extends keyof PluginHookHandlerMap>(hookName: K, handler: PluginHookHandlerMap[K], opts?: {
|
|
19
|
+
priority?: number;
|
|
20
|
+
}) => void;
|
|
21
|
+
};
|
|
22
|
+
type OpenClawPluginDefinition = {
|
|
23
|
+
id?: string;
|
|
24
|
+
name?: string;
|
|
25
|
+
version?: string;
|
|
26
|
+
kind?: 'memory';
|
|
27
|
+
activate?: (api: OpenClawPluginApi) => void | Promise<void>;
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Plumb OpenClaw plugin entry point.
|
|
31
|
+
*
|
|
32
|
+
* Automatically ingests every LLM exchange into the local memory store via the
|
|
33
|
+
* llm_output hook. Runs fire-and-forget so it never blocks the agent pipeline.
|
|
34
|
+
*/
|
|
35
|
+
export declare const plugin: OpenClawPluginDefinition;
|
|
36
|
+
export {};
|
|
37
|
+
//# sourceMappingURL=plugin-module.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin-module.d.ts","sourceRoot":"","sources":["../src/plugin-module.ts"],"names":[],"mappings":"AAQA,KAAK,YAAY,GAAG;IAClB,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,KAAK,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CAClC,CAAC;AAEF,KAAK,oBAAoB,GAAG;IAC1B,UAAU,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAC3D,WAAW,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAC5D,mBAAmB,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,KAAK,OAAO,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC;IACnE,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACpB,CAAC;AAEF,KAAK,iBAAiB,GAAG;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACvC,MAAM,EAAE,YAAY,CAAC;IACrB,EAAE,EAAE,CAAC,CAAC,SAAS,MAAM,oBAAoB,EACvC,QAAQ,EAAE,CAAC,EACX,OAAO,EAAE,oBAAoB,CAAC,CAAC,CAAC,EAChC,IAAI,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,KACzB,IAAI,CAAC;CACX,CAAC;AAEF,KAAK,wBAAwB,GAAG;IAC9B,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,QAAQ,CAAC;IAChB,QAAQ,CAAC,EAAE,CAAC,GAAG,EAAE,iBAAiB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC7D,CAAC;AAIF;;;;;GAKG;AACH,eAAO,MAAM,MAAM,EAAE,wBAoCpB,CAAC"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { LocalStore } from '@getplumb/core';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { createPostExchangeHook } from './hooks/post-exchange.js';
|
|
5
|
+
import { createPreResponseHook } from './hooks/pre-response.js';
|
|
6
|
+
import { NudgeManager } from './nudge.js';
|
|
7
|
+
const DEFAULT_DB_PATH = join(homedir(), '.plumb', 'memory.db');
|
|
8
|
+
/**
|
|
9
|
+
* Plumb OpenClaw plugin entry point.
|
|
10
|
+
*
|
|
11
|
+
* Automatically ingests every LLM exchange into the local memory store via the
|
|
12
|
+
* llm_output hook. Runs fire-and-forget so it never blocks the agent pipeline.
|
|
13
|
+
*/
|
|
14
|
+
export const plugin = {
|
|
15
|
+
id: 'plumb',
|
|
16
|
+
name: 'Plumb Memory',
|
|
17
|
+
version: '0.1.0',
|
|
18
|
+
kind: 'memory',
|
|
19
|
+
async activate(api) {
|
|
20
|
+
const dbPath = api.pluginConfig?.dbPath ?? DEFAULT_DB_PATH;
|
|
21
|
+
const userId = api.pluginConfig?.userId ?? 'default';
|
|
22
|
+
const shadowMode = api.pluginConfig?.shadowMode ?? false;
|
|
23
|
+
api.logger.info(`[plumb] Activating with dbPath=${dbPath}, userId=${userId}, shadowMode=${shadowMode}`);
|
|
24
|
+
const store = new LocalStore({ dbPath, userId });
|
|
25
|
+
const nudgeManager = new NudgeManager();
|
|
26
|
+
// Register the llm_output hook for auto-ingest
|
|
27
|
+
api.on('llm_output', createPostExchangeHook(store, userId));
|
|
28
|
+
// Register the before_prompt_build hook for memory injection
|
|
29
|
+
api.on('before_prompt_build', createPreResponseHook(store, nudgeManager, shadowMode));
|
|
30
|
+
// Clean up on session end
|
|
31
|
+
api.on('session_end', async () => {
|
|
32
|
+
try {
|
|
33
|
+
store.close();
|
|
34
|
+
api.logger.debug?.('[plumb] Store closed on session_end');
|
|
35
|
+
}
|
|
36
|
+
catch (e) {
|
|
37
|
+
api.logger.debug?.(`[plumb] Error closing store: ${e}`);
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
api.logger.info('[plumb] Plugin activated');
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
//# sourceMappingURL=plugin-module.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin-module.js","sourceRoot":"","sources":["../src/plugin-module.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAC5C,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAClE,OAAO,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAChE,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAqC1C,MAAM,eAAe,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,WAAW,CAAC,CAAC;AAE/D;;;;;GAKG;AACH,MAAM,CAAC,MAAM,MAAM,GAA6B;IAC9C,EAAE,EAAE,OAAO;IACX,IAAI,EAAE,cAAc;IACpB,OAAO,EAAE,OAAO;IAChB,IAAI,EAAE,QAAQ;IAEd,KAAK,CAAC,QAAQ,CAAC,GAAsB;QACnC,MAAM,MAAM,GAAI,GAAG,CAAC,YAAY,EAAE,MAA6B,IAAI,eAAe,CAAC;QACnF,MAAM,MAAM,GAAI,GAAG,CAAC,YAAY,EAAE,MAA6B,IAAI,SAAS,CAAC;QAC7E,MAAM,UAAU,GAAI,GAAG,CAAC,YAAY,EAAE,UAAkC,IAAI,KAAK,CAAC;QAElF,GAAG,CAAC,MAAM,CAAC,IAAI,CACb,kCAAkC,MAAM,YAAY,MAAM,gBAAgB,UAAU,EAAE,CACvF,CAAC;QAEF,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;QACjD,MAAM,YAAY,GAAG,IAAI,YAAY,EAAE,CAAC;QAExC,+CAA+C;QAC/C,GAAG,CAAC,EAAE,CAAC,YAAY,EAAE,sBAAsB,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC;QAE5D,6DAA6D;QAC7D,GAAG,CAAC,EAAE,CAAC,qBAAqB,EAAE,qBAAqB,CAAC,KAAK,EAAE,YAAY,EAAE,UAAU,CAAC,CAAC,CAAC;QAEtF,0BAA0B;QAC1B,GAAG,CAAC,EAAE,CAAC,aAAa,EAAE,KAAK,IAAI,EAAE;YAC/B,IAAI,CAAC;gBACH,KAAK,CAAC,KAAK,EAAE,CAAC;gBACd,GAAG,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,qCAAqC,CAAC,CAAC;YAC5D,CAAC;YAAC,OAAO,CAAC,EAAE,CAAC;gBACX,GAAG,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,gCAAgC,CAAC,EAAE,CAAC,CAAC;YAC1D,CAAC;QACH,CAAC,CAAC,CAAC;QAEH,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,0BAA0B,CAAC,CAAC;IAC9C,CAAC;CACF,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@getplumb/openclaw-plugin",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Plumb OpenClaw plugin — auto-ingest and memory injection for OpenClaw",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"files": [
|
|
10
|
+
"dist",
|
|
11
|
+
"LICENSE",
|
|
12
|
+
"README.md"
|
|
13
|
+
],
|
|
14
|
+
"publishConfig": {
|
|
15
|
+
"access": "public"
|
|
16
|
+
},
|
|
17
|
+
"exports": {
|
|
18
|
+
".": {
|
|
19
|
+
"import": "./dist/index.js",
|
|
20
|
+
"types": "./dist/index.d.ts"
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
25
|
+
"@getplumb/core": "0.1.0"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@types/better-sqlite3": "^7.6.13",
|
|
29
|
+
"better-sqlite3": "^12.6.2",
|
|
30
|
+
"openclaw": "^2026.3.2",
|
|
31
|
+
"typescript": "^5.4.0",
|
|
32
|
+
"vitest": "^4.0.18"
|
|
33
|
+
},
|
|
34
|
+
"scripts": {
|
|
35
|
+
"build": "tsc",
|
|
36
|
+
"test": "vitest run",
|
|
37
|
+
"lint": "echo \"No lint yet\" && exit 0",
|
|
38
|
+
"clean": "rm -rf dist *.tsbuildinfo"
|
|
39
|
+
}
|
|
40
|
+
}
|