openclaw-agent-wake-protocol 1.0.0 → 1.0.5
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/index.ts +81 -129
- package/openclaw.plugin.json +1 -1
- package/package.json +8 -3
- package/src/lib.ts +148 -0
package/index.ts
CHANGED
|
@@ -23,38 +23,21 @@
|
|
|
23
23
|
|
|
24
24
|
import { exec } from "node:child_process";
|
|
25
25
|
import { promisify } from "node:util";
|
|
26
|
+
import {
|
|
27
|
+
type LifecycleStatus,
|
|
28
|
+
type AgentWakeResult,
|
|
29
|
+
type DiscoveredAgent,
|
|
30
|
+
parseDiscovery,
|
|
31
|
+
extractAgentName,
|
|
32
|
+
isNonInteractive,
|
|
33
|
+
resolveLifeId,
|
|
34
|
+
formatContextBlock,
|
|
35
|
+
} from "./src/lib.js";
|
|
26
36
|
|
|
27
37
|
const execAsync = promisify(exec);
|
|
28
38
|
|
|
29
39
|
// ── Types ──────────────────────────────────────────────────────────────────
|
|
30
40
|
|
|
31
|
-
type LifecycleStatus =
|
|
32
|
-
| "ok"
|
|
33
|
-
| "degraded"
|
|
34
|
-
| "genesis_required"
|
|
35
|
-
| "not_registered"
|
|
36
|
-
| "failed";
|
|
37
|
-
|
|
38
|
-
interface AgentWakeResult {
|
|
39
|
-
agentId: string;
|
|
40
|
-
status: LifecycleStatus;
|
|
41
|
-
/** Human-readable summary injected into context */
|
|
42
|
-
message: string;
|
|
43
|
-
/** Raw output from wake command (status=ok/degraded) */
|
|
44
|
-
wakeOutput?: string;
|
|
45
|
-
/** Instructions returned by run_genesis_interview (status=genesis_required) */
|
|
46
|
-
genesisInstructions?: string;
|
|
47
|
-
timestamp: string;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
interface DiscoveredAgent {
|
|
51
|
-
agent_id: string;
|
|
52
|
-
registered: boolean;
|
|
53
|
-
genesis_completed: boolean;
|
|
54
|
-
workspace?: string;
|
|
55
|
-
name?: string;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
41
|
interface PluginConfig {
|
|
59
42
|
/**
|
|
60
43
|
* Map of OpenClaw agent name → LIFE agent_id.
|
|
@@ -81,6 +64,15 @@ interface PluginConfig {
|
|
|
81
64
|
/** Keyed by LIFE agent_id */
|
|
82
65
|
const wakeResults = new Map<string, AgentWakeResult>();
|
|
83
66
|
|
|
67
|
+
/**
|
|
68
|
+
* Sessions that have already received the bootstrap wake injection.
|
|
69
|
+
* Prevents re-injection on tool sub-calls and follow-up turns within
|
|
70
|
+
* the same session. Entries expire after 30 minutes so long-lived
|
|
71
|
+
* sessions still get a refresh if the gateway restarts.
|
|
72
|
+
*/
|
|
73
|
+
const bootstrappedSessions = new Map<string, number>(); // sessionKey → injected-at ms
|
|
74
|
+
const BOOTSTRAP_TTL_MS = 30 * 60 * 1000; // 30 minutes
|
|
75
|
+
|
|
84
76
|
// ── Helpers ────────────────────────────────────────────────────────────────
|
|
85
77
|
|
|
86
78
|
async function mcporter(
|
|
@@ -98,97 +90,6 @@ async function mcporter(
|
|
|
98
90
|
}
|
|
99
91
|
}
|
|
100
92
|
|
|
101
|
-
/**
|
|
102
|
-
* Parse the output of `mcporter call life-gateway.discover_agents`.
|
|
103
|
-
* FastMCP returns a JSON array; mcporter may wrap it in content blocks.
|
|
104
|
-
*/
|
|
105
|
-
function parseDiscovery(raw: string): DiscoveredAgent[] {
|
|
106
|
-
// Try raw JSON array first
|
|
107
|
-
try {
|
|
108
|
-
const parsed = JSON.parse(raw);
|
|
109
|
-
if (Array.isArray(parsed)) return parsed;
|
|
110
|
-
} catch {}
|
|
111
|
-
// Try extracting JSON array embedded in text
|
|
112
|
-
const match = raw.match(/\[[\s\S]*\]/);
|
|
113
|
-
if (match) {
|
|
114
|
-
try {
|
|
115
|
-
return JSON.parse(match[0]);
|
|
116
|
-
} catch {}
|
|
117
|
-
}
|
|
118
|
-
return [];
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
function extractAgentName(sessionKey: string): string | null {
|
|
122
|
-
// sessionKey: "agent:{name}:{sessionId}" | "agent:{name}:cron:{id}"
|
|
123
|
-
const m = sessionKey.match(/^agent:([^:]+):/);
|
|
124
|
-
return m ? m[1] : null;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
function isNonInteractive(trigger: string, sessionKey: string): boolean {
|
|
128
|
-
if (/^(cron|heartbeat|automation|schedule)$/i.test(trigger)) return true;
|
|
129
|
-
if (/:cron:|:heartbeat:|:subagent:/i.test(sessionKey)) return true;
|
|
130
|
-
return false;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
function isBootstrapTurn(prompt: string, messages: unknown[]): boolean {
|
|
134
|
-
// The internal bootstrap-extra-files hook appends this phrase to the first message
|
|
135
|
-
if (prompt.includes("new session was started")) return true;
|
|
136
|
-
if (prompt.includes("Session Startup sequence")) return true;
|
|
137
|
-
return (messages?.length ?? 0) === 0;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
function formatContextBlock(result: AgentWakeResult): string {
|
|
141
|
-
const tag = `wake-protocol-status`;
|
|
142
|
-
|
|
143
|
-
if (result.status === "not_registered") {
|
|
144
|
-
return [
|
|
145
|
-
`<${tag} agent="${result.agentId}" status="NOT_REGISTERED">`,
|
|
146
|
-
`Agent ${result.agentId} is not registered in the LIFE gateway.`,
|
|
147
|
-
``,
|
|
148
|
-
`To register, call these tools in order:`,
|
|
149
|
-
` 1. life-gateway.register_agent — agent_id=${result.agentId}, name=<your name>, workspace_dir=<path>`,
|
|
150
|
-
` 2. life-gateway.initialize_life_core — agent_id=${result.agentId}`,
|
|
151
|
-
` 3. Then run genesis (see run_genesis_interview tool)`,
|
|
152
|
-
`</${tag}>`,
|
|
153
|
-
].join("\n");
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
if (result.status === "genesis_required") {
|
|
157
|
-
return [
|
|
158
|
-
`<${tag} agent="${result.agentId}" status="GENESIS_REQUIRED">`,
|
|
159
|
-
`You have not completed your LIFE Genesis interview. This must happen before your first wake.`,
|
|
160
|
-
``,
|
|
161
|
-
`The Genesis interview establishes your identity, values, and traits in the LIFE system.`,
|
|
162
|
-
`It is a one-time process — once complete, you will wake normally on every subsequent boot.`,
|
|
163
|
-
``,
|
|
164
|
-
`=== How to complete Genesis ===`,
|
|
165
|
-
`1. Read your Genesis questions: CORE/genesis/questions.md in your workspace`,
|
|
166
|
-
`2. Save your answers to: CORE/genesis/answers.md`,
|
|
167
|
-
`3. Call tool: genesis_apply (or: life-gateway.apply_genesis_answers agent_id=${result.agentId})`,
|
|
168
|
-
``,
|
|
169
|
-
`=== Instructions from LIFE gateway ===`,
|
|
170
|
-
result.genesisInstructions ?? "(no instructions returned)",
|
|
171
|
-
`</${tag}>`,
|
|
172
|
-
].join("\n");
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
if (result.status === "failed") {
|
|
176
|
-
return [
|
|
177
|
-
`<${tag} agent="${result.agentId}" status="FAILED" checked="${result.timestamp}">`,
|
|
178
|
-
`Wake protocol failed. Operating in degraded mode — proceed without LIFE context.`,
|
|
179
|
-
`Error: ${result.message}`,
|
|
180
|
-
`</${tag}>`,
|
|
181
|
-
].join("\n");
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
// ok or degraded
|
|
185
|
-
return [
|
|
186
|
-
`<${tag} agent="${result.agentId}" status="${result.status.toUpperCase()}" checked="${result.timestamp}">`,
|
|
187
|
-
result.wakeOutput ?? result.message,
|
|
188
|
-
`</${tag}>`,
|
|
189
|
-
].join("\n");
|
|
190
|
-
}
|
|
191
|
-
|
|
192
93
|
// ── Per-agent lifecycle handler ────────────────────────────────────────────
|
|
193
94
|
|
|
194
95
|
async function runAgentLifecycle(
|
|
@@ -200,8 +101,49 @@ async function runAgentLifecycle(
|
|
|
200
101
|
const timestamp = new Date().toISOString();
|
|
201
102
|
const found = discovered.find((a) => a.agent_id === lifeAgentId);
|
|
202
103
|
|
|
203
|
-
// ── Not
|
|
204
|
-
if (!found
|
|
104
|
+
// ── Not in shared-path discovery — try direct wake (e.g. quin-ea-v1) ─────
|
|
105
|
+
if (!found) {
|
|
106
|
+
logger.info(
|
|
107
|
+
`[agent-wake] ${lifeAgentId}: not in discover_agents — trying direct wake`
|
|
108
|
+
);
|
|
109
|
+
const directWake = await mcporter(
|
|
110
|
+
["call", "life-gateway.wake", `agent_id=${lifeAgentId}`],
|
|
111
|
+
timeoutMs
|
|
112
|
+
);
|
|
113
|
+
if (
|
|
114
|
+
directWake.startsWith("ERROR") ||
|
|
115
|
+
directWake.toLowerCase().includes("agent not found")
|
|
116
|
+
) {
|
|
117
|
+
logger.warn(
|
|
118
|
+
`[agent-wake] ${lifeAgentId}: not found in LIFE gateway — will prompt on boot`
|
|
119
|
+
);
|
|
120
|
+
wakeResults.set(lifeAgentId, {
|
|
121
|
+
agentId: lifeAgentId,
|
|
122
|
+
status: "not_registered",
|
|
123
|
+
message: `Agent ${lifeAgentId} is not in the LIFE gateway registry.`,
|
|
124
|
+
timestamp,
|
|
125
|
+
});
|
|
126
|
+
} else {
|
|
127
|
+
const degraded =
|
|
128
|
+
directWake.toLowerCase().includes("missing") ||
|
|
129
|
+
directWake.toLowerCase().includes("degraded") ||
|
|
130
|
+
directWake.toLowerCase().includes("error");
|
|
131
|
+
wakeResults.set(lifeAgentId, {
|
|
132
|
+
agentId: lifeAgentId,
|
|
133
|
+
status: degraded ? "degraded" : "ok",
|
|
134
|
+
message: `Wake complete for ${lifeAgentId}`,
|
|
135
|
+
wakeOutput: directWake,
|
|
136
|
+
timestamp,
|
|
137
|
+
});
|
|
138
|
+
logger.info(
|
|
139
|
+
`[agent-wake] ${lifeAgentId}: ${degraded ? "DEGRADED" : "OK"} (direct wake)`
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// ── Discovered but not yet registered ────────────────────────────────────
|
|
146
|
+
if (!found.registered) {
|
|
205
147
|
logger.warn(
|
|
206
148
|
`[agent-wake] ${lifeAgentId}: not registered in LIFE gateway — will prompt on boot`
|
|
207
149
|
);
|
|
@@ -277,7 +219,7 @@ export default function (api: any) {
|
|
|
277
219
|
|
|
278
220
|
// ── Service: discover and wake all agents at gateway startup ──────────────
|
|
279
221
|
api.registerService({
|
|
280
|
-
id: "agent-wake-protocol",
|
|
222
|
+
id: "openclaw-agent-wake-protocol",
|
|
281
223
|
|
|
282
224
|
async start() {
|
|
283
225
|
api.logger.info(
|
|
@@ -343,17 +285,27 @@ export default function (api: any) {
|
|
|
343
285
|
const result = wakeResults.get(lifeId);
|
|
344
286
|
if (!result) return;
|
|
345
287
|
|
|
346
|
-
|
|
347
|
-
|
|
288
|
+
// Genesis-required: always re-inject until the agent completes the interview
|
|
289
|
+
if (result.status === "genesis_required") {
|
|
290
|
+
api.logger.info(
|
|
291
|
+
`[agent-wake] Injecting genesis_required context for ${lifeId} (session: ${sessionKey})`
|
|
292
|
+
);
|
|
293
|
+
return { prependContext: formatContextBlock(result) };
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// For ok/degraded/failed: bootstrappedSessions is the SOLE gate.
|
|
297
|
+
// OpenClaw passes messages=[] on every hook call (each turn is stateless
|
|
298
|
+
// from the hook's perspective), so checking messages.length is unreliable.
|
|
299
|
+
// Inject once per session; re-inject only after TTL expires.
|
|
300
|
+
const lastInjected = bootstrappedSessions.get(sessionKey) ?? 0;
|
|
301
|
+
const needsBootstrap = Date.now() - lastInjected > BOOTSTRAP_TTL_MS;
|
|
302
|
+
if (!needsBootstrap) return;
|
|
348
303
|
|
|
349
|
-
|
|
350
|
-
// (so the agent keeps trying until it completes the interview)
|
|
351
|
-
const inject =
|
|
352
|
-
isBootstrapTurn(prompt, messages) || result.status === "genesis_required";
|
|
353
|
-
if (!inject) return;
|
|
304
|
+
bootstrappedSessions.set(sessionKey, Date.now());
|
|
354
305
|
|
|
306
|
+
const isRefresh = lastInjected > 0;
|
|
355
307
|
api.logger.info(
|
|
356
|
-
`[agent-wake] Injecting ${result.status} context for ${lifeId} (session: ${sessionKey})`
|
|
308
|
+
`[agent-wake] Injecting ${result.status} context for ${lifeId} (session: ${sessionKey}${isRefresh ? ", ttl-refresh" : ""})`
|
|
357
309
|
);
|
|
358
310
|
|
|
359
311
|
return { prependContext: formatContextBlock(result) };
|
package/openclaw.plugin.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"id": "agent-wake-protocol",
|
|
2
|
+
"id": "openclaw-agent-wake-protocol",
|
|
3
3
|
"name": "Agent Wake Protocol",
|
|
4
4
|
"description": "General-purpose LIFE gateway lifecycle manager for OpenClaw multi-agent systems. Handles agent registration, Genesis interview, and wake protocol for any agent configuration.",
|
|
5
5
|
"configSchema": {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openclaw-agent-wake-protocol",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.5",
|
|
4
4
|
"description": "OpenClaw extension: general-purpose LIFE gateway lifecycle manager for multi-agent systems. Handles agent registration, Genesis interview, and wake protocol for any agent configuration.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.ts",
|
|
@@ -35,7 +35,8 @@
|
|
|
35
35
|
"openclaw.plugin.json",
|
|
36
36
|
"gateway/",
|
|
37
37
|
"scripts/",
|
|
38
|
-
"README.md"
|
|
38
|
+
"README.md",
|
|
39
|
+
"src/"
|
|
39
40
|
],
|
|
40
41
|
"engines": {
|
|
41
42
|
"node": ">=18"
|
|
@@ -44,6 +45,10 @@
|
|
|
44
45
|
"openclaw": ">=0.1.0"
|
|
45
46
|
},
|
|
46
47
|
"devDependencies": {
|
|
47
|
-
"typescript": "^5.0.0"
|
|
48
|
+
"typescript": "^5.0.0",
|
|
49
|
+
"vitest": "^2.0.0"
|
|
50
|
+
},
|
|
51
|
+
"scripts": {
|
|
52
|
+
"test": "vitest run"
|
|
48
53
|
}
|
|
49
54
|
}
|
package/src/lib.ts
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure utility functions — no I/O, fully testable.
|
|
3
|
+
* Imported by index.ts and tested by tests/lib.test.ts.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export type LifecycleStatus =
|
|
7
|
+
| "ok"
|
|
8
|
+
| "degraded"
|
|
9
|
+
| "genesis_required"
|
|
10
|
+
| "not_registered"
|
|
11
|
+
| "failed";
|
|
12
|
+
|
|
13
|
+
export interface AgentWakeResult {
|
|
14
|
+
agentId: string;
|
|
15
|
+
status: LifecycleStatus;
|
|
16
|
+
message: string;
|
|
17
|
+
wakeOutput?: string;
|
|
18
|
+
genesisInstructions?: string;
|
|
19
|
+
timestamp: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface DiscoveredAgent {
|
|
23
|
+
agent_id: string;
|
|
24
|
+
registered: boolean;
|
|
25
|
+
genesis_completed: boolean;
|
|
26
|
+
workspace?: string;
|
|
27
|
+
name?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Parse the raw stdout of `mcporter call life-gateway.discover_agents`.
|
|
32
|
+
* FastMCP may return a plain JSON array OR wrap it in content blocks.
|
|
33
|
+
*/
|
|
34
|
+
export function parseDiscovery(raw: string): DiscoveredAgent[] {
|
|
35
|
+
try {
|
|
36
|
+
const parsed = JSON.parse(raw);
|
|
37
|
+
if (Array.isArray(parsed)) return parsed;
|
|
38
|
+
} catch {}
|
|
39
|
+
const match = raw.match(/\[[\s\S]*\]/);
|
|
40
|
+
if (match) {
|
|
41
|
+
try {
|
|
42
|
+
return JSON.parse(match[0]);
|
|
43
|
+
} catch {}
|
|
44
|
+
}
|
|
45
|
+
return [];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Extract the OpenClaw agent name from a session key.
|
|
50
|
+
* Format: "agent:{name}:{sessionId}" or "agent:{name}:cron:{id}"
|
|
51
|
+
*/
|
|
52
|
+
export function extractAgentName(sessionKey: string): string | null {
|
|
53
|
+
const m = sessionKey.match(/^agent:([^:]+):/);
|
|
54
|
+
return m ? m[1] : null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Returns true when the session is a non-interactive trigger (cron, heartbeat,
|
|
59
|
+
* subagent) where wake-protocol context injection should be skipped.
|
|
60
|
+
*/
|
|
61
|
+
export function isNonInteractive(trigger: string, sessionKey: string): boolean {
|
|
62
|
+
if (/^(cron|heartbeat|automation|schedule)$/i.test(trigger)) return true;
|
|
63
|
+
if (/:cron:|:heartbeat:|:subagent:/i.test(sessionKey)) return true;
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Returns true when this is the bootstrap turn of a session — the first
|
|
69
|
+
* message where wake-protocol context should be prepended.
|
|
70
|
+
*/
|
|
71
|
+
export function isBootstrapTurn(prompt: string, messages: unknown[]): boolean {
|
|
72
|
+
if (prompt.includes("new session was started")) return true;
|
|
73
|
+
if (prompt.includes("Session Startup sequence")) return true;
|
|
74
|
+
return (messages?.length ?? 0) === 0;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Resolve a LIFE agent_id from an OpenClaw agent name using an explicit
|
|
79
|
+
* map first, then falling back to name+suffix.
|
|
80
|
+
*/
|
|
81
|
+
export function resolveLifeId(
|
|
82
|
+
openclawName: string,
|
|
83
|
+
agentIdMap: Record<string, string>,
|
|
84
|
+
agentIdSuffix: string
|
|
85
|
+
): string {
|
|
86
|
+
return agentIdMap[openclawName] ?? `${openclawName}${agentIdSuffix}`;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Build the XML-style context block injected into the agent's prependContext.
|
|
91
|
+
*/
|
|
92
|
+
export function formatContextBlock(result: AgentWakeResult): string {
|
|
93
|
+
const tag = `wake-protocol-status`;
|
|
94
|
+
|
|
95
|
+
if (result.status === "not_registered") {
|
|
96
|
+
return [
|
|
97
|
+
`<${tag} agent="${result.agentId}" status="NOT_REGISTERED">`,
|
|
98
|
+
`Agent ${result.agentId} is not registered in the LIFE gateway.`,
|
|
99
|
+
``,
|
|
100
|
+
`To register, call these tools in order:`,
|
|
101
|
+
` 1. life-gateway.register_agent — agent_id=${result.agentId}, name=<your name>, workspace_dir=<path>`,
|
|
102
|
+
` 2. life-gateway.initialize_life_core — agent_id=${result.agentId}`,
|
|
103
|
+
` 3. Then run genesis (see run_genesis_interview tool)`,
|
|
104
|
+
`</${tag}>`,
|
|
105
|
+
].join("\n");
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (result.status === "genesis_required") {
|
|
109
|
+
return [
|
|
110
|
+
`<${tag} agent="${result.agentId}" status="GENESIS_REQUIRED">`,
|
|
111
|
+
`You have not completed your LIFE Genesis interview. This must happen before your first wake.`,
|
|
112
|
+
``,
|
|
113
|
+
`The Genesis interview establishes your identity, values, and traits in the LIFE system.`,
|
|
114
|
+
`It is a one-time process — once complete, you will wake normally on every subsequent boot.`,
|
|
115
|
+
``,
|
|
116
|
+
`=== How to complete Genesis ===`,
|
|
117
|
+
`1. Read your Genesis questions: CORE/genesis/questions.md in your workspace`,
|
|
118
|
+
`2. Save your answers to: CORE/genesis/answers.md`,
|
|
119
|
+
`3. Call tool: genesis_apply (or: life-gateway.apply_genesis_answers agent_id=${result.agentId})`,
|
|
120
|
+
``,
|
|
121
|
+
`=== Instructions from LIFE gateway ===`,
|
|
122
|
+
result.genesisInstructions ?? "(no instructions returned)",
|
|
123
|
+
`</${tag}>`,
|
|
124
|
+
].join("\n");
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (result.status === "failed") {
|
|
128
|
+
return [
|
|
129
|
+
`<${tag} agent="${result.agentId}" status="FAILED" checked="${result.timestamp}">`,
|
|
130
|
+
`Wake protocol failed. Operating in degraded mode — proceed without LIFE context.`,
|
|
131
|
+
`Error: ${result.message}`,
|
|
132
|
+
`</${tag}>`,
|
|
133
|
+
].join("\n");
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// ok or degraded — frame clearly so the model embodies the identity rather than reads it as a restriction
|
|
137
|
+
const preamble = result.status === "ok"
|
|
138
|
+
? `You are ${result.agentId}. The following is your persistent identity, memory, and current state loaded from your LIFE system. This is who you are — embody it fully and proceed with your normal duties. "No privileged action without approval" refers to irreversible external actions (wire transfers, deployments, deletions), NOT to conversational assistance or analysis.`
|
|
139
|
+
: `You are ${result.agentId}. LIFE system is in degraded state — some modules unavailable. Proceed using available context.`;
|
|
140
|
+
|
|
141
|
+
return [
|
|
142
|
+
`<${tag} agent="${result.agentId}" status="${result.status.toUpperCase()}" checked="${result.timestamp}">`,
|
|
143
|
+
preamble,
|
|
144
|
+
``,
|
|
145
|
+
result.wakeOutput ?? result.message,
|
|
146
|
+
`</${tag}>`,
|
|
147
|
+
].join("\n");
|
|
148
|
+
}
|