@rubytech/create-maxy 1.0.450 → 1.0.451
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/package.json +1 -1
- package/payload/maxy/server.js +48 -23
- package/payload/platform/plugins/admin/hooks/session-start.sh +21 -5
- package/payload/platform/plugins/admin/hooks/test-agent-creation-gate.sh +43 -10
- package/payload/platform/plugins/admin/skills/public-agent-manager/skill.md +1 -1
- package/payload/platform/templates/agents/admin/IDENTITY.md +2 -0
- package/payload/platform/templates/agents/admin/LEARNINGS.md +0 -18
package/package.json
CHANGED
package/payload/maxy/server.js
CHANGED
|
@@ -9398,8 +9398,9 @@ async function safeSaveCreds(authDir, saveCreds) {
|
|
|
9398
9398
|
}
|
|
9399
9399
|
try {
|
|
9400
9400
|
await Promise.resolve(saveCreds());
|
|
9401
|
+
console.error(`${TAG2} creds saved successfully to ${authDir}`);
|
|
9401
9402
|
} catch (err) {
|
|
9402
|
-
console.error(`${TAG2} failed saving creds: ${String(err)}`);
|
|
9403
|
+
console.error(`${TAG2} failed saving creds to ${authDir}: ${String(err)}`);
|
|
9403
9404
|
}
|
|
9404
9405
|
}
|
|
9405
9406
|
function enqueueSaveCreds(authDir, saveCreds) {
|
|
@@ -9413,44 +9414,63 @@ async function createWaSocket(opts) {
|
|
|
9413
9414
|
maybeRestoreCredsFromBackup(authDir);
|
|
9414
9415
|
const { state, saveCreds } = await useMultiFileAuthState(authDir);
|
|
9415
9416
|
const { version: version2 } = await fetchLatestBaileysVersion();
|
|
9416
|
-
const
|
|
9417
|
-
|
|
9418
|
-
|
|
9419
|
-
|
|
9420
|
-
|
|
9421
|
-
|
|
9422
|
-
|
|
9423
|
-
|
|
9424
|
-
|
|
9425
|
-
|
|
9426
|
-
|
|
9427
|
-
|
|
9428
|
-
|
|
9429
|
-
|
|
9430
|
-
}
|
|
9431
|
-
|
|
9417
|
+
const BAILEYS_TAG = "[whatsapp:baileys]";
|
|
9418
|
+
function makeBaileysLogger() {
|
|
9419
|
+
const logger = {
|
|
9420
|
+
level: "warn",
|
|
9421
|
+
child: () => makeBaileysLogger(),
|
|
9422
|
+
trace: () => {
|
|
9423
|
+
},
|
|
9424
|
+
debug: () => {
|
|
9425
|
+
},
|
|
9426
|
+
info: () => {
|
|
9427
|
+
},
|
|
9428
|
+
warn: (...args) => console.error(BAILEYS_TAG, "WARN", ...args),
|
|
9429
|
+
error: (...args) => console.error(BAILEYS_TAG, "ERROR", ...args),
|
|
9430
|
+
fatal: (...args) => console.error(BAILEYS_TAG, "FATAL", ...args)
|
|
9431
|
+
};
|
|
9432
|
+
return logger;
|
|
9433
|
+
}
|
|
9434
|
+
const baileysLogger = makeBaileysLogger();
|
|
9432
9435
|
const sock = makeWASocket({
|
|
9433
9436
|
auth: {
|
|
9434
9437
|
creds: state.creds,
|
|
9435
|
-
keys: makeCacheableSignalKeyStore(state.keys,
|
|
9438
|
+
keys: makeCacheableSignalKeyStore(state.keys, baileysLogger)
|
|
9436
9439
|
},
|
|
9437
9440
|
version: version2,
|
|
9438
|
-
logger:
|
|
9441
|
+
logger: baileysLogger,
|
|
9439
9442
|
printQRInTerminal: false,
|
|
9440
9443
|
browser: Browsers.macOS("Chrome"),
|
|
9441
9444
|
syncFullHistory: false,
|
|
9442
9445
|
markOnlineOnConnect: false
|
|
9443
9446
|
});
|
|
9444
|
-
sock.ev.on("creds.update", () =>
|
|
9447
|
+
sock.ev.on("creds.update", () => {
|
|
9448
|
+
console.error(`${TAG2} creds.update received \u2014 saving to ${authDir}`);
|
|
9449
|
+
enqueueSaveCreds(authDir, saveCreds);
|
|
9450
|
+
});
|
|
9451
|
+
let qrSequence = 0;
|
|
9445
9452
|
sock.ev.on("connection.update", (update) => {
|
|
9446
9453
|
try {
|
|
9454
|
+
const parts = [];
|
|
9455
|
+
if (update.connection) parts.push(`connection=${update.connection}`);
|
|
9456
|
+
if (update.qr) parts.push(`qr=#${++qrSequence}`);
|
|
9457
|
+
if (update.isNewLogin !== void 0) parts.push(`isNewLogin=${update.isNewLogin}`);
|
|
9458
|
+
if (update.receivedPendingNotifications !== void 0) parts.push(`pendingNotifications=${update.receivedPendingNotifications}`);
|
|
9459
|
+
if (update.connection === "close" && update.lastDisconnect) {
|
|
9460
|
+
const status = getStatusCode(update.lastDisconnect.error);
|
|
9461
|
+
parts.push(`disconnectStatus=${status ?? "unknown"}`);
|
|
9462
|
+
parts.push(`disconnectReason=${formatError(update.lastDisconnect.error)}`);
|
|
9463
|
+
}
|
|
9464
|
+
if (!silent && parts.length > 0) {
|
|
9465
|
+
console.error(`${TAG2} connection.update ${parts.join(" ")}`);
|
|
9466
|
+
}
|
|
9447
9467
|
if (update.qr && onQr) {
|
|
9448
9468
|
onQr(update.qr);
|
|
9449
9469
|
}
|
|
9450
9470
|
if (update.connection === "close") {
|
|
9451
9471
|
const status = getStatusCode(update.lastDisconnect?.error);
|
|
9452
9472
|
if (status === DisconnectReason.loggedOut && !silent) {
|
|
9453
|
-
console.error(`${TAG2} session logged out \u2014 re-link required`);
|
|
9473
|
+
console.error(`${TAG2} session logged out (401) \u2014 re-link required`);
|
|
9454
9474
|
}
|
|
9455
9475
|
}
|
|
9456
9476
|
if (update.connection === "open" && !silent) {
|
|
@@ -9580,16 +9600,21 @@ async function startLogin(opts) {
|
|
|
9580
9600
|
);
|
|
9581
9601
|
let sock;
|
|
9582
9602
|
let pendingQr = null;
|
|
9603
|
+
let loginQrCount = 0;
|
|
9583
9604
|
try {
|
|
9584
9605
|
sock = await createWaSocket({
|
|
9585
9606
|
authDir,
|
|
9586
9607
|
onQr: (qr2) => {
|
|
9587
|
-
|
|
9608
|
+
loginQrCount++;
|
|
9609
|
+
if (pendingQr) {
|
|
9610
|
+
console.error(`${TAG3} QR rotation #${loginQrCount} received for account=${accountId} \u2014 not forwarded (initial QR already captured)`);
|
|
9611
|
+
return;
|
|
9612
|
+
}
|
|
9588
9613
|
pendingQr = qr2;
|
|
9589
9614
|
const current = activeLogins.get(accountId);
|
|
9590
9615
|
if (current && !current.qr) current.qr = qr2;
|
|
9591
9616
|
clearTimeout(qrTimer);
|
|
9592
|
-
console.error(`${TAG3} QR received for account=${accountId}`);
|
|
9617
|
+
console.error(`${TAG3} QR #${loginQrCount} received for account=${accountId} \u2014 forwarding to caller`);
|
|
9593
9618
|
resolveQr?.(qr2);
|
|
9594
9619
|
}
|
|
9595
9620
|
});
|
|
@@ -30,9 +30,13 @@ if [ ! -f "$DURABLE_STATE" ]; then
|
|
|
30
30
|
fi
|
|
31
31
|
|
|
32
32
|
# Staleness + completion check — reject durable state that is stale (>6h, missing
|
|
33
|
-
# timestamp) OR already complete (all gates true
|
|
34
|
-
# completed durable file would be recovered on
|
|
35
|
-
# misleading "resume creation" message.
|
|
33
|
+
# timestamp) OR already complete (all gates true AND all gated files exist on
|
|
34
|
+
# disk). Without this, a stale or completed durable file would be recovered on
|
|
35
|
+
# every new session, injecting a misleading "resume creation" message.
|
|
36
|
+
#
|
|
37
|
+
# "All gates true" alone is NOT completion — it means user approval is done but
|
|
38
|
+
# file writes may still be pending. Deleting state at that point causes the
|
|
39
|
+
# remaining writes to fail with "invoke the public-agent-manager skill first."
|
|
36
40
|
DURABLE_VALID=$(python3 -c "
|
|
37
41
|
import json, sys
|
|
38
42
|
from datetime import datetime, timezone
|
|
@@ -49,10 +53,22 @@ try:
|
|
|
49
53
|
if age_hours > 6 or age_hours < 0:
|
|
50
54
|
print('stale')
|
|
51
55
|
sys.exit(0)
|
|
52
|
-
# All gates true
|
|
56
|
+
# All gates true AND all files on disk = truly complete. All gates true
|
|
57
|
+
# alone means the user approved everything but writes may still be pending.
|
|
58
|
+
# Must match the PostToolUse completion check in agent-creation-post.sh.
|
|
53
59
|
gates = state.get('gates', {})
|
|
54
60
|
if isinstance(gates, dict) and set(gates.keys()) == {'soul', 'knowledge', 'config'} and all(v is True for v in gates.values()):
|
|
55
|
-
|
|
61
|
+
slug = state.get('slug', '')
|
|
62
|
+
if slug:
|
|
63
|
+
import os
|
|
64
|
+
agent_dir = os.path.join('$ACCOUNT_DIR', 'agents', slug)
|
|
65
|
+
gated_files = ['SOUL.md', 'KNOWLEDGE.md', 'config.json']
|
|
66
|
+
if all(os.path.isfile(os.path.join(agent_dir, f)) for f in gated_files):
|
|
67
|
+
print('complete')
|
|
68
|
+
else:
|
|
69
|
+
print('valid')
|
|
70
|
+
else:
|
|
71
|
+
print('valid')
|
|
56
72
|
else:
|
|
57
73
|
print('valid')
|
|
58
74
|
except Exception:
|
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
# - Allow Bash writes after gates pass
|
|
14
14
|
# - Allow all gated writes when all gates pass
|
|
15
15
|
# - Clean up state immediately when all files exist on disk (PostToolUse)
|
|
16
|
-
# -
|
|
16
|
+
# - Recover all-gates-true state on session start when files not yet written
|
|
17
|
+
# - Detect and clean up truly completed state on session start (all gates true + files on disk)
|
|
17
18
|
# - Ignore non-document-editor/form components
|
|
18
19
|
# - Ignore _componentDone for admin agent
|
|
19
20
|
# - Handle gate advancement idempotently
|
|
@@ -721,10 +722,11 @@ fi
|
|
|
721
722
|
rm -f "$STATE_FILE"
|
|
722
723
|
|
|
723
724
|
# ---------------------------------------------------------------------------
|
|
724
|
-
# Test 30: Session-start
|
|
725
|
+
# Test 30: Session-start RECOVERS all-gates-true state when files are missing
|
|
725
726
|
# ---------------------------------------------------------------------------
|
|
726
|
-
echo "Test 30: Session-start
|
|
727
|
+
echo "Test 30: Session-start recovers all-gates-true state when files are missing"
|
|
727
728
|
rm -f "$STATE_FILE"
|
|
729
|
+
rm -f "$ACCOUNT_DIR/agents/test-agent/SOUL.md" "$ACCOUNT_DIR/agents/test-agent/KNOWLEDGE.md" "$ACCOUNT_DIR/agents/test-agent/config.json" 2>/dev/null || true
|
|
728
730
|
DURABLE_FILE="$ACCOUNT_DIR/.claude/agent-create-state.json"
|
|
729
731
|
|
|
730
732
|
python3 -c "
|
|
@@ -741,16 +743,47 @@ with open('$DURABLE_FILE', 'w') as f:
|
|
|
741
743
|
|
|
742
744
|
bash "$SCRIPT_DIR/session-start.sh" "$ACCOUNT_DIR" admin 2>/dev/null || true
|
|
743
745
|
|
|
744
|
-
if [
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
746
|
+
if [ -f "$STATE_FILE" ] && [ -f "$DURABLE_FILE" ]; then
|
|
747
|
+
pass "All-gates-true state recovered (files not yet written — writes still pending)"
|
|
748
|
+
else
|
|
749
|
+
fail "All-gates-true state should be recovered when gated files are missing from disk"
|
|
750
|
+
fi
|
|
751
|
+
|
|
752
|
+
rm -f "$STATE_FILE"
|
|
753
|
+
|
|
754
|
+
# ---------------------------------------------------------------------------
|
|
755
|
+
# Test 30a: Session-start does NOT recover truly completed state (all gates true + all files on disk)
|
|
756
|
+
# ---------------------------------------------------------------------------
|
|
757
|
+
echo "Test 30a: Session-start does NOT recover truly completed state"
|
|
758
|
+
|
|
759
|
+
# Create all gated files on disk
|
|
760
|
+
touch "$ACCOUNT_DIR/agents/test-agent/SOUL.md"
|
|
761
|
+
touch "$ACCOUNT_DIR/agents/test-agent/KNOWLEDGE.md"
|
|
762
|
+
touch "$ACCOUNT_DIR/agents/test-agent/config.json"
|
|
763
|
+
|
|
764
|
+
python3 -c "
|
|
765
|
+
import json
|
|
766
|
+
from datetime import datetime, timezone
|
|
767
|
+
state = {
|
|
768
|
+
'slug': 'test-agent',
|
|
769
|
+
'started': datetime.now(timezone.utc).isoformat(),
|
|
770
|
+
'gates': {'soul': True, 'knowledge': True, 'config': True}
|
|
771
|
+
}
|
|
772
|
+
with open('$DURABLE_FILE', 'w') as f:
|
|
773
|
+
json.dump(state, f)
|
|
774
|
+
"
|
|
775
|
+
|
|
776
|
+
bash "$SCRIPT_DIR/session-start.sh" "$ACCOUNT_DIR" admin 2>/dev/null || true
|
|
777
|
+
|
|
778
|
+
if [ ! -f "$STATE_FILE" ] && [ ! -f "$DURABLE_FILE" ]; then
|
|
779
|
+
pass "Truly completed state (all gates true + all files on disk) deleted"
|
|
750
780
|
else
|
|
751
|
-
fail "
|
|
781
|
+
fail "Truly completed state should be deleted — both gates passed and files written"
|
|
752
782
|
fi
|
|
753
783
|
|
|
784
|
+
# Clean up gated files for subsequent tests
|
|
785
|
+
rm -f "$ACCOUNT_DIR/agents/test-agent/SOUL.md" "$ACCOUNT_DIR/agents/test-agent/KNOWLEDGE.md" "$ACCOUNT_DIR/agents/test-agent/config.json"
|
|
786
|
+
|
|
754
787
|
# ---------------------------------------------------------------------------
|
|
755
788
|
# Test 31: PostToolUse cleans up state after last gated file is written
|
|
756
789
|
# ---------------------------------------------------------------------------
|
|
@@ -94,7 +94,7 @@ SOUL.md is strictly personality. When the user provides content for SOUL.md, rou
|
|
|
94
94
|
- **Engagement strategy, qualification flows, component usage** → handled by selected plugins
|
|
95
95
|
- **Boundaries, tool rules, procedural instructions** → IDENTITY.md
|
|
96
96
|
|
|
97
|
-
SOUL.md answers "what does this agent feel like to talk to?" — tone, warmth, formality, humour, greeting personality. SOUL.md must not restate constraints from IDENTITY.md. When user-provided content could belong to either file, apply this test: "does this restrict what the agent does?" → IDENTITY.md. "Does this describe how the agent sounds?" → SOUL.md. If content does both (e.g. "always respond politely"), route to SOUL — the constraint is a consequence of tone, not an operational boundary. Present SOUL.md via `document-editor` for the user to review and approve.
|
|
97
|
+
SOUL.md answers "what does this agent feel like to talk to?" — tone, warmth, formality, humour, greeting personality. SOUL.md must not restate constraints from IDENTITY.md. When user-provided content could belong to either file, apply this test: "does this restrict what the agent does?" → IDENTITY.md. "Does this describe how the agent sounds?" → SOUL.md. If content does both (e.g. "always respond politely"), route to SOUL — the constraint is a consequence of tone, not an operational boundary. Present SOUL.md via `document-editor` for the user to review and approve. Follow the document-editor encoding constraint in IDENTITY.md when generating content.
|
|
98
98
|
|
|
99
99
|
## KNOWLEDGE.md Population
|
|
100
100
|
|
|
@@ -127,6 +127,8 @@ The user can also launch the browser independently from the header menu. Both pa
|
|
|
127
127
|
|
|
128
128
|
When the user asks to view, review, attach, or download a file or document, render it via `render-component` with `name: "document-editor"` and `data: { title, content }`. This gives a render-review-edit-download flow — the user sees the content inline, can edit it, and can download it as a `.md` file directly from the component. Do not use `memory-write`, `memory-ingest`, or other tools to deliver file content to the user. Synthesised content (summaries, reports, drafts) follows the same path — render via `document-editor` so the user can review and download.
|
|
129
129
|
|
|
130
|
+
The document-editor's markdown parser fails silently on these specific typographical characters: em dashes (U+2014), en dashes (U+2013), curly double quotes (U+201C, U+201D), curly single quotes (U+2018, U+2019), and horizontal ellipsis (U+2026). Replace them with their plain equivalents — hyphens, straight quotes, three periods. Currency symbols (£, €), accented characters, and other Unicode are unaffected — use them normally.
|
|
131
|
+
|
|
130
132
|
## Plugins and Skills
|
|
131
133
|
|
|
132
134
|
Your behaviour is defined by your loaded plugins. Follow them. The `<plugin-manifest>` lists every active plugin's skills and references with their file paths. To load any skill or reference, call `plugin-read` with the plugin's directory name and the file path from the manifest. The manifest is authoritative for plugin discovery and skill loading.
|
|
@@ -1,21 +1,3 @@
|
|
|
1
1
|
# Learnings
|
|
2
2
|
|
|
3
3
|
Accumulated tool-call patterns. Each entry records a mistake and the correct approach. Consult before calling unfamiliar tools.
|
|
4
|
-
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
### Deferred tool schemas must be loaded before use
|
|
8
|
-
|
|
9
|
-
MCP tools have deferred schemas — parameter names, types, and required fields are not in context until fetched. Calling an MCP tool without loading its schema produces wrong parameter names, wrong types, and missing required fields. Always call `ToolSearch` for a tool before its first use in a session. This applies to every `mcp__*` tool — memory, tasks, scheduling, contacts, admin, cloudflare, telegram, documents.
|
|
10
|
-
|
|
11
|
-
### memory-update requires Neo4j element IDs
|
|
12
|
-
|
|
13
|
-
The `memory-update` tool identifies nodes by their Neo4j element ID — an integer returned in `memory-search` results (the `elementId` field). Do not pass UUIDs, attachment IDs, or string identifiers. If you do not have the element ID, run `memory-search` first to retrieve it.
|
|
14
|
-
|
|
15
|
-
### memory-ingest complex parameters must be arrays
|
|
16
|
-
|
|
17
|
-
The `memory-ingest` tool's `sections` and `keywords` parameters are arrays, not strings. `sections` is an array of objects; `keywords` is an array of strings. Passing a string where an array is expected produces a schema validation error. Load the tool schema via `ToolSearch` to confirm the expected types before calling.
|
|
18
|
-
|
|
19
|
-
### document-editor content must be ASCII-safe
|
|
20
|
-
|
|
21
|
-
The `document-editor` markdown parser silently fails on Unicode typographical characters — em dashes (—), en dashes (–), curly quotes (" " ' '), and ellipsis (…). When these appear in the `content` field, the editor renders empty and the user sees a blank document. Use ASCII alternatives: hyphens (-), straight quotes (" '), and three periods (...). This applies to all `document-editor` content including SOUL.md, KNOWLEDGE.md, and any other document presented for review.
|