@rubytech/create-maxy-code 0.1.434 → 0.1.436
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/platform/docs/superpowers/plans/2026-07-11-task-1557-operator-on-behalf-preference-attribution.md +542 -0
- package/payload/platform/docs/superpowers/specs/2026-07-11-task-1557-operator-on-behalf-preference-attribution-design.md +90 -0
- package/payload/platform/lib/embed-client/dist/index.d.ts +2 -2
- package/payload/platform/lib/embed-client/dist/index.d.ts.map +1 -1
- package/payload/platform/lib/embed-client/dist/index.js +114 -37
- package/payload/platform/lib/embed-client/dist/index.js.map +1 -1
- package/payload/platform/lib/embed-client/src/index.ts +120 -37
- package/payload/platform/plugins/admin/PLUGIN.md +1 -0
- package/payload/platform/plugins/admin/hooks/__tests__/askuserquestion-channel-carrier-gate.test.sh +175 -0
- package/payload/platform/plugins/admin/hooks/askuserquestion-channel-carrier-gate.sh +137 -0
- package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +2 -2
- package/payload/platform/plugins/docs/references/internals.md +1 -1
- package/payload/platform/plugins/memory/PLUGIN.md +2 -0
- package/payload/platform/plugins/memory/mcp/dist/index.js +42 -22
- package/payload/platform/plugins/memory/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/embed-progress-reporter.d.ts +22 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/embed-progress-reporter.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/embed-progress-reporter.js +43 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/embed-progress-reporter.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/profile-identity.d.ts +34 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/profile-identity.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/profile-identity.js +22 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/profile-identity.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/resolve-admin-home-account.d.ts +13 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/resolve-admin-home-account.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/resolve-admin-home-account.js +26 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/resolve-admin-home-account.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/embed-progress-reporter.test.d.ts +2 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/embed-progress-reporter.test.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/embed-progress-reporter.test.js +52 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/embed-progress-reporter.test.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/embed-progress.test.d.ts +2 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/embed-progress.test.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/embed-progress.test.js +56 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/embed-progress.test.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/embed-throughput.test.js +186 -19
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/embed-throughput.test.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/embed-timeout.test.js +11 -9
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/embed-timeout.test.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/embeddings-cap.test.js +18 -12
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/embeddings-cap.test.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/profile-identity.test.d.ts +2 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/profile-identity.test.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/profile-identity.test.js +72 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/profile-identity.test.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/profile-update-own-preference-selfheal.test.d.ts +2 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/profile-update-own-preference-selfheal.test.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/profile-update-own-preference-selfheal.test.js +113 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/profile-update-own-preference-selfheal.test.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/profile-update-personfields-bootstrap.test.js +2 -2
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/profile-update-personfields-bootstrap.test.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/profile-update-reconcile.test.js +6 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/profile-update-reconcile.test.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/resolve-admin-home-account.test.d.ts +2 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/resolve-admin-home-account.test.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/resolve-admin-home-account.test.js +38 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/resolve-admin-home-account.test.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest.d.ts +9 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest.js +2 -2
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/profile-update.js +52 -7
- package/payload/platform/plugins/memory/mcp/dist/tools/profile-update.js.map +1 -1
- package/payload/platform/plugins/memory/skills/conversational-memory/SKILL.md +8 -0
- package/payload/platform/scripts/lib/provision-account-dir.sh +2 -1
- package/payload/platform/services/claude-session-manager/dist/index.js +20 -0
- package/payload/platform/services/claude-session-manager/dist/index.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/interactive-wedge.d.ts +42 -0
- package/payload/platform/services/claude-session-manager/dist/interactive-wedge.d.ts.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/interactive-wedge.js +149 -0
- package/payload/platform/services/claude-session-manager/dist/interactive-wedge.js.map +1 -0
- package/payload/server/{chunk-UUG6ZSO5.js → chunk-LMLVOZ6G.js} +35 -2
- package/payload/server/maxy-edge.js +1 -1
- package/payload/server/public/assets/{chat-C56bmC6C.js → chat-DUc7bt5W.js} +1 -1
- package/payload/server/public/assets/{operator-B4wN2c53.js → operator-B3Mi3TW0.js} +1 -1
- package/payload/server/public/assets/page-DbUaFfk6.js +32 -0
- package/payload/server/public/assets/{public-DmBKnBkr.js → public-CYhDdDJd.js} +1 -1
- package/payload/server/public/chat.html +2 -2
- package/payload/server/public/operator.html +2 -2
- package/payload/server/public/public.html +2 -2
- package/payload/server/server.js +8 -7
- package/payload/server/public/assets/page-BIfOKi9I.js +0 -32
package/payload/platform/plugins/admin/hooks/__tests__/askuserquestion-channel-carrier-gate.test.sh
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Regression test for askuserquestion-channel-carrier-gate.sh.
|
|
3
|
+
#
|
|
4
|
+
# The hook blocks AskUserQuestion when the session has a native channel
|
|
5
|
+
# attached — signalled by a channel MCP config file for this session id in
|
|
6
|
+
# tmpdir. A channel carries no elicitation card, so the turn would wedge; the
|
|
7
|
+
# hook steers the agent to ask in prose via its channel reply tool instead.
|
|
8
|
+
#
|
|
9
|
+
# Covers (per .tasks/1552):
|
|
10
|
+
# 1. Channel attached (webchat config file present) + AskUserQuestion → BLOCK channel-attached
|
|
11
|
+
# 2. No channel file + AskUserQuestion → ALLOW no-channel
|
|
12
|
+
# 3. Non-AskUserQuestion tool → exit 0 silent (no [ask-channel] line)
|
|
13
|
+
# 4. PostToolUse event on AskUserQuestion → exit 0 silent
|
|
14
|
+
# 5. Missing session_id → ALLOW fail-open-no-session
|
|
15
|
+
# 6. No stdin envelope → ALLOW fail-open-no-envelope
|
|
16
|
+
# 7. Each of the 4 channel filename shapes present → BLOCK channel-attached
|
|
17
|
+
# 8. Session id needing sanitization, sanitized file present → BLOCK channel-attached
|
|
18
|
+
#
|
|
19
|
+
# All decisive cases assert exit code AND the decision-log signature on stderr.
|
|
20
|
+
|
|
21
|
+
set -u
|
|
22
|
+
|
|
23
|
+
HOOK="$(cd "$(dirname "$0")/.." && pwd)/askuserquestion-channel-carrier-gate.sh"
|
|
24
|
+
if [ ! -x "$HOOK" ]; then
|
|
25
|
+
echo "FAIL: $HOOK not executable" >&2
|
|
26
|
+
exit 1
|
|
27
|
+
fi
|
|
28
|
+
|
|
29
|
+
PASS=0
|
|
30
|
+
FAIL=0
|
|
31
|
+
|
|
32
|
+
# Run one case. Args:
|
|
33
|
+
# $1 = name
|
|
34
|
+
# $2 = stdin payload ("" for no-envelope: closed stdin)
|
|
35
|
+
# $3 = tmpdir the hook should scan (ASK_CHANNEL_TMPDIR override), "" for default
|
|
36
|
+
# $4 = expected exit code
|
|
37
|
+
# $5 = expected log signature regex (matched against stderr; "" = assert stderr is empty)
|
|
38
|
+
run_case() {
|
|
39
|
+
local name="$1" stdin="$2" scan_dir="$3" expected_exit="$4" expected_log_re="$5"
|
|
40
|
+
local stderr_file actual_exit
|
|
41
|
+
stderr_file="$(mktemp)"
|
|
42
|
+
if [ -z "$stdin" ]; then
|
|
43
|
+
# No envelope: close stdin so the hook's `[ -t 0 ]` / empty-read path fires.
|
|
44
|
+
ASK_CHANNEL_TMPDIR="$scan_dir" bash "$HOOK" >/dev/null 2>"$stderr_file" </dev/null
|
|
45
|
+
else
|
|
46
|
+
printf '%s' "$stdin" | ASK_CHANNEL_TMPDIR="$scan_dir" bash "$HOOK" >/dev/null 2>"$stderr_file"
|
|
47
|
+
fi
|
|
48
|
+
actual_exit=$?
|
|
49
|
+
local stderr_content
|
|
50
|
+
stderr_content=$(cat "$stderr_file")
|
|
51
|
+
rm -f "$stderr_file"
|
|
52
|
+
|
|
53
|
+
local exit_ok=0 log_ok=0
|
|
54
|
+
if [ "$actual_exit" -eq "$expected_exit" ]; then exit_ok=1; fi
|
|
55
|
+
if [ -z "$expected_log_re" ]; then
|
|
56
|
+
# Empty regex means: assert the hook emitted NO [ask-channel] line.
|
|
57
|
+
if ! printf '%s' "$stderr_content" | grep -q '\[ask-channel\]'; then log_ok=1; fi
|
|
58
|
+
else
|
|
59
|
+
if printf '%s' "$stderr_content" | grep -Eq "$expected_log_re"; then log_ok=1; fi
|
|
60
|
+
fi
|
|
61
|
+
|
|
62
|
+
if [ $exit_ok -eq 1 ] && [ $log_ok -eq 1 ]; then
|
|
63
|
+
echo "PASS: $name (exit=$actual_exit)"
|
|
64
|
+
PASS=$((PASS + 1))
|
|
65
|
+
else
|
|
66
|
+
echo "FAIL: $name" >&2
|
|
67
|
+
[ $exit_ok -eq 0 ] && echo " expected exit=$expected_exit, got=$actual_exit" >&2
|
|
68
|
+
[ $log_ok -eq 0 ] && echo " expected log regex: ${expected_log_re:-<none>}" >&2 && \
|
|
69
|
+
echo " actual stderr: $stderr_content" >&2
|
|
70
|
+
FAIL=$((FAIL + 1))
|
|
71
|
+
fi
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
SID="1a085718-1111-2222-3333-444455556666"
|
|
75
|
+
SID8="1a085718"
|
|
76
|
+
|
|
77
|
+
# Stdin payloads.
|
|
78
|
+
PRE_ASK="{\"hook_event_name\":\"PreToolUse\",\"tool_name\":\"AskUserQuestion\",\"session_id\":\"$SID\",\"tool_input\":{\"questions\":[]}}"
|
|
79
|
+
PRE_OTHER="{\"hook_event_name\":\"PreToolUse\",\"tool_name\":\"Bash\",\"session_id\":\"$SID\",\"tool_input\":{\"command\":\"ls\"}}"
|
|
80
|
+
POST_ASK="{\"hook_event_name\":\"PostToolUse\",\"tool_name\":\"AskUserQuestion\",\"session_id\":\"$SID\",\"tool_input\":{\"questions\":[]}}"
|
|
81
|
+
PRE_ASK_NO_SID='{"hook_event_name":"PreToolUse","tool_name":"AskUserQuestion","tool_input":{"questions":[]}}'
|
|
82
|
+
|
|
83
|
+
# ---------- Cases ----------
|
|
84
|
+
|
|
85
|
+
# 1. Channel attached (webchat config present) → BLOCK.
|
|
86
|
+
DIR_WEBCHAT=$(mktemp -d)
|
|
87
|
+
: > "$DIR_WEBCHAT/maxy-webchat-channel-$SID.json"
|
|
88
|
+
run_case "Webchat channel attached → BLOCK channel-attached" \
|
|
89
|
+
"$PRE_ASK" "$DIR_WEBCHAT" 2 \
|
|
90
|
+
"\[ask-channel\] decision=block sessionId=$SID8 channel=attached reason=channel-attached"
|
|
91
|
+
rm -rf "$DIR_WEBCHAT"
|
|
92
|
+
|
|
93
|
+
# 2. No channel file → ALLOW no-channel.
|
|
94
|
+
DIR_EMPTY=$(mktemp -d)
|
|
95
|
+
run_case "No channel file → ALLOW no-channel" \
|
|
96
|
+
"$PRE_ASK" "$DIR_EMPTY" 0 \
|
|
97
|
+
"\[ask-channel\] decision=allow sessionId=$SID8 channel=none reason=no-channel"
|
|
98
|
+
rm -rf "$DIR_EMPTY"
|
|
99
|
+
|
|
100
|
+
# 3. Non-AskUserQuestion tool → silent exit 0.
|
|
101
|
+
DIR_WITH_FILE=$(mktemp -d)
|
|
102
|
+
: > "$DIR_WITH_FILE/maxy-webchat-channel-$SID.json"
|
|
103
|
+
run_case "Non-AskUserQuestion tool → exit 0 silent" \
|
|
104
|
+
"$PRE_OTHER" "$DIR_WITH_FILE" 0 ""
|
|
105
|
+
rm -rf "$DIR_WITH_FILE"
|
|
106
|
+
|
|
107
|
+
# 4. PostToolUse event → silent exit 0 (even with channel file present).
|
|
108
|
+
DIR_POST=$(mktemp -d)
|
|
109
|
+
: > "$DIR_POST/maxy-webchat-channel-$SID.json"
|
|
110
|
+
run_case "PostToolUse on AskUserQuestion → exit 0 silent" \
|
|
111
|
+
"$POST_ASK" "$DIR_POST" 0 ""
|
|
112
|
+
rm -rf "$DIR_POST"
|
|
113
|
+
|
|
114
|
+
# 5. Missing session_id → ALLOW fail-open-no-session.
|
|
115
|
+
DIR_NOSID=$(mktemp -d)
|
|
116
|
+
run_case "Missing session_id → ALLOW fail-open-no-session" \
|
|
117
|
+
"$PRE_ASK_NO_SID" "$DIR_NOSID" 0 \
|
|
118
|
+
'\[ask-channel\] decision=allow sessionId=- channel=none reason=fail-open-no-session'
|
|
119
|
+
rm -rf "$DIR_NOSID"
|
|
120
|
+
|
|
121
|
+
# 6. No stdin envelope → ALLOW fail-open-no-envelope.
|
|
122
|
+
run_case "No stdin envelope → ALLOW fail-open-no-envelope" \
|
|
123
|
+
"" "" 0 \
|
|
124
|
+
'\[ask-channel\] decision=allow sessionId=- channel=none reason=fail-open-no-envelope'
|
|
125
|
+
|
|
126
|
+
# 7. Each of the 4 channel filename shapes present → BLOCK.
|
|
127
|
+
for kind in channel webchat-channel wa-channel telegram-channel; do
|
|
128
|
+
DIR_KIND=$(mktemp -d)
|
|
129
|
+
: > "$DIR_KIND/maxy-$kind-$SID.json"
|
|
130
|
+
run_case "Channel file shape maxy-$kind-<sid>.json → BLOCK channel-attached" \
|
|
131
|
+
"$PRE_ASK" "$DIR_KIND" 2 \
|
|
132
|
+
"\[ask-channel\] decision=block sessionId=$SID8 channel=attached reason=channel-attached"
|
|
133
|
+
rm -rf "$DIR_KIND"
|
|
134
|
+
done
|
|
135
|
+
|
|
136
|
+
# 8. Session id needing sanitization: raw id has chars outside [A-Za-z0-9_-];
|
|
137
|
+
# the writer sanitizes to '_', so the on-disk file uses the sanitized id.
|
|
138
|
+
RAW_SID="acc/one:sess" # '/' and ':' are sanitized to '_'
|
|
139
|
+
SAN_SID="acc_one_sess"
|
|
140
|
+
DIR_SAN=$(mktemp -d)
|
|
141
|
+
: > "$DIR_SAN/maxy-webchat-channel-$SAN_SID.json"
|
|
142
|
+
PRE_ASK_RAW="{\"hook_event_name\":\"PreToolUse\",\"tool_name\":\"AskUserQuestion\",\"session_id\":\"$RAW_SID\",\"tool_input\":{\"questions\":[]}}"
|
|
143
|
+
run_case "Sanitized session id resolves the file → BLOCK channel-attached" \
|
|
144
|
+
"$PRE_ASK_RAW" "$DIR_SAN" 2 \
|
|
145
|
+
'\[ask-channel\] decision=block sessionId=acc/one: channel=attached reason=channel-attached'
|
|
146
|
+
rm -rf "$DIR_SAN"
|
|
147
|
+
|
|
148
|
+
# 9. Real tmpdir resolution fallback: no ASK_CHANNEL_TMPDIR override, TMPDIR
|
|
149
|
+
# unset, TMP set → the hook must resolve TMP (mirroring Node's os.tmpdir())
|
|
150
|
+
# and find the config file there. Proves the production resolution order, not
|
|
151
|
+
# just the test override.
|
|
152
|
+
DIR_TMP=$(mktemp -d)
|
|
153
|
+
: > "$DIR_TMP/maxy-webchat-channel-$SID.json"
|
|
154
|
+
tmp_stderr="$(mktemp)"
|
|
155
|
+
printf '%s' "$PRE_ASK" | env -u TMPDIR -u ASK_CHANNEL_TMPDIR -u TEMP TMP="$DIR_TMP" \
|
|
156
|
+
bash "$HOOK" >/dev/null 2>"$tmp_stderr"
|
|
157
|
+
tmp_exit=$?
|
|
158
|
+
if [ "$tmp_exit" -eq 2 ] && grep -Eq "\[ask-channel\] decision=block sessionId=$SID8 channel=attached reason=channel-attached" "$tmp_stderr"; then
|
|
159
|
+
echo "PASS: TMP-fallback resolution finds the config file → BLOCK (exit=$tmp_exit)"
|
|
160
|
+
PASS=$((PASS + 1))
|
|
161
|
+
else
|
|
162
|
+
echo "FAIL: TMP-fallback resolution" >&2
|
|
163
|
+
echo " expected exit=2 got=$tmp_exit; stderr: $(cat "$tmp_stderr")" >&2
|
|
164
|
+
FAIL=$((FAIL + 1))
|
|
165
|
+
fi
|
|
166
|
+
rm -f "$tmp_stderr"
|
|
167
|
+
rm -rf "$DIR_TMP"
|
|
168
|
+
|
|
169
|
+
echo
|
|
170
|
+
echo "──────── askuserquestion-channel-carrier-gate test summary ────────"
|
|
171
|
+
echo "PASS: $PASS"
|
|
172
|
+
echo "FAIL: $FAIL"
|
|
173
|
+
|
|
174
|
+
[ "$FAIL" -gt 0 ] && exit 1
|
|
175
|
+
exit 0
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# AskUserQuestion native-channel carrier gate.
|
|
3
|
+
#
|
|
4
|
+
# Blocks `AskUserQuestion` when the session has a native channel attached. The
|
|
5
|
+
# Claude Code channel protocol carries exactly two interactive methods
|
|
6
|
+
# (permission_request / permission); there is NO carrier for the AskUserQuestion
|
|
7
|
+
# elicitation. On a native-channel (rc-spawn) webchat/WhatsApp/Telegram session
|
|
8
|
+
# the harness blocks the turn awaiting a selection that can never arrive: the
|
|
9
|
+
# reader renders only delivered replies, never an elicitation card, so the turn
|
|
10
|
+
# wedges — the operator sees the last delivered reply and a live-looking but
|
|
11
|
+
# dead composer (see .tasks/1552, live SiteDesk incident session 1a085718
|
|
12
|
+
# 2026-07-11). This hook removes that wedge deterministically by steering the
|
|
13
|
+
# agent to ask the same question as prose via its channel reply tool, which
|
|
14
|
+
# rides the ordinary delivered-reply path that works on every surface.
|
|
15
|
+
#
|
|
16
|
+
# This is a SEPARATE concern from askuserquestion-investigate-gate.sh (which
|
|
17
|
+
# blocks a fabricated menu raised before any investigation). Both are registered
|
|
18
|
+
# as commands on the same PreToolUse AskUserQuestion matcher; either exit 2
|
|
19
|
+
# blocks. This hook guards the "surface can't render this" condition; the other
|
|
20
|
+
# guards the "no evidence yet" condition.
|
|
21
|
+
#
|
|
22
|
+
# Signal: the rc-spawn writes the channel MCP registration as a config file in
|
|
23
|
+
# os.tmpdir() keyed by session id (channel-mcp.ts / webchat-channel-mcp.ts /
|
|
24
|
+
# wa-channel-mcp.ts / telegram-channel-mcp.ts). Its presence at tool-call time
|
|
25
|
+
# is the authoritative "channel attached to this session" marker: a Pi channel
|
|
26
|
+
# process sees the file (block); a claude.ai/code process on another host never
|
|
27
|
+
# sees the Pi tmpfile (fail-open, allow — that surface can answer the card).
|
|
28
|
+
#
|
|
29
|
+
# Contract:
|
|
30
|
+
# Input: PreToolUse stdin JSON envelope from Claude Code
|
|
31
|
+
# Output: exit 0 (allow) or exit 2 (block) per hook protocol
|
|
32
|
+
# Logs: one stderr line per decisive call, format
|
|
33
|
+
# [ask-channel] decision=<allow|block> sessionId=<id8>
|
|
34
|
+
# channel=<attached|none>
|
|
35
|
+
# reason=<channel-attached|no-channel|fail-open-no-envelope|fail-open-no-session>
|
|
36
|
+
#
|
|
37
|
+
# Fail-open philosophy: any missing/blank marker (no channel file, no session
|
|
38
|
+
# id, no envelope) nudges the agent through, never bricks the UI. Block is
|
|
39
|
+
# reserved for the deterministic case "AskUserQuestion fired while a channel
|
|
40
|
+
# config file for this session exists in tmpdir".
|
|
41
|
+
|
|
42
|
+
set -uo pipefail
|
|
43
|
+
|
|
44
|
+
# ----- Read stdin, identify event ------------------------------------------
|
|
45
|
+
if [ -t 0 ]; then
|
|
46
|
+
# No envelope — cannot enforce. Fail open per nudge-not-brick rule.
|
|
47
|
+
echo "[ask-channel] decision=allow sessionId=- channel=none reason=fail-open-no-envelope" >&2
|
|
48
|
+
exit 0
|
|
49
|
+
fi
|
|
50
|
+
INPUT=$(cat)
|
|
51
|
+
|
|
52
|
+
if [ -z "$INPUT" ]; then
|
|
53
|
+
echo "[ask-channel] decision=allow sessionId=- channel=none reason=fail-open-no-envelope" >&2
|
|
54
|
+
exit 0
|
|
55
|
+
fi
|
|
56
|
+
|
|
57
|
+
HOOK_EVENT=$(printf '%s' "$INPUT" | python3 -c '
|
|
58
|
+
import sys, json
|
|
59
|
+
try:
|
|
60
|
+
d = json.load(sys.stdin)
|
|
61
|
+
print(d.get("hook_event_name", "") or "")
|
|
62
|
+
except Exception:
|
|
63
|
+
print("")
|
|
64
|
+
' 2>/dev/null)
|
|
65
|
+
|
|
66
|
+
TOOL_NAME=$(printf '%s' "$INPUT" | python3 -c '
|
|
67
|
+
import sys, json
|
|
68
|
+
try:
|
|
69
|
+
d = json.load(sys.stdin)
|
|
70
|
+
print(d.get("tool_name", "") or "")
|
|
71
|
+
except Exception:
|
|
72
|
+
print("")
|
|
73
|
+
' 2>/dev/null)
|
|
74
|
+
|
|
75
|
+
# Only applies to PreToolUse on AskUserQuestion. Anything else: silent exit.
|
|
76
|
+
if [ "$HOOK_EVENT" != "PreToolUse" ] || [ "$TOOL_NAME" != "AskUserQuestion" ]; then
|
|
77
|
+
exit 0
|
|
78
|
+
fi
|
|
79
|
+
|
|
80
|
+
SESSION_ID=$(printf '%s' "$INPUT" | python3 -c '
|
|
81
|
+
import sys, json
|
|
82
|
+
try:
|
|
83
|
+
d = json.load(sys.stdin)
|
|
84
|
+
print(d.get("session_id", "") or "")
|
|
85
|
+
except Exception:
|
|
86
|
+
print("")
|
|
87
|
+
' 2>/dev/null)
|
|
88
|
+
SESSION_ID8="${SESSION_ID:0:8}"
|
|
89
|
+
if [ -z "$SESSION_ID8" ]; then SESSION_ID8="-"; fi
|
|
90
|
+
|
|
91
|
+
if [ -z "$SESSION_ID" ]; then
|
|
92
|
+
echo "[ask-channel] decision=allow sessionId=- channel=none reason=fail-open-no-session" >&2
|
|
93
|
+
exit 0
|
|
94
|
+
fi
|
|
95
|
+
|
|
96
|
+
# ----- Resolve the tmpdir the writers used ---------------------------------
|
|
97
|
+
# The channel MCP config path is join(os.tmpdir(), ...). Node's os.tmpdir() on
|
|
98
|
+
# POSIX is `process.env.TMPDIR` with a trailing separator stripped, else '/tmp'.
|
|
99
|
+
# Reproduce that exactly so the lookup lands on the same directory the rc-spawn
|
|
100
|
+
# wrote to. `ASK_CHANNEL_TMPDIR` overrides it for tests only — never set in
|
|
101
|
+
# production; the SDK-spawned child inherits the manager's TMPDIR.
|
|
102
|
+
if [ -n "${ASK_CHANNEL_TMPDIR:-}" ]; then
|
|
103
|
+
TMP_DIR="$ASK_CHANNEL_TMPDIR"
|
|
104
|
+
else
|
|
105
|
+
# Mirror Node's os.tmpdir() (libuv uv_os_tmpdir): TMPDIR, then TMP, then TEMP,
|
|
106
|
+
# then /tmp; a trailing slash is stripped. The lookup must land on the exact
|
|
107
|
+
# directory the rc-spawn writer used, so this resolution order matches the
|
|
108
|
+
# writer's byte for byte. On the Pi (systemd user service) and macOS (launchd)
|
|
109
|
+
# only TMPDIR is ever set, but honouring the full order removes a latent
|
|
110
|
+
# false-allow if TMP/TEMP alone are present.
|
|
111
|
+
TMP_DIR="${TMPDIR:-${TMP:-${TEMP:-/tmp}}}"
|
|
112
|
+
TMP_DIR="${TMP_DIR%/}"
|
|
113
|
+
fi
|
|
114
|
+
|
|
115
|
+
# Sanitize the session id the same way the writers do:
|
|
116
|
+
# `sessionId.replace(/[^A-Za-z0-9_-]/g, '_')`.
|
|
117
|
+
SID_SAN=$(printf '%s' "$SESSION_ID" | sed 's/[^A-Za-z0-9_-]/_/g')
|
|
118
|
+
|
|
119
|
+
# ----- Channel attached? ---------------------------------------------------
|
|
120
|
+
# Any of the four channel MCP config filename shapes present in tmpdir for this
|
|
121
|
+
# session means a channel is bound (unified, webchat, whatsapp, or telegram).
|
|
122
|
+
CHANNEL_ATTACHED=0
|
|
123
|
+
for kind in channel webchat-channel wa-channel telegram-channel; do
|
|
124
|
+
if [ -e "$TMP_DIR/maxy-${kind}-${SID_SAN}.json" ]; then
|
|
125
|
+
CHANNEL_ATTACHED=1
|
|
126
|
+
break
|
|
127
|
+
fi
|
|
128
|
+
done
|
|
129
|
+
|
|
130
|
+
if [ "$CHANNEL_ATTACHED" -eq 1 ]; then
|
|
131
|
+
echo "[ask-channel] decision=block sessionId=${SESSION_ID8} channel=attached reason=channel-attached" >&2
|
|
132
|
+
echo "Blocked: this is a native-channel session and AskUserQuestion cannot be delivered over a channel (the channel protocol carries no elicitation card, so the turn would wedge). Ask the same question as prose in your channel reply tool and wait for the operator's next message." >&2
|
|
133
|
+
exit 2
|
|
134
|
+
fi
|
|
135
|
+
|
|
136
|
+
echo "[ask-channel] decision=allow sessionId=${SESSION_ID8} channel=none reason=no-channel" >&2
|
|
137
|
+
exit 0
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: platform-architecture
|
|
3
3
|
description: Use when grounding any documented-surface claim about what Maxy ships — plugins, skills, specialists, install/deploy flows, internals. This is the install catalogue, not evidence of what is enabled on the current account. For install state on this account, call `capabilities-here`; for documented surface, cite the `Source:` URL inline.
|
|
4
|
-
content-hash: sha256:
|
|
4
|
+
content-hash: sha256:bd08bc341313562b1dd2139010b1120b23d7aab130e1d32c3e800678ad559f7c
|
|
5
5
|
brand: maxy-code
|
|
6
6
|
product-name: Maxy
|
|
7
7
|
---
|
|
@@ -3566,7 +3566,7 @@ If Ollama is unavailable at write time, the node is stored without an embedding
|
|
|
3566
3566
|
|
|
3567
3567
|
The `memory-reindex` tool backfills missing embeddings by iterating nodes where `embedding IS NULL`. It uses the bounded representation (the `summary` for document/archive labels, never the raw body), embeds in batches with a per-node fallback on batch error, and **skips and continues** past a failing node — keeping skipped nodes out of the re-fetch via an exclude set — instead of aborting the whole run. `embed()`/`embedBatch()` cap oversized input (`EMBED_INPUT_MAX_CHARS`) before the Ollama POST, so a large body embeds truncated rather than throwing. Per-node `[reindex] op=embedded|skip-error` lines and a terminal `[reindex] op=done processed=N skipped=M`.
|
|
3568
3568
|
|
|
3569
|
-
`embedBatch()` also paces large batches. A document's summary plus every section body in one `/api/embed` POST pins ollama's `llama-server` across all cores for the whole batch with no idle gaps; on a passively-cooled Pi the SoC climbs past its soft-temp limit and the board thermally hard-resets. `embedBatch` splits
|
|
3569
|
+
`embedBatch()` also paces large batches. A document's summary plus every section body in one `/api/embed` POST pins ollama's `llama-server` across all cores for the whole batch with no idle gaps; on a passively-cooled Pi the SoC climbs past its soft-temp limit and the board thermally hard-resets. `embedBatch` splits a multi-input array into sub-batches, waiting `EMBED_SUBBATCH_COOLDOWN_MS` (default 1000) between them so the SoC gets cooling gaps; vectors are concatenated in input order. Each POST is sized from the device's *measured* throughput, not a fixed constant: the first POST of a multi-input batch is a single-input probe (the only size provably safe without a measurement), and every POST's observed per-character wall-time folds into a running maximum. Subsequent POSTs are packed with as many inputs as fit a character budget `floor(EMBED_REQUEST_TIMEOUT_MS * 0.8 / observedMsPerChar)`, capped by the `EMBED_SUBBATCH_SIZE` count ceiling. Sizing by characters rather than input count matters because an embed's wall-time scales with its characters — a POST sized by count from a short probe (a document summary) would over-provision when the later inputs are near-cap sections and abort. So a CPU-only device slower than the old budget gets character-bounded POSTs that complete inside `EMBED_REQUEST_TIMEOUT_MS`, instead of a fixed 16/32-input POST that exceeds it and aborts the atomic ingest write; the running maximum is monotonic, so a mid-batch slowdown shrinks the remainder and never re-expands into a timeout. `EMBED_SUBBATCH_SIZE` (default 4) is the thermal count ceiling and the hard cap on any POST — kept low because the character budget is derived from the probe's observed rate, so a probe taken while the machine is briefly quiet would otherwise let the next POST grow to the ceiling and then blow `EMBED_REQUEST_TIMEOUT_MS` if desktop contention spikes mid-batch; a ceiling of 4 bounds that worst case (~4 near-cap cold-CPU embeds) under the timeout on a contended shared desktop, while a quiet appliance is governed by the character budget instead. `EMBED_MS_PER_INPUT` (default 6000 ms, env-tunable) no longer sizes the POST; it is the reconciliation baseline — after each POST an `[embed] op=drift` line warns when the observed per-input time exceeds the budget by more than 1.5×, so a device slower than budget is surfaced by a standing check rather than by a later ingest failure. Every `/api/embed` POST also carries `keep_alive` (`EMBED_KEEP_ALIVE`, default `-1` = keep the model resident forever) so an interactive graph query-embed never cold-loads and blows its 5 s timeout. All knobs are env-overridable for on-device tuning. The load-bearing thermal protection is the installer's `CPUQuota` cap on `ollama.service` (every embed caller is bounded there regardless of batch shape); this pacing is defence-in-depth. The UI's file-index path uses its own single-input embed client in `platform/ui/app/lib/neo4j-store.ts` (one embed per file, not a batch), so it is covered by the CPU cap, not by `embedBatch` pacing.
|
|
3570
3570
|
|
|
3571
3571
|
---
|
|
3572
3572
|
|
|
@@ -95,7 +95,7 @@ If Ollama is unavailable at write time, the node is stored without an embedding
|
|
|
95
95
|
|
|
96
96
|
The `memory-reindex` tool backfills missing embeddings by iterating nodes where `embedding IS NULL`. It uses the bounded representation (the `summary` for document/archive labels, never the raw body), embeds in batches with a per-node fallback on batch error, and **skips and continues** past a failing node — keeping skipped nodes out of the re-fetch via an exclude set — instead of aborting the whole run. `embed()`/`embedBatch()` cap oversized input (`EMBED_INPUT_MAX_CHARS`) before the Ollama POST, so a large body embeds truncated rather than throwing. Per-node `[reindex] op=embedded|skip-error` lines and a terminal `[reindex] op=done processed=N skipped=M`.
|
|
97
97
|
|
|
98
|
-
`embedBatch()` also paces large batches. A document's summary plus every section body in one `/api/embed` POST pins ollama's `llama-server` across all cores for the whole batch with no idle gaps; on a passively-cooled Pi the SoC climbs past its soft-temp limit and the board thermally hard-resets. `embedBatch` splits
|
|
98
|
+
`embedBatch()` also paces large batches. A document's summary plus every section body in one `/api/embed` POST pins ollama's `llama-server` across all cores for the whole batch with no idle gaps; on a passively-cooled Pi the SoC climbs past its soft-temp limit and the board thermally hard-resets. `embedBatch` splits a multi-input array into sub-batches, waiting `EMBED_SUBBATCH_COOLDOWN_MS` (default 1000) between them so the SoC gets cooling gaps; vectors are concatenated in input order. Each POST is sized from the device's *measured* throughput, not a fixed constant: the first POST of a multi-input batch is a single-input probe (the only size provably safe without a measurement), and every POST's observed per-character wall-time folds into a running maximum. Subsequent POSTs are packed with as many inputs as fit a character budget `floor(EMBED_REQUEST_TIMEOUT_MS * 0.8 / observedMsPerChar)`, capped by the `EMBED_SUBBATCH_SIZE` count ceiling. Sizing by characters rather than input count matters because an embed's wall-time scales with its characters — a POST sized by count from a short probe (a document summary) would over-provision when the later inputs are near-cap sections and abort. So a CPU-only device slower than the old budget gets character-bounded POSTs that complete inside `EMBED_REQUEST_TIMEOUT_MS`, instead of a fixed 16/32-input POST that exceeds it and aborts the atomic ingest write; the running maximum is monotonic, so a mid-batch slowdown shrinks the remainder and never re-expands into a timeout. `EMBED_SUBBATCH_SIZE` (default 4) is the thermal count ceiling and the hard cap on any POST — kept low because the character budget is derived from the probe's observed rate, so a probe taken while the machine is briefly quiet would otherwise let the next POST grow to the ceiling and then blow `EMBED_REQUEST_TIMEOUT_MS` if desktop contention spikes mid-batch; a ceiling of 4 bounds that worst case (~4 near-cap cold-CPU embeds) under the timeout on a contended shared desktop, while a quiet appliance is governed by the character budget instead. `EMBED_MS_PER_INPUT` (default 6000 ms, env-tunable) no longer sizes the POST; it is the reconciliation baseline — after each POST an `[embed] op=drift` line warns when the observed per-input time exceeds the budget by more than 1.5×, so a device slower than budget is surfaced by a standing check rather than by a later ingest failure. Every `/api/embed` POST also carries `keep_alive` (`EMBED_KEEP_ALIVE`, default `-1` = keep the model resident forever) so an interactive graph query-embed never cold-loads and blows its 5 s timeout. All knobs are env-overridable for on-device tuning. The load-bearing thermal protection is the installer's `CPUQuota` cap on `ollama.service` (every embed caller is bounded there regardless of batch shape); this pacing is defence-in-depth. The UI's file-index path uses its own single-input embed client in `platform/ui/app/lib/neo4j-store.ts` (one embed per file, not a batch), so it is covered by the CPU cap, not by `embedBatch` pacing.
|
|
99
99
|
|
|
100
100
|
---
|
|
101
101
|
|
|
@@ -222,6 +222,8 @@ Read-only window into the extractive graph writes produced under this account. R
|
|
|
222
222
|
|
|
223
223
|
The owner's profile and preferences accumulate organically from conversation — never from questionnaires. Run `skill-load skillName=conversational-memory` for full guidance on when to observe preferences, how to handle remember/forget requests, and how to answer "what do you know about me?" transparently with confidence scores and evidence trail.
|
|
224
224
|
|
|
225
|
+
When a house operator operates a client's sub-account, the session is scoped into that account (`ACCOUNT_ID` is the sub-account, `USER_ID` is the operator, no `HOUSE_ADMIN_SCOPE`). In that session `profile-read`/`profile-update`/`profile-delete` resolve to the account's **owner** `AdminUser`, not the operator: the operator has no `AdminUser` node in the sub-account subgraph, so `resolveProfileIdentity` sees the caller is not an admin of the account it is scoped to and attributes to the seeded owner — the same owner identity the cross-account `targetAccountId` path resolves (above), reached without `targetAccountId` because the re-scoped session cannot set it. An account with no seeded owner rejects the write loudly (`… has no owner AdminUser …`) rather than attributing it to the operator, and emits `[xacct] op=identity tool=profile-* mode=operator-on-behalf target=<sub8> caller=<op8> resolved-userId=<owner8>` on the owner-attributed path. The operator's own preferences live on their own (house) session.
|
|
226
|
+
|
|
225
227
|
## UserProfile + Person Field Management
|
|
226
228
|
|
|
227
229
|
`profile-update` serves three purposes: managing Preference nodes (category/key/value), setting top-level UserProfile node properties via `profileFields`, and setting Person properties on the operator's personal-profile Person via `personFields`.
|
|
@@ -12,6 +12,7 @@ import { buildLiveSchemaSource, defaultSchemaCypherPath, } from "./lib/live-sche
|
|
|
12
12
|
import { memoryReindex } from "./tools/memory-reindex.js";
|
|
13
13
|
import { memoryIngestExtract } from "./tools/memory-ingest-extract.js";
|
|
14
14
|
import { memoryIngest } from "./tools/memory-ingest.js";
|
|
15
|
+
import { makeEmbedProgressReporter } from "./lib/embed-progress-reporter.js";
|
|
15
16
|
import { CONVERSATION_SOURCES } from "./lib/conversation-normalisers/types.js";
|
|
16
17
|
import { kdClassify } from "./tools/kd-classify.js";
|
|
17
18
|
import { memoryArchiveWrite } from "./tools/memory-archive-write.js";
|
|
@@ -42,6 +43,8 @@ import { memoryBrainCaptureRecent } from "./tools/memory-brain-capture-recent.js
|
|
|
42
43
|
import { profileUpdate } from "./tools/profile-update.js";
|
|
43
44
|
import { profileDelete } from "./tools/profile-delete.js";
|
|
44
45
|
import { resolveOwnerUserId } from "./lib/resolve-owner-userid.js";
|
|
46
|
+
import { resolveAdminHomeAccount } from "./lib/resolve-admin-home-account.js";
|
|
47
|
+
import { classifyProfileIdentity } from "./lib/profile-identity.js";
|
|
45
48
|
import { graphPruneDenylistAdd } from "./tools/graph-prune-denylist-add.js";
|
|
46
49
|
import { graphPruneDenylistList } from "./tools/graph-prune-denylist-list.js";
|
|
47
50
|
import { graphPruneDenylistRemove } from "./tools/graph-prune-denylist-remove.js";
|
|
@@ -113,31 +116,42 @@ const XACCT_DESC = "House-scoped admin only: operate on the named sub-account su
|
|
|
113
116
|
// call (the resolver only returns a target for a house-authorised session).
|
|
114
117
|
const BOOT_ACCOUNT_ID = accountId;
|
|
115
118
|
async function resolveProfileIdentity(toolName, crossAccount, resolvedAccountId, callerUserId) {
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
119
|
+
// Resolve the facts the pure classifier needs. On the own-account path the
|
|
120
|
+
// account's owner is resolved only when the caller is NOT operating their
|
|
121
|
+
// home account (a foreign house operator inside a client's sub-account), so
|
|
122
|
+
// the common owner-is-speaker path costs one AdminUser lookup, not two.
|
|
123
|
+
const callerHomeAccountId = !crossAccount && callerUserId ? await resolveAdminHomeAccount(callerUserId) : null;
|
|
124
|
+
const needOwner = crossAccount || (!!callerUserId && callerHomeAccountId !== resolvedAccountId);
|
|
125
|
+
const ownerUserId = needOwner ? await resolveOwnerUserId(resolvedAccountId) : null;
|
|
126
|
+
const decision = classifyProfileIdentity({
|
|
127
|
+
crossAccount,
|
|
128
|
+
resolvedAccountId,
|
|
129
|
+
callerUserId,
|
|
130
|
+
callerHomeAccountId,
|
|
131
|
+
ownerUserId,
|
|
132
|
+
});
|
|
133
|
+
if (decision.kind === "reject") {
|
|
134
|
+
const text = decision.reason === "no-owner"
|
|
135
|
+
? `${toolName} failed: account ${resolvedAccountId.slice(0, 8)}… has no owner AdminUser to attribute the profile to — its graph root is unprovisioned (see Task 1359 seedAccountGraphRoot).`
|
|
136
|
+
: `${toolName} requires an authenticated admin session with userId`;
|
|
133
137
|
return {
|
|
134
138
|
reject: {
|
|
135
|
-
content: [{ type: "text", text
|
|
139
|
+
content: [{ type: "text", text }],
|
|
136
140
|
isError: true,
|
|
137
141
|
},
|
|
138
142
|
};
|
|
139
143
|
}
|
|
140
|
-
|
|
144
|
+
if (decision.kind === "owner") {
|
|
145
|
+
if (crossAccount) {
|
|
146
|
+
process.stderr.write(`[xacct] op=identity tool=${toolName} target=${resolvedAccountId.slice(0, 8)} resolved-userId=${decision.userId.slice(0, 8)}\n`);
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
process.stderr.write(`[xacct] op=identity tool=${toolName} mode=operator-on-behalf target=${resolvedAccountId.slice(0, 8)} caller=${(callerUserId ?? "").slice(0, 8)} resolved-userId=${decision.userId.slice(0, 8)}\n`);
|
|
150
|
+
}
|
|
151
|
+
return { userId: decision.userId, bootstrap: true };
|
|
152
|
+
}
|
|
153
|
+
// decision.kind === "caller" — own-account owner-is-speaker / second admin.
|
|
154
|
+
return { userId: decision.userId, bootstrap: false };
|
|
141
155
|
}
|
|
142
156
|
// Load the markdown schema sidecar once at startup. Every memory-write call
|
|
143
157
|
// reads its required-property and synonym maps. If loading fails we throw —
|
|
@@ -1336,11 +1350,16 @@ if (!readOnly) {
|
|
|
1336
1350
|
forceRewrite: z.boolean().optional().describe("When inlineRewrite is true, bypass the 7-day per-entity cooldown on compiledTruthUpdatedAt. All threshold-qualifying entities are returned as inlineRewriteCandidates regardless of when their truth was last rewritten."),
|
|
1337
1351
|
inlineRewriteThreshold: z.number().int().min(1).optional().describe("Minimum MENTIONS/REFERENCES edge count from this source to an entity for it to qualify as an inline-rewrite candidate. Default: 2."),
|
|
1338
1352
|
targetAccountId: z.string().optional().describe(XACCT_DESC),
|
|
1339
|
-
}, async ({ attachmentId, conversationIdentity, source, archiveSha256, archiveSourceFile, participantElementIds, lastIngestedMessageHash, lastIngestedMessageAt, archiveTitle, cleanupPriorChunks, sessionIndex, documentSummary, anchorNodeId, anchorLabel, sections, documentEdges, orphanCandidates, scope, sourceUrl, sourceType, documentKeywords, userKeywords, sessionId: sessionIdOverride, inlineRewrite, forceRewrite, inlineRewriteThreshold, targetAccountId }) => {
|
|
1353
|
+
}, async ({ attachmentId, conversationIdentity, source, archiveSha256, archiveSourceFile, participantElementIds, lastIngestedMessageHash, lastIngestedMessageAt, archiveTitle, cleanupPriorChunks, sessionIndex, documentSummary, anchorNodeId, anchorLabel, sections, documentEdges, orphanCandidates, scope, sourceUrl, sourceType, documentKeywords, userKeywords, sessionId: sessionIdOverride, inlineRewrite, forceRewrite, inlineRewriteThreshold, targetAccountId }, extra) => {
|
|
1340
1354
|
const scoped = resolveToolAccount("memory-ingest", targetAccountId);
|
|
1341
1355
|
if ("reject" in scoped)
|
|
1342
1356
|
return scoped.reject;
|
|
1343
1357
|
const accountId = scoped.accountId;
|
|
1358
|
+
// Task 1555 — stream a progress notification per embed sub-batch POST so
|
|
1359
|
+
// a long embedding phase resets Claude Code's stdio idle timer and the
|
|
1360
|
+
// ingest commits instead of aborting mid-write. No-ops when the client
|
|
1361
|
+
// sent no progressToken.
|
|
1362
|
+
const reportProgress = makeEmbedProgressReporter(extra);
|
|
1344
1363
|
try {
|
|
1345
1364
|
const result = await memoryIngest({
|
|
1346
1365
|
accountId,
|
|
@@ -1370,6 +1389,7 @@ if (!readOnly) {
|
|
|
1370
1389
|
inlineRewrite,
|
|
1371
1390
|
forceRewrite,
|
|
1372
1391
|
inlineRewriteThreshold,
|
|
1392
|
+
reportProgress,
|
|
1373
1393
|
});
|
|
1374
1394
|
return {
|
|
1375
1395
|
content: [{
|
|
@@ -2228,8 +2248,8 @@ if (!readOnly) {
|
|
|
2228
2248
|
profileFields: profileFields,
|
|
2229
2249
|
personFields,
|
|
2230
2250
|
mergeSourceIds,
|
|
2231
|
-
bootstrapProfile:
|
|
2232
|
-
bootstrapPerson:
|
|
2251
|
+
bootstrapProfile: ident.bootstrap,
|
|
2252
|
+
bootstrapPerson: ident.bootstrap,
|
|
2233
2253
|
});
|
|
2234
2254
|
return {
|
|
2235
2255
|
content: [{
|