mirror of
https://github.com/dtzp555-max/memory-continuity.git
synced 2026-07-21 21:15:07 +00:00
release: v5.0.0 — conservative subagent support (parent seed + child recovery)
- Add workspace resolution helpers (isSubagentSession, parseParentAgentId, resolveAgentWorkspace) - Parent→Child seeding: inject parent CURRENT_STATE.md into subagent context at startup - Child→Parent recovery: new subagent_ended hook reads child unsurfaced results, merges into parent - Add subagentSeed and subagentRecovery config keys (both default: true) - Add /mc subagents command for cross-workspace state overview - Bump lifecycle plugin to v5.0.0, mc-plugin to v2.1.0 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,556 @@
|
||||
# Phase 5 — Conservative Subagent Support (v5.0) Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** Enable working-state continuity across parent/child agent boundaries — parent seeds child with structured context, child recovers unsurfaced results back to parent.
|
||||
|
||||
**Architecture:** Two new lifecycle hooks added to the existing plugin. (1) Enhance `before_agent_start` to detect forked child sessions and inject the parent's CURRENT_STATE.md as a structured seed. (2) Add a new `subagent_ended` hook that reads the child's workspace CURRENT_STATE.md and merges unsurfaced results into the parent's state. Both features are conservative — they degrade gracefully when context is unavailable and never overwrite parent state destructively.
|
||||
|
||||
**Tech Stack:** Node.js ESM, OpenClaw plugin lifecycle hooks (`before_agent_start`, `subagent_ended`), filesystem-based state passing (cross-workspace read), zero external dependencies.
|
||||
|
||||
**Research Findings (constraints):**
|
||||
- `subagent_ended` event provides: `{ targetSessionKey, reason, outcome, error }` and context `{ childSessionKey, requesterSessionKey }`
|
||||
- No pre-spawn hook exists — parent seeding happens via `before_agent_start` on the child side
|
||||
- Workspaces are fully isolated — each agent has its own `memory/` directory
|
||||
- Session key format: `agent:<agentId>:subagent:<uuid>` — parseable to extract `agentId`
|
||||
- Agent workspace is resolvable via config: `agents.list[].workspace` or default `~/.openclaw/workspace/main`
|
||||
- `isSubagentSessionKey()` and `parseAgentSessionKey()` are runtime utilities (not available to plugins directly — must parse manually)
|
||||
|
||||
---
|
||||
|
||||
## File Structure
|
||||
|
||||
| Action | File | Responsibility |
|
||||
|--------|------|----------------|
|
||||
| Modify | `index.js` | Add parent-seed logic to `before_agent_start`, add new `subagent_ended` hook, add helper functions |
|
||||
| Modify | `openclaw.plugin.json` | Bump to 5.0.0, add new config keys |
|
||||
| Modify | `package.json` | Bump to 5.0.0 |
|
||||
|
||||
---
|
||||
|
||||
### Task 1: Add Workspace Resolution Helpers
|
||||
|
||||
**Files:**
|
||||
- Modify: `index.js` (helpers section, before plugin definition)
|
||||
|
||||
Two new helper functions are needed: one to detect if the current session is a subagent, and one to resolve a workspace directory from an agent ID.
|
||||
|
||||
- [ ] **Step 1: Add `isSubagentSession` helper**
|
||||
|
||||
In `index.js`, after the existing `extractStateFromMessages` function (around line 709) and before the `// Plugin Definition` comment, add:
|
||||
|
||||
```javascript
|
||||
/**
|
||||
* Detect if the current session is a subagent by checking the session key format.
|
||||
* Subagent session keys follow: agent:<agentId>:subagent:<uuid>
|
||||
*/
|
||||
function isSubagentSession(ctx) {
|
||||
const key = ctx?.sessionKey || ctx?.SessionKey || "";
|
||||
return /^agent:[^:]+:subagent:/.test(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the parent agent ID from a subagent session key.
|
||||
* Session key format: agent:<parentAgentId>:subagent:<uuid>
|
||||
* Returns null if not a subagent key.
|
||||
*/
|
||||
function parseParentAgentId(sessionKey) {
|
||||
const match = sessionKey?.match(/^agent:([^:]+):subagent:/);
|
||||
return match?.[1] || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve workspace directory for a given agent ID.
|
||||
* Searches the OpenClaw config for agent workspace mappings.
|
||||
* Falls back to ~/.openclaw/workspace/main for the "main" agent,
|
||||
* or ~/.openclaw/workspaces/<agentId> for named agents.
|
||||
*/
|
||||
function resolveAgentWorkspace(agentId) {
|
||||
if (!agentId) return null;
|
||||
|
||||
const base = process.env.OPENCLAW_HOME || path.join(process.env.HOME || "/tmp", ".openclaw");
|
||||
|
||||
// Try to read config for explicit workspace mapping
|
||||
try {
|
||||
const configPath = path.join(base, "openclaw.json");
|
||||
const config = JSON.parse(fs.readFileSync(configPath, "utf8"));
|
||||
const agents = config?.agents?.list || [];
|
||||
const entry = agents.find(a => a.id === agentId || a.name === agentId);
|
||||
if (entry?.workspace) return entry.workspace;
|
||||
|
||||
// Check defaults
|
||||
if (agentId === "main") {
|
||||
return config?.agents?.defaults?.workspace || path.join(base, "workspace", "main");
|
||||
}
|
||||
} catch {}
|
||||
|
||||
// Fallback heuristics
|
||||
if (agentId === "main") return path.join(base, "workspace", "main");
|
||||
|
||||
// Named agents typically use ~/.openclaw/workspaces/<agentId>
|
||||
const namedWs = path.join(base, "workspaces", agentId);
|
||||
if (fs.existsSync(namedWs)) return namedWs;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the Unsurfaced Results section from a state markdown file.
|
||||
* Returns the raw text content of the section, or null if empty/placeholder.
|
||||
*/
|
||||
function extractUnsurfacedResults(md) {
|
||||
if (!md) return null;
|
||||
const section = extractSection(md, "Unsurfaced Results");
|
||||
if (!section || !isMeaningful(section)) return null;
|
||||
return section;
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Verify the file still parses**
|
||||
|
||||
```bash
|
||||
cd /Users/taodeng/.openclaw/projects/memory-continuity
|
||||
node -e "import('./index.js').then(m => console.log('OK:', typeof m.default))"
|
||||
```
|
||||
|
||||
Expected: `OK: object`
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add index.js
|
||||
git commit -m "feat(subagent): add workspace resolution and session detection helpers"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 2: Parent→Child Seeding in before_agent_start
|
||||
|
||||
**Files:**
|
||||
- Modify: `index.js` (HOOK 1: before_agent_start)
|
||||
|
||||
When a child agent starts (detected via subagent session key format), read the parent agent's CURRENT_STATE.md and inject it as additional context alongside the child's own state.
|
||||
|
||||
- [ ] **Step 1: Enhance the `before_agent_start` hook**
|
||||
|
||||
In `index.js`, find HOOK 1 (`before_agent_start`). The current code reads the agent's own state and injects it. After the existing relevance injection block (around the `if (config.relevanceInjection !== false)` block), add parent-seed logic:
|
||||
|
||||
Find this existing code block:
|
||||
|
||||
```javascript
|
||||
// Relevance injection: find related historical entries
|
||||
if (config.relevanceInjection !== false) {
|
||||
const objective = extractSection(md, "Objective");
|
||||
const maxItems = config.maxRelevanceItems ?? 3;
|
||||
const history = findRelevantHistory(ws, objective, maxItems);
|
||||
if (history) {
|
||||
parts.push(history);
|
||||
log.info?.("[memory-continuity] Injected relevant history context");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Right after this block (before the `return` statement), insert:
|
||||
|
||||
```javascript
|
||||
// Parent seed: if this is a subagent, inject parent's working state
|
||||
if (config.subagentSeed !== false && isSubagentSession(_ctx)) {
|
||||
try {
|
||||
const sessionKey = _ctx?.sessionKey || _ctx?.SessionKey || "";
|
||||
const parentAgentId = parseParentAgentId(sessionKey);
|
||||
if (parentAgentId) {
|
||||
const parentWs = resolveAgentWorkspace(parentAgentId);
|
||||
if (parentWs) {
|
||||
const parentStatePath = resolveStatePath(parentWs);
|
||||
const parentMd = parentStatePath ? readFile(parentStatePath) : null;
|
||||
if (parentMd) {
|
||||
const parentSnapshot = buildSnapshot(parentMd);
|
||||
if (parentSnapshot) {
|
||||
parts.push(
|
||||
"=== PARENT AGENT CONTEXT ===\n" +
|
||||
"The following is the parent agent's working state. " +
|
||||
"Use this to understand the broader task context.\n" +
|
||||
parentSnapshot +
|
||||
"\n=== END PARENT CONTEXT ==="
|
||||
);
|
||||
log.info?.("[memory-continuity] Injected parent state seed for subagent");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
log.warn?.("[memory-continuity] Parent seed failed (non-fatal): " + err.message);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Verify plugin loads**
|
||||
|
||||
```bash
|
||||
cd /Users/taodeng/.openclaw/projects/memory-continuity
|
||||
node -e "import('./index.js').then(m => console.log('OK:', typeof m.default))"
|
||||
```
|
||||
|
||||
Expected: `OK: object`
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add index.js
|
||||
git commit -m "feat(subagent): parent-to-child state seeding in before_agent_start"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 3: Child→Parent Recovery via subagent_ended Hook
|
||||
|
||||
**Files:**
|
||||
- Modify: `index.js` (add new HOOK 6: subagent_ended)
|
||||
|
||||
When a child agent ends, read its CURRENT_STATE.md and check for unsurfaced results. If found, append them to the parent's state file.
|
||||
|
||||
- [ ] **Step 1: Add the `subagent_ended` hook**
|
||||
|
||||
In `index.js`, find the section after HOOK 5 (`session_end`) and before the SERVICE section. Add a new hook between them:
|
||||
|
||||
```javascript
|
||||
// ------------------------------------------------------------------
|
||||
// HOOK 6: subagent_ended — recover child's unsurfaced results
|
||||
// ------------------------------------------------------------------
|
||||
api.on("subagent_ended", async (event, _ctx) => {
|
||||
const config = getConfig();
|
||||
if (config.subagentRecovery === false) return;
|
||||
|
||||
const parentWs = _ctx?.workspaceDir;
|
||||
if (!parentWs) return;
|
||||
|
||||
const childSessionKey = event?.childSessionKey || _ctx?.childSessionKey;
|
||||
if (!childSessionKey) {
|
||||
log.info?.("[memory-continuity] subagent_ended: no childSessionKey, skipping");
|
||||
return;
|
||||
}
|
||||
|
||||
// Only process successful completions (not kills/errors)
|
||||
const outcome = event?.outcome || "";
|
||||
const reason = event?.reason || "";
|
||||
if (outcome === "error" || reason === "killed" || reason === "spawn-failed") {
|
||||
log.info?.("[memory-continuity] subagent_ended: child ended with " + (reason || outcome) + ", skipping recovery");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Parse child agent ID from session key
|
||||
const childMatch = childSessionKey.match(/^agent:([^:]+)/);
|
||||
const childAgentId = childMatch?.[1];
|
||||
if (!childAgentId) return;
|
||||
|
||||
const childWs = resolveAgentWorkspace(childAgentId);
|
||||
if (!childWs) {
|
||||
log.info?.("[memory-continuity] subagent_ended: cannot resolve child workspace for " + childAgentId);
|
||||
return;
|
||||
}
|
||||
|
||||
// Read child's state
|
||||
const childStatePath = resolveStatePath(childWs);
|
||||
const childMd = childStatePath ? readFile(childStatePath) : null;
|
||||
if (!childMd) return;
|
||||
|
||||
// Extract unsurfaced results from child
|
||||
const childUnsurfaced = extractUnsurfacedResults(childMd);
|
||||
if (!childUnsurfaced) {
|
||||
log.info?.("[memory-continuity] subagent_ended: no unsurfaced results in child state");
|
||||
return;
|
||||
}
|
||||
|
||||
// Also extract child's objective for context
|
||||
const childObjective = extractSection(childMd, "Objective");
|
||||
|
||||
// Read parent's current state
|
||||
const parentStatePath = resolveStatePath(parentWs);
|
||||
if (!parentStatePath) return;
|
||||
|
||||
let parentMd = readFile(parentStatePath);
|
||||
if (!parentMd) {
|
||||
parentMd = STATE_TEMPLATE;
|
||||
}
|
||||
|
||||
// Build the recovery note
|
||||
const now = new Date().toISOString();
|
||||
const recoveryNote = [
|
||||
`[${now}] Subagent "${childAgentId}" completed.`,
|
||||
childObjective ? ` Task: ${childObjective.split("\n")[0].slice(0, 120)}` : "",
|
||||
` Result: ${childUnsurfaced.split("\n")[0].slice(0, 200)}`,
|
||||
].filter(Boolean).join("\n");
|
||||
|
||||
// Merge into parent's Unsurfaced Results section
|
||||
const existingUnsurfaced = extractSection(parentMd, "Unsurfaced Results");
|
||||
const mergedUnsurfaced = isMeaningful(existingUnsurfaced)
|
||||
? existingUnsurfaced + "\n" + recoveryNote
|
||||
: recoveryNote;
|
||||
|
||||
// Token-aware truncation of merged results
|
||||
if (estimateTokens(mergedUnsurfaced) > 500) {
|
||||
// Keep only the most recent entries (last 500 tokens)
|
||||
const lines = mergedUnsurfaced.split("\n");
|
||||
let kept = [];
|
||||
let tokens = 0;
|
||||
for (let i = lines.length - 1; i >= 0; i--) {
|
||||
const lineTokens = estimateTokens(lines[i]);
|
||||
if (tokens + lineTokens > 500) break;
|
||||
kept.unshift(lines[i]);
|
||||
tokens += lineTokens;
|
||||
}
|
||||
const truncated = kept.join("\n");
|
||||
parentMd = parentMd.replace(
|
||||
/## Unsurfaced Results\n[\s\S]*?(?=\n## |\n$|$)/,
|
||||
"## Unsurfaced Results\n" + truncated
|
||||
);
|
||||
} else {
|
||||
parentMd = parentMd.replace(
|
||||
/## Unsurfaced Results\n[\s\S]*?(?=\n## |\n$|$)/,
|
||||
"## Unsurfaced Results\n" + mergedUnsurfaced
|
||||
);
|
||||
}
|
||||
|
||||
// Update the timestamp
|
||||
parentMd = parentMd.replace(
|
||||
/^> Last updated:.*$/m,
|
||||
`> Last updated: ${now}`
|
||||
);
|
||||
|
||||
writeFile(parentStatePath, parentMd);
|
||||
log.info?.("[memory-continuity] Recovered unsurfaced results from subagent " + childAgentId);
|
||||
} catch (err) {
|
||||
log.warn?.("[memory-continuity] subagent_ended recovery failed (non-fatal): " + err.message);
|
||||
}
|
||||
}, { priority: 50 });
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Verify plugin loads**
|
||||
|
||||
```bash
|
||||
cd /Users/taodeng/.openclaw/projects/memory-continuity
|
||||
node -e "import('./index.js').then(m => console.log('OK:', typeof m.default))"
|
||||
```
|
||||
|
||||
Expected: `OK: object`
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add index.js
|
||||
git commit -m "feat(subagent): child-to-parent unsurfaced results recovery via subagent_ended"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 4: New Config Keys + Manifest Bump
|
||||
|
||||
**Files:**
|
||||
- Modify: `openclaw.plugin.json`
|
||||
- Modify: `package.json`
|
||||
|
||||
- [ ] **Step 1: Add subagent config keys to `openclaw.plugin.json`**
|
||||
|
||||
Bump version to `5.0.0`. Add two new config properties inside `configSchema.properties`:
|
||||
|
||||
```json
|
||||
"subagentSeed": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "Inject parent working state into subagent context at startup"
|
||||
},
|
||||
"subagentRecovery": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "Recover unsurfaced results from completed subagents back to parent"
|
||||
}
|
||||
```
|
||||
|
||||
Add corresponding `uiHints`:
|
||||
|
||||
```json
|
||||
"subagentSeed": {
|
||||
"label": "Subagent seeding",
|
||||
"help": "Give subagents parent context so they understand the broader task"
|
||||
},
|
||||
"subagentRecovery": {
|
||||
"label": "Subagent recovery",
|
||||
"help": "Pull completed subagent results back into parent state"
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Bump `package.json` to 5.0.0**
|
||||
|
||||
Change `"version": "4.0.0"` to `"version": "5.0.0"`.
|
||||
|
||||
- [ ] **Step 3: Verify JSON validity**
|
||||
|
||||
```bash
|
||||
cd /Users/taodeng/.openclaw/projects/memory-continuity
|
||||
python3 -m json.tool openclaw.plugin.json > /dev/null && echo "JSON valid"
|
||||
node -e "import('./index.js').then(m => console.log('OK:', typeof m.default))"
|
||||
```
|
||||
|
||||
Expected:
|
||||
```
|
||||
JSON valid
|
||||
OK: object
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Commit**
|
||||
|
||||
```bash
|
||||
git add openclaw.plugin.json package.json
|
||||
git commit -m "feat(subagent): add subagentSeed and subagentRecovery config keys, bump to v5.0.0"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 5: Add `/mc subagents` Command
|
||||
|
||||
**Files:**
|
||||
- Modify: `mc-plugin/index.js`
|
||||
- Modify: `mc-plugin/package.json`
|
||||
- Modify: `mc-plugin/openclaw.plugin.json`
|
||||
|
||||
Add a new `/mc subagents` command that shows subagent state across workspaces.
|
||||
|
||||
- [ ] **Step 1: Add `cmdSubagents` function to mc-plugin**
|
||||
|
||||
In `mc-plugin/index.js`, before the `cmdHelp` function, add:
|
||||
|
||||
```javascript
|
||||
function cmdSubagents(args) {
|
||||
const agents = discoverAgents();
|
||||
if (agents.length === 0) return "No agents with memory found.";
|
||||
|
||||
let out = "Subagent State Overview\n";
|
||||
out += "═══════════════════════\n\n";
|
||||
|
||||
for (const { name, memDir } of agents) {
|
||||
const statePath = path.join(memDir, "CURRENT_STATE.md");
|
||||
const content = readFile(statePath);
|
||||
if (!content) {
|
||||
out += `[${name}] No state file\n\n`;
|
||||
continue;
|
||||
}
|
||||
|
||||
const objMatch = content.match(/## Objective\n([\s\S]*?)(?=\n## )/);
|
||||
const unsurfMatch = content.match(/## Unsurfaced Results\n([\s\S]*?)(?=\n## |$)/);
|
||||
const updatedMatch = content.match(/^> Last updated:\s*(.+)$/m);
|
||||
|
||||
const objective = objMatch?.[1]?.trim() || "None";
|
||||
const unsurfaced = unsurfMatch?.[1]?.trim() || "None";
|
||||
const updated = updatedMatch?.[1]?.trim() || "unknown";
|
||||
|
||||
out += `[${name}] Updated: ${updated}\n`;
|
||||
out += ` Objective: ${truncate(objective.split("\n")[0], 80)}\n`;
|
||||
if (unsurfaced !== "None") {
|
||||
out += ` Unsurfaced: ${truncate(unsurfaced.split("\n")[0], 80)}\n`;
|
||||
}
|
||||
out += "\n";
|
||||
}
|
||||
|
||||
return out.trimEnd();
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Register the command in the switch statement**
|
||||
|
||||
Find the `switch (subcmd)` block and add a new case before the `help` case:
|
||||
|
||||
```javascript
|
||||
case "subagents": text = cmdSubagents(subargs || null); break;
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Update help text**
|
||||
|
||||
In `cmdHelp`, add:
|
||||
|
||||
```
|
||||
/mc subagents Subagent state overview across workspaces
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Bump mc-plugin versions to 2.1.0**
|
||||
|
||||
In `mc-plugin/package.json`, change version to `"2.1.0"`.
|
||||
In `mc-plugin/openclaw.plugin.json`, change version to `"2.1.0"`.
|
||||
|
||||
- [ ] **Step 5: Verify mc-plugin loads**
|
||||
|
||||
```bash
|
||||
cd /Users/taodeng/.openclaw/projects/memory-continuity/mc-plugin
|
||||
node -e "import('./index.js').then(m => console.log('OK:', typeof m.default))"
|
||||
```
|
||||
|
||||
Expected: `OK: function`
|
||||
|
||||
- [ ] **Step 6: Commit**
|
||||
|
||||
```bash
|
||||
git add mc-plugin/
|
||||
git commit -m "feat(subagent): add /mc subagents command for cross-workspace state view"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 6: Version Verify + Git + Deploy
|
||||
|
||||
**Files:** All modified files
|
||||
|
||||
- [ ] **Step 1: Verify all versions**
|
||||
|
||||
```bash
|
||||
grep '"version"' openclaw.plugin.json package.json mc-plugin/openclaw.plugin.json mc-plugin/package.json
|
||||
```
|
||||
|
||||
Expected:
|
||||
```
|
||||
openclaw.plugin.json: "version": "5.0.0"
|
||||
package.json: "version": "5.0.0"
|
||||
mc-plugin/openclaw.plugin.json: "version": "2.1.0"
|
||||
mc-plugin/package.json: "version": "2.1.0"
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Final commit + tag + push**
|
||||
|
||||
```bash
|
||||
git add -A
|
||||
git commit -m "release: v5.0.0 — conservative subagent support (parent seed + child recovery)"
|
||||
git tag v5.0.0
|
||||
git push origin main --tags
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Deploy to Cloud**
|
||||
|
||||
```bash
|
||||
scp index.js package.json openclaw.plugin.json opc@152.67.121.35:~/.openclaw/extensions/memory-continuity/
|
||||
scp mc-plugin/index.js mc-plugin/package.json mc-plugin/openclaw.plugin.json opc@152.67.121.35:~/.openclaw/extensions/mc/
|
||||
ssh opc@152.67.121.35 "pkill -f 'openclaw.*gateway' 2>/dev/null; sleep 2; nohup openclaw gateway start > /dev/null 2>&1 &"
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Deploy to PI**
|
||||
|
||||
```bash
|
||||
scp index.js package.json openclaw.plugin.json administrator@172.16.2.232:~/.openclaw/extensions/memory-continuity/
|
||||
scp mc-plugin/index.js mc-plugin/package.json mc-plugin/openclaw.plugin.json administrator@172.16.2.232:~/.openclaw/extensions/mc/
|
||||
ssh administrator@172.16.2.232 "pkill -f 'openclaw.*gateway' 2>/dev/null; sleep 2; nohup openclaw gateway start > /dev/null 2>&1 &"
|
||||
```
|
||||
|
||||
- [ ] **Step 5: Deploy to Mac**
|
||||
|
||||
```bash
|
||||
cp index.js package.json openclaw.plugin.json ~/.openclaw/extensions/memory-continuity/
|
||||
pkill -f 'openclaw.*gateway' 2>/dev/null; sleep 2; nohup openclaw gateway start > /dev/null 2>&1 &
|
||||
```
|
||||
|
||||
- [ ] **Step 6: Verify all 3 servers**
|
||||
|
||||
```bash
|
||||
grep version ~/.openclaw/extensions/memory-continuity/package.json
|
||||
ssh opc@152.67.121.35 "grep version ~/.openclaw/extensions/memory-continuity/package.json"
|
||||
ssh administrator@172.16.2.232 "grep version ~/.openclaw/extensions/memory-continuity/package.json"
|
||||
```
|
||||
|
||||
Expected: all show `"version": "5.0.0"`.
|
||||
Reference in New Issue
Block a user