@rubytech/create-maxy 1.0.473 → 1.0.474

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.
Files changed (30) hide show
  1. package/package.json +1 -1
  2. package/payload/platform/plugins/admin/PLUGIN.md +2 -2
  3. package/payload/platform/plugins/docs/references/migration-guide.md +29 -21
  4. package/payload/platform/plugins/memory/PLUGIN.md +6 -0
  5. package/payload/platform/plugins/memory/mcp/dist/index.js +58 -22
  6. package/payload/platform/plugins/memory/mcp/dist/index.js.map +1 -1
  7. package/payload/platform/plugins/memory/mcp/dist/tools/memory-reindex.d.ts +1 -0
  8. package/payload/platform/plugins/memory/mcp/dist/tools/memory-reindex.d.ts.map +1 -1
  9. package/payload/platform/plugins/memory/mcp/dist/tools/memory-reindex.js +35 -23
  10. package/payload/platform/plugins/memory/mcp/dist/tools/memory-reindex.js.map +1 -1
  11. package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.d.ts +5 -0
  12. package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.d.ts.map +1 -1
  13. package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.js +40 -7
  14. package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.js.map +1 -1
  15. package/payload/platform/scripts/migrate-import.sh +17 -11
  16. package/payload/platform/scripts/seed-neo4j.sh +2 -55
  17. package/payload/server/public/assets/ChatInput-BZayQkAS.css +1 -0
  18. package/payload/server/public/assets/{admin-BEbxw46k.js → admin-CW4uB1GJ.js} +2 -2
  19. package/payload/server/public/assets/public-DEZaIlO-.js +5 -0
  20. package/payload/server/public/index.html +3 -3
  21. package/payload/server/public/public.html +3 -3
  22. package/payload/server/server.js +44 -102
  23. package/payload/platform/plugins/admin/hooks/agent-creation-approval.sh +0 -161
  24. package/payload/platform/plugins/admin/hooks/agent-creation-gate.sh +0 -317
  25. package/payload/platform/plugins/admin/hooks/agent-creation-post.sh +0 -165
  26. package/payload/platform/plugins/admin/hooks/session-start.sh +0 -104
  27. package/payload/platform/plugins/admin/hooks/test-agent-creation-gate.sh +0 -926
  28. package/payload/server/public/assets/ChatInput-BEwQxFL9.css +0 -1
  29. package/payload/server/public/assets/public-OdyNuhVE.js +0 -5
  30. /package/payload/server/public/assets/{ChatInput-Dnp1FLis.js → ChatInput-DgjefIvo.js} +0 -0
@@ -1,317 +0,0 @@
1
- #!/usr/bin/env bash
2
- # PreToolUse hook — agent creation approval gate.
3
- #
4
- # Blocks Write, Edit, and Bash writes to public agent files (SOUL.md,
5
- # KNOWLEDGE.md, config.json) unless the agent creation workflow has been
6
- # started AND the corresponding approval gate has been passed.
7
- #
8
- # Decision tree:
9
- #
10
- # Bash command targeting the state file itself?
11
- # │
12
- # ├─ Yes → exit 2: "State file is managed by hooks only."
13
- # │
14
- # └─ No → (continue)
15
- #
16
- # Write or Edit to agents/{slug}/* where slug ≠ admin?
17
- # │
18
- # ├─ No → exit 0 (not an agent file, irrelevant)
19
- # │
20
- # └─ Yes → (gate check below)
21
- #
22
- # Bash command writing to agents/{slug}/* where slug ≠ admin?
23
- # │ (write patterns: >, >>, cat.*>, tee, cp, mv targeting gated files)
24
- # │
25
- # ├─ No → exit 0 (not a write to an agent file)
26
- # │
27
- # └─ Yes → (gate check below)
28
- #
29
- # Gate check:
30
- # state file exists?
31
- # │
32
- # ├─ No → exit 2: "Load public-agent-manager skill first."
33
- # │
34
- # └─ Yes → has 'started' ISO timestamp?
35
- # │
36
- # ├─ No → delete state file, exit 2 (invalid)
37
- # │
38
- # └─ Yes → age > 6 hours?
39
- # │
40
- # ├─ Yes → delete state file, exit 2 (stale)
41
- # │
42
- # └─ No → structure valid (3 boolean gate keys)?
43
- # │
44
- # ├─ No → delete state file, exit 2 (malformed)
45
- # │
46
- # └─ Yes → gate for this file true?
47
- # │
48
- # ├─ Yes → exit 0 (approved)
49
- # │
50
- # └─ No → exit 2 (gate message)
51
- #
52
- # Python parse errors = fail open (exit 0) — never block on an infra issue.
53
- # Structural validation failures = fail closed (exit 2) — forged/stale state.
54
- # Admin agent files (agents/admin/*) are exempt.
55
- #
56
- # The Bash gate is defense-in-depth — it catches common fallback patterns
57
- # (cat >, tee, cp, mv) but cannot catch all possible write vectors
58
- # (e.g. python3 -c "open(...).write(...)"). The primary enforcement is
59
- # the Write/Edit gate. The Bash gate also protects the state file itself
60
- # from being written or deleted by the agent.
61
- #
62
- # Exit 0: allow
63
- # Exit 2: block (stderr shown to agent)
64
- # Env: AGENT_CREATE_STATE_FILE (override volatile path, for testing)
65
- # ACCOUNT_DIR (account directory for durable state cleanup)
66
- # GATE_LOG_FILE (override log path, for testing)
67
-
68
- if [ -n "$GATE_LOG_FILE" ]; then
69
- GATE_LOG="$GATE_LOG_FILE"
70
- elif [ -n "$ACCOUNT_DIR" ]; then
71
- mkdir -p "$ACCOUNT_DIR/logs"
72
- GATE_LOG="$ACCOUNT_DIR/logs/maxy-gate.log"
73
- else
74
- echo "ACCOUNT_DIR is not set. Cannot determine gate log path." >&2
75
- exit 2
76
- fi
77
- STATE_FILE="${AGENT_CREATE_STATE_FILE:-/tmp/maxy-agent-create-state.json}"
78
- ACCOUNT_DIR="${ACCOUNT_DIR:-}"
79
- DURABLE_STATE="${ACCOUNT_DIR:+$ACCOUNT_DIR/.claude/agent-create-state.json}"
80
-
81
- # Read stdin (tool call JSON from Claude Code hook protocol)
82
- INPUT=$(cat)
83
-
84
- TOOL_NAME=$(echo "$INPUT" | python3 -c \
85
- "import sys,json; d=json.load(sys.stdin); print(d.get('tool_name',''))" \
86
- 2>>"$GATE_LOG" || echo "")
87
-
88
- AGENT_SLUG=""
89
- AGENT_FILE=""
90
- GATE_FLAG=""
91
-
92
- case "$TOOL_NAME" in
93
- Write|Edit)
94
- # Extract file_path from tool input
95
- FILE_PATH=$(echo "$INPUT" | python3 -c \
96
- "import sys,json; d=json.load(sys.stdin); print(d.get('tool_input',{}).get('file_path',''))" \
97
- 2>>"$GATE_LOG" || echo "")
98
-
99
- [[ -z "$FILE_PATH" ]] && exit 0
100
-
101
- # Block Write/Edit targeting the state file itself (volatile or durable).
102
- # Same protection as the Bash handler — the agent must not forge or
103
- # destroy gate state via any tool.
104
- if [[ "$FILE_PATH" == *maxy-agent-create-state* ]] || [[ "$FILE_PATH" == *agent-create-state.json ]]; then
105
- echo "Agent creation gate: the gate state file is managed by hooks only. Do not write to or delete it directly." >&2
106
- exit 2
107
- fi
108
-
109
- # Check if this is an agent file: path contains agents/<slug>/<file>
110
- AGENT_INFO=$(python3 -c "
111
- import re
112
- path = '''$FILE_PATH'''
113
- m = re.search(r'agents/([^/]+)/([^/]+)$', path)
114
- if m:
115
- print(m.group(1) + ' ' + m.group(2))
116
- else:
117
- print('')
118
- " 2>>"$GATE_LOG" || echo "")
119
-
120
- [[ -z "$AGENT_INFO" ]] && exit 0
121
-
122
- AGENT_SLUG="${AGENT_INFO%% *}"
123
- AGENT_FILE="${AGENT_INFO##* }"
124
- ;;
125
-
126
- Bash)
127
- # --- Bash state-file protection (defense-in-depth) ---
128
- # Block commands that write to or delete the state file itself.
129
- # The state file is managed exclusively by hooks — the agent must not
130
- # forge or destroy gate state via shell commands.
131
- STATE_FILE_TARGETED=$(echo "$INPUT" | python3 -c "
132
- import sys, json, re
133
- d = json.load(sys.stdin)
134
- cmd = d.get('tool_input', {}).get('command', '')
135
- if re.search(r'(>|>>|cat\s.*>|tee\s|echo\s.*>|cp\s|mv\s|rm\s).*maxy-agent-create-state', cmd):
136
- print('yes')
137
- else:
138
- print('no')
139
- " 2>>"$GATE_LOG" || echo "no")
140
-
141
- if [[ "$STATE_FILE_TARGETED" == "yes" ]]; then
142
- echo "Agent creation gate: the gate state file is managed by hooks only. Do not write to or delete it directly." >&2
143
- exit 2
144
- fi
145
-
146
- # Extract command and check for write patterns targeting agent directories.
147
- # Matches: >, >>, cat.*>, tee, cp, mv followed by agents/{non-admin}/ path
148
- # containing a gated filename (SOUL.md, KNOWLEDGE.md, config.json).
149
- AGENT_INFO=$(echo "$INPUT" | python3 -c "
150
- import sys, json, re
151
- d = json.load(sys.stdin)
152
- cmd = d.get('tool_input', {}).get('command', '')
153
- # Match write patterns followed by a path containing agents/{slug}/{file}
154
- # where file is one of the gated files
155
- m = re.search(r'agents/([^/\s]+)/(SOUL\.md|KNOWLEDGE\.md|config\.json)', cmd)
156
- if not m:
157
- print('')
158
- sys.exit(0)
159
- slug = m.group(1)
160
- filename = m.group(2)
161
- # Check the command contains a write pattern before the path
162
- write_patterns = r'(>|>>|cat\s.*>|tee\s|cp\s|mv\s)'
163
- if re.search(write_patterns, cmd):
164
- print(slug + ' ' + filename)
165
- else:
166
- print('')
167
- " 2>>"$GATE_LOG" || echo "")
168
-
169
- [[ -z "$AGENT_INFO" ]] && exit 0
170
-
171
- AGENT_SLUG="${AGENT_INFO%% *}"
172
- AGENT_FILE="${AGENT_INFO##* }"
173
- ;;
174
-
175
- *)
176
- exit 0
177
- ;;
178
- esac
179
-
180
- # Admin agent is exempt — never gated
181
- [[ "$AGENT_SLUG" == "admin" ]] && exit 0
182
-
183
- # Determine which gate flag to check
184
- case "$AGENT_FILE" in
185
- SOUL.md) GATE_FLAG="soul" ;;
186
- KNOWLEDGE.md) GATE_FLAG="knowledge" ;;
187
- config.json) GATE_FLAG="config" ;;
188
- *) exit 0 ;; # Other agent files are not gated
189
- esac
190
-
191
- # --- This is a gated agent file. State file must exist. ---
192
-
193
- if [[ ! -f "$STATE_FILE" ]]; then
194
- echo "Agent creation gate: invoke the public-agent-manager skill before writing agent files. The skill workflow ensures the user reviews and approves each file." >&2
195
- exit 2
196
- fi
197
-
198
- # --- State file exists. Validate before trusting. ---
199
- #
200
- # Two-stage validation:
201
- # 1. Staleness — reject files older than 6 hours or missing 'started'
202
- # 2. Structure — require exactly {started, gates: {soul, knowledge, config}}
203
- #
204
- # Python parse errors (can't read JSON at all) → fail open (exit 0).
205
- # Structural validation failures (wrong shape) → fail closed (exit 2) + delete.
206
- # These are distinct: infra issues don't block work; forged state does.
207
-
208
- GATE_RESULT=$(python3 -c "
209
- import json, os, sys
210
- from datetime import datetime, timezone
211
-
212
- state_file = '$STATE_FILE'
213
- durable_file = '${DURABLE_STATE:-}'
214
- gate_flag = '$GATE_FLAG'
215
-
216
- def delete_state():
217
- for f in [state_file, durable_file]:
218
- if f:
219
- try:
220
- os.remove(f)
221
- except OSError:
222
- pass
223
-
224
- try:
225
- with open(state_file) as f:
226
- state = json.load(f)
227
- except Exception:
228
- # Cannot parse JSON — fail open (infra issue, not forged state)
229
- print('allow')
230
- sys.exit(0)
231
-
232
- # Non-dict JSON (e.g. array, string, number) is structurally invalid
233
- if not isinstance(state, dict):
234
- delete_state()
235
- print('reject_malformed')
236
- sys.exit(0)
237
-
238
- # --- Staleness check ---
239
- started = state.get('started')
240
- if not isinstance(started, str) or not started:
241
- delete_state()
242
- print('reject_invalid')
243
- sys.exit(0)
244
-
245
- try:
246
- started_dt = datetime.fromisoformat(started.replace('Z', '+00:00'))
247
- age_hours = (datetime.now(timezone.utc) - started_dt).total_seconds() / 3600
248
- except (ValueError, TypeError):
249
- delete_state()
250
- print('reject_invalid')
251
- sys.exit(0)
252
-
253
- if age_hours > 6 or age_hours < 0:
254
- delete_state()
255
- print('reject_stale')
256
- sys.exit(0)
257
-
258
- # --- Structure validation ---
259
- gates = state.get('gates')
260
- if not isinstance(gates, dict):
261
- delete_state()
262
- print('reject_malformed')
263
- sys.exit(0)
264
-
265
- expected_keys = {'soul', 'knowledge', 'config'}
266
- if set(gates.keys()) != expected_keys:
267
- delete_state()
268
- print('reject_malformed')
269
- sys.exit(0)
270
-
271
- if not all(isinstance(v, bool) for v in gates.values()):
272
- delete_state()
273
- print('reject_malformed')
274
- sys.exit(0)
275
-
276
- # --- Per-gate check ---
277
- if gates.get(gate_flag, False):
278
- print('allow')
279
- else:
280
- print('reject_gate')
281
- " 2>>"$GATE_LOG" || echo "allow")
282
-
283
- case "$GATE_RESULT" in
284
- allow)
285
- exit 0
286
- ;;
287
- reject_invalid)
288
- echo "Agent creation gate: state file is invalid (missing or unparseable timestamp). Invoke the public-agent-manager skill to start a new agent creation workflow." >&2
289
- exit 2
290
- ;;
291
- reject_stale)
292
- echo "Agent creation gate: state file is stale (older than 6 hours). Invoke the public-agent-manager skill to start a new agent creation workflow." >&2
293
- exit 2
294
- ;;
295
- reject_malformed)
296
- echo "Agent creation gate: state file has invalid structure. Invoke the public-agent-manager skill to start a new agent creation workflow." >&2
297
- exit 2
298
- ;;
299
- reject_gate)
300
- # Gate not passed — block with descriptive error
301
- case "$GATE_FLAG" in
302
- soul)
303
- echo "Agent creation gate: present SOUL.md to the user via a document-editor component with the correct filePath, then wait for the user to approve. The gate advances when the user clicks Approve." >&2
304
- ;;
305
- knowledge)
306
- echo "Agent creation gate: present KNOWLEDGE.md to the user via a document-editor component with the correct filePath, then wait for the user to approve. The gate advances when the user clicks Approve." >&2
307
- ;;
308
- config)
309
- echo "Agent creation gate: present the agent configuration to the user via a form component, then wait for the user to submit. The gate advances when the user submits the form." >&2
310
- ;;
311
- esac
312
- exit 2
313
- ;;
314
- esac
315
-
316
- # Fallback — should not reach here, but fail open for safety
317
- exit 0
@@ -1,165 +0,0 @@
1
- #!/usr/bin/env bash
2
- # PostToolUse hook — agent creation state creation and completion cleanup.
3
- #
4
- # Two responsibilities:
5
- #
6
- # 1. STATE CREATION: When mcp__admin__plugin-read loads the public-agent-manager
7
- # skill, creates (or resets) the gate state file with all gates false. Always
8
- # overwrites any existing state — loading the skill is the intent to start a
9
- # fresh workflow. This is the only entry point that unlocks agent file writes.
10
- #
11
- # 2. STATE CLEANUP: After a successful Write/Edit to a gated agent file, checks
12
- # whether all gates are true AND all three gated files (SOUL.md, KNOWLEDGE.md,
13
- # config.json) exist on disk. If so, creation is complete — deletes both
14
- # volatile and durable state files immediately.
15
- #
16
- # Gate advancement (advancing gates from false to true) is handled by
17
- # agent-creation-approval.sh (UserPromptSubmit), which fires when the user
18
- # approves a component. Gate enforcement (blocking writes until gates pass)
19
- # is handled by agent-creation-gate.sh (PreToolUse).
20
- #
21
- # Exit 0 always — PostToolUse hooks must never block tool execution.
22
- # Env: AGENT_CREATE_STATE_FILE (override volatile path, for testing)
23
- # ACCOUNT_DIR (account directory for durable state)
24
- # GATE_LOG_FILE (override log path, for testing)
25
-
26
- if [ -n "$GATE_LOG_FILE" ]; then
27
- GATE_LOG="$GATE_LOG_FILE"
28
- elif [ -n "$ACCOUNT_DIR" ]; then
29
- mkdir -p "$ACCOUNT_DIR/logs"
30
- GATE_LOG="$ACCOUNT_DIR/logs/maxy-gate.log"
31
- else
32
- # PostToolUse hooks must never block — exit 0 on misconfiguration
33
- exit 0
34
- fi
35
- STATE_FILE="${AGENT_CREATE_STATE_FILE:-/tmp/maxy-agent-create-state.json}"
36
- ACCOUNT_DIR="${ACCOUNT_DIR:-}"
37
- DURABLE_STATE="${ACCOUNT_DIR:+$ACCOUNT_DIR/.claude/agent-create-state.json}"
38
-
39
- # Read stdin (tool call JSON from Claude Code hook protocol)
40
- INPUT=$(cat)
41
-
42
- TOOL_NAME=$(echo "$INPUT" | python3 -c \
43
- "import sys,json; d=json.load(sys.stdin); print(d.get('tool_name',''))" \
44
- 2>>"$GATE_LOG" || echo "")
45
-
46
- # ── STATE CREATION: plugin-read for public-agent-manager ──
47
-
48
- if [[ "$TOOL_NAME" == "mcp__admin__plugin-read" ]]; then
49
- MATCHES_PAM=$(echo "$INPUT" | python3 -c "
50
- import sys, json
51
- d = json.load(sys.stdin)
52
- ti = d.get('tool_input', {})
53
- plugin = ti.get('pluginName', '')
54
- file = ti.get('file', '')
55
- # plugin-read uses pluginName + file, not path
56
- print('yes' if plugin == 'admin' and file.startswith('skills/public-agent-manager/') else 'no')
57
- " 2>>"$GATE_LOG" || echo "no")
58
-
59
- if [[ "$MATCHES_PAM" == "yes" ]]; then
60
- python3 -c "
61
- import json, datetime, os, sys
62
-
63
- state_file = '$STATE_FILE'
64
- durable_file = '${DURABLE_STATE:-}'
65
- log_file = '$GATE_LOG'
66
-
67
- state = {
68
- 'slug': '',
69
- 'started': datetime.datetime.utcnow().isoformat() + 'Z',
70
- 'gates': {'soul': False, 'knowledge': False, 'config': False}
71
- }
72
-
73
- try:
74
- with open(state_file, 'w') as f:
75
- json.dump(state, f)
76
- except OSError as e:
77
- with open(log_file, 'a') as lf:
78
- lf.write('agent-creation-gate: ERROR — failed to create volatile state file: %s\n' % e)
79
- sys.exit(0)
80
-
81
- if durable_file:
82
- try:
83
- durable_dir = os.path.dirname(durable_file)
84
- os.makedirs(durable_dir, exist_ok=True)
85
- with open(durable_file, 'w') as f:
86
- json.dump(state, f)
87
- except OSError as e:
88
- with open(log_file, 'a') as lf:
89
- lf.write('agent-creation-gate: WARNING — failed to create durable state file: %s\n' % e)
90
-
91
- with open(log_file, 'a') as lf:
92
- lf.write('agent-creation-gate: workflow started. All gates pending: soul, knowledge, config\n')
93
- " 2>>"$GATE_LOG" || true
94
- fi
95
- exit 0
96
- fi
97
-
98
- # ── STATE CLEANUP: delete state when all gated files exist on disk ──
99
- # Fast path: no state file → nothing to clean up
100
- [[ -f "$STATE_FILE" ]] || exit 0
101
-
102
- if [[ "$TOOL_NAME" == "Write" || "$TOOL_NAME" == "Edit" ]]; then
103
- export _HOOK_INPUT="$INPUT"
104
- export _STATE_FILE="$STATE_FILE"
105
- export _DURABLE_STATE="${DURABLE_STATE:-}"
106
- export _GATE_LOG="$GATE_LOG"
107
-
108
- python3 -c '
109
- import json, os, re, sys
110
-
111
- state_file = os.environ.get("_STATE_FILE", "/tmp/maxy-agent-create-state.json")
112
- durable_file = os.environ.get("_DURABLE_STATE", "")
113
- log_file = os.environ.get("_GATE_LOG", "/tmp/maxy-gate.log")
114
- input_raw = os.environ.get("_HOOK_INPUT", "")
115
-
116
- def log(msg):
117
- try:
118
- with open(log_file, "a") as lf:
119
- lf.write("agent-creation-post: %s\n" % msg)
120
- except OSError:
121
- pass
122
-
123
- # Parse tool input to get file_path
124
- try:
125
- tool_data = json.loads(input_raw)
126
- file_path = tool_data.get("tool_input", {}).get("file_path", "")
127
- except Exception:
128
- sys.exit(0)
129
-
130
- # Is this a write to a gated agent file?
131
- m = re.search(r"agents/([^/]+)/(SOUL\.md|KNOWLEDGE\.md|config\.json)$", file_path)
132
- if not m or m.group(1) == "admin":
133
- sys.exit(0)
134
-
135
- # Read state — are all gates true?
136
- try:
137
- with open(state_file) as f:
138
- state = json.load(f)
139
- except Exception:
140
- sys.exit(0)
141
-
142
- gates = state.get("gates", {})
143
- if not (isinstance(gates, dict) and all(v is True for v in gates.values())):
144
- sys.exit(0)
145
-
146
- # All gates true. Check if all three gated files exist on disk.
147
- agent_dir = os.path.dirname(file_path)
148
- gated_files = ["SOUL.md", "KNOWLEDGE.md", "config.json"]
149
- if not all(os.path.isfile(os.path.join(agent_dir, f)) for f in gated_files):
150
- sys.exit(0)
151
-
152
- # All files exist — creation is complete. Clean up state.
153
- for f in [state_file, durable_file]:
154
- if f:
155
- try:
156
- os.remove(f)
157
- except OSError:
158
- pass
159
-
160
- slug = state.get("slug", "")
161
- log("creation complete for \"%s\" — state files cleaned up" % slug)
162
- ' 2>>"$GATE_LOG" || true
163
- fi
164
-
165
- exit 0
@@ -1,104 +0,0 @@
1
- #!/usr/bin/env bash
2
- # SessionStart hook — agent-creation gate state recovery.
3
- #
4
- # If agent creation was in progress when the last session ended, the durable
5
- # state file in the account directory preserves the gate flags. This hook
6
- # copies it to the volatile /tmp/ location so PreToolUse picks up where it
7
- # left off, and injects context telling the agent which gates are pending.
8
- #
9
- # Without this, a session restart would silently drop the gate — the admin
10
- # agent could write SOUL.md/KNOWLEDGE.md/config.json without user approval.
11
- #
12
- # IDENTITY.md loading is handled by claude-agent.ts (system prompt assembly),
13
- # not by this hook.
14
- #
15
- # Usage: session-start.sh <account-dir> <agent-name>
16
- # Env: AGENT_CREATE_STATE_FILE (override volatile path, for testing)
17
-
18
- ACCOUNT_DIR="${1:-${ACCOUNT_DIR:-}}"
19
-
20
- if [ -z "$ACCOUNT_DIR" ]; then
21
- exit 0
22
- fi
23
-
24
- # --- Agent-creation gate state recovery ---
25
- DURABLE_STATE="$ACCOUNT_DIR/.claude/agent-create-state.json"
26
- VOLATILE_STATE="${AGENT_CREATE_STATE_FILE:-/tmp/maxy-agent-create-state.json}"
27
-
28
- if [ ! -f "$DURABLE_STATE" ]; then
29
- exit 0
30
- fi
31
-
32
- # Staleness + completion check — reject durable state that is stale (>6h, missing
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."
40
- DURABLE_VALID=$(python3 -c "
41
- import json, sys
42
- from datetime import datetime, timezone
43
-
44
- try:
45
- with open('$DURABLE_STATE') as f:
46
- state = json.load(f)
47
- started = state.get('started', '')
48
- if not isinstance(started, str) or not started:
49
- print('stale')
50
- sys.exit(0)
51
- started_dt = datetime.fromisoformat(started.replace('Z', '+00:00'))
52
- age_hours = (datetime.now(timezone.utc) - started_dt).total_seconds() / 3600
53
- if age_hours > 6 or age_hours < 0:
54
- print('stale')
55
- sys.exit(0)
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.
59
- gates = state.get('gates', {})
60
- if isinstance(gates, dict) and set(gates.keys()) == {'soul', 'knowledge', 'config'} and all(v is True for v in gates.values()):
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')
72
- else:
73
- print('valid')
74
- except Exception:
75
- print('stale')
76
- " 2>/dev/null || echo "stale")
77
-
78
- if [[ "$DURABLE_VALID" != "valid" ]]; then
79
- # Stale, invalid, or complete — clean up both files, do not recover
80
- rm -f "$DURABLE_STATE" "$VOLATILE_STATE" 2>/dev/null || true
81
- exit 0
82
- fi
83
-
84
- # Copy durable state to volatile location for PreToolUse
85
- cp "$DURABLE_STATE" "$VOLATILE_STATE" 2>/dev/null || exit 0
86
-
87
- # Inject context so the agent knows creation is resuming
88
- python3 -c "
89
- import json
90
-
91
- try:
92
- with open('$DURABLE_STATE') as f:
93
- state = json.load(f)
94
- slug = state.get('slug', '')
95
- gates = state.get('gates', {})
96
- pending = [k for k, v in gates.items() if not v]
97
- pending_str = ', '.join(pending) if pending else 'none'
98
- msg = f\"Agent creation for '{slug}' is in progress (resumed from prior session). Pending gates: {pending_str}. Render the remaining UI components (document-editor for SOUL.md/KNOWLEDGE.md, form with liveMemory for config) before writing agent files.\"
99
- print(json.dumps({'additionalContext': msg}))
100
- except:
101
- pass
102
- " 2>/dev/null || true
103
-
104
- exit 0