viveworker 0.8.9 → 0.9.0
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/README.md +14 -3
- package/package.json +1 -1
- package/plugins/viveworker-control-plane/skills/viveworker-control-plane/SKILL.md +7 -1
- package/scripts/lib/immutable-json-patch.mjs +142 -0
- package/scripts/lib/ipc-frame-worker.mjs +30 -0
- package/scripts/lib/ipc-message-projector.mjs +221 -0
- package/scripts/lib/length-prefixed-frame-decoder.mjs +128 -0
- package/scripts/lib/remote-pairing/bridge-relay-client.mjs +24 -0
- package/scripts/lib/remote-pairing/orchestrator.mjs +10 -0
- package/scripts/lib/work-items.mjs +179 -0
- package/scripts/mcp-server.mjs +240 -16
- package/scripts/moltbook-api.mjs +27 -0
- package/scripts/moltbook-cli.mjs +12 -4
- package/scripts/moltbook-scout-auto.sh +105 -37
- package/scripts/viveworker-bridge.mjs +483 -234
- package/scripts/viveworker-claude-hook.mjs +5 -1
- package/scripts/viveworker.mjs +5 -0
- package/skills/viveworker-control-plane/SKILL.md +7 -0
- package/templates/CLAUDE.viveworker.md +12 -1
- package/web/app.css +106 -0
- package/web/app.js +183 -28
- package/web/build-id.js +1 -1
- package/web/i18n.js +44 -10
- package/web/remote-pairing/api-router.js +356 -11
- package/web/remote-pairing/transport.js +7 -1
|
@@ -39,19 +39,88 @@ WINDOW_MS=$((WINDOW_SEC * 1000))
|
|
|
39
39
|
MAX_SKIP=5
|
|
40
40
|
|
|
41
41
|
# ── Detect harness ────────────────────────────────────────────
|
|
42
|
+
resolve_harness_bin() {
|
|
43
|
+
local harness="$1"
|
|
44
|
+
local configured="${2:-}"
|
|
45
|
+
local resolved=""
|
|
46
|
+
|
|
47
|
+
if [ -n "$configured" ]; then
|
|
48
|
+
if [ -x "$configured" ]; then
|
|
49
|
+
printf '%s\n' "$configured"
|
|
50
|
+
return 0
|
|
51
|
+
fi
|
|
52
|
+
resolved=$(command -v "$configured" 2>/dev/null || true)
|
|
53
|
+
if [ -n "$resolved" ] && [ -x "$resolved" ]; then
|
|
54
|
+
printf '%s\n' "$resolved"
|
|
55
|
+
return 0
|
|
56
|
+
fi
|
|
57
|
+
fi
|
|
58
|
+
|
|
59
|
+
resolved=$(command -v "$harness" 2>/dev/null || true)
|
|
60
|
+
if [ -n "$resolved" ] && [ -x "$resolved" ]; then
|
|
61
|
+
printf '%s\n' "$resolved"
|
|
62
|
+
return 0
|
|
63
|
+
fi
|
|
64
|
+
|
|
65
|
+
# Desktop app updates can move the bundled Codex binary without rewriting
|
|
66
|
+
# an already-installed LaunchAgent. Resolve the current app bundle at every
|
|
67
|
+
# run so a stale absolute path does not disable scoring and drafting.
|
|
68
|
+
if [ "$harness" = "codex" ]; then
|
|
69
|
+
local candidate=""
|
|
70
|
+
for candidate in \
|
|
71
|
+
"/Applications/ChatGPT.app/Contents/Resources/codex" \
|
|
72
|
+
"/Applications/Codex.app/Contents/Resources/codex" \
|
|
73
|
+
"$HOME/Applications/ChatGPT.app/Contents/Resources/codex" \
|
|
74
|
+
"$HOME/Applications/Codex.app/Contents/Resources/codex"; do
|
|
75
|
+
if [ -x "$candidate" ]; then
|
|
76
|
+
printf '%s\n' "$candidate"
|
|
77
|
+
return 0
|
|
78
|
+
fi
|
|
79
|
+
done
|
|
80
|
+
fi
|
|
81
|
+
|
|
82
|
+
return 1
|
|
83
|
+
}
|
|
84
|
+
|
|
42
85
|
HARNESS="${SCOUT_HARNESS:-}"
|
|
86
|
+
CONFIGURED_HARNESS_BIN="${SCOUT_HARNESS_BIN:-}"
|
|
43
87
|
HARNESS_BIN=""
|
|
44
|
-
if [ -
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
88
|
+
if [ -n "$HARNESS" ]; then
|
|
89
|
+
HARNESS_BIN=$(resolve_harness_bin "$HARNESS" "$CONFIGURED_HARNESS_BIN" || true)
|
|
90
|
+
else
|
|
91
|
+
HARNESS_BIN=$(resolve_harness_bin "codex" "" || true)
|
|
92
|
+
if [ -n "$HARNESS_BIN" ]; then
|
|
48
93
|
HARNESS="codex"
|
|
94
|
+
else
|
|
95
|
+
HARNESS_BIN=$(resolve_harness_bin "claude" "" || true)
|
|
96
|
+
if [ -n "$HARNESS_BIN" ]; then
|
|
97
|
+
HARNESS="claude"
|
|
98
|
+
fi
|
|
49
99
|
fi
|
|
50
100
|
fi
|
|
51
|
-
if [ -
|
|
52
|
-
|
|
101
|
+
if [ -z "${SCOUT_DRAFT_TIMEOUT:-}" ] && [ "$HARNESS" = "codex" ]; then
|
|
102
|
+
DRAFT_TIMEOUT_SEC=300
|
|
53
103
|
fi
|
|
54
104
|
|
|
105
|
+
run_harness_prompt() {
|
|
106
|
+
local timeout_sec="$1"
|
|
107
|
+
local prompt="$2"
|
|
108
|
+
|
|
109
|
+
if [ "$HARNESS" = "claude" ]; then
|
|
110
|
+
portable_timeout "$timeout_sec" "$HARNESS_BIN" -p "$prompt" --output-format text
|
|
111
|
+
elif [ "$HARNESS" = "codex" ]; then
|
|
112
|
+
printf '%s' "$prompt" | portable_timeout "$timeout_sec" "$HARNESS_BIN" exec \
|
|
113
|
+
-C "$SCRIPT_DIR/.." \
|
|
114
|
+
--sandbox read-only \
|
|
115
|
+
--ephemeral \
|
|
116
|
+
--ignore-user-config \
|
|
117
|
+
--ignore-rules \
|
|
118
|
+
--color never
|
|
119
|
+
else
|
|
120
|
+
return 127
|
|
121
|
+
fi
|
|
122
|
+
}
|
|
123
|
+
|
|
55
124
|
# ── Load persona & avoid keywords ─────────────────────────────
|
|
56
125
|
PERSONA=""
|
|
57
126
|
AVOID_PATTERN=""
|
|
@@ -86,6 +155,19 @@ candidate_should_skip() {
|
|
|
86
155
|
printf '%s\n%s' "$1" "$2" | grep -iqE "$AVOID_PATTERN"
|
|
87
156
|
}
|
|
88
157
|
|
|
158
|
+
looks_like_harness_error() {
|
|
159
|
+
printf '%s' "$1" | grep -Eiq 'failed to authenticate|invalid authentication credentials|authentication_error|api error:[[:space:]]*(401|403)|incorrect api key|invalid api key'
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
abort_if_harness_error() {
|
|
163
|
+
local stage="$1"
|
|
164
|
+
local text="$2"
|
|
165
|
+
if looks_like_harness_error "$text"; then
|
|
166
|
+
echo "[scout-auto] $stage: harness returned a provider authentication error; not proposing a Moltbook draft"
|
|
167
|
+
exit 1
|
|
168
|
+
fi
|
|
169
|
+
}
|
|
170
|
+
|
|
89
171
|
# Temp file for JSON interchange
|
|
90
172
|
SCOUT_TMP=$(mktemp /tmp/viveworker-scout-XXXXXX.json)
|
|
91
173
|
LOCK_DIR="${TMPDIR:-/tmp}/viveworker-moltbook-scout.lock"
|
|
@@ -160,16 +242,12 @@ INTENT: (1-2 sentences in Japanese: why this reply angle fits the incoming comme
|
|
|
160
242
|
INBOX_PROMPT_EOF
|
|
161
243
|
)"
|
|
162
244
|
|
|
163
|
-
INBOX_DRAFT_TEXT
|
|
164
|
-
if [ "$HARNESS" = "claude" ]; then
|
|
165
|
-
INBOX_DRAFT_TEXT=$(portable_timeout "$DRAFT_TIMEOUT_SEC" "$HARNESS_BIN" -p "$INBOX_PROMPT" --output-format text 2>/dev/null) || true
|
|
166
|
-
elif [ "$HARNESS" = "codex" ]; then
|
|
167
|
-
INBOX_DRAFT_TEXT=$(portable_timeout "$DRAFT_TIMEOUT_SEC" "$HARNESS_BIN" exec "$INBOX_PROMPT" 2>/dev/null) || true
|
|
168
|
-
fi
|
|
245
|
+
INBOX_DRAFT_TEXT=$(run_harness_prompt "$DRAFT_TIMEOUT_SEC" "$INBOX_PROMPT" 2>/dev/null) || true
|
|
169
246
|
|
|
170
247
|
if [ -z "$INBOX_DRAFT_TEXT" ]; then
|
|
171
248
|
echo "[scout-auto] inbox: harness returned empty draft or timed out after ${DRAFT_TIMEOUT_SEC}s"
|
|
172
249
|
else
|
|
250
|
+
abort_if_harness_error "inbox" "$INBOX_DRAFT_TEXT"
|
|
173
251
|
INBOX_INTENT=""
|
|
174
252
|
INBOX_REPLY_BODY=""
|
|
175
253
|
if echo "$INBOX_DRAFT_TEXT" | grep -q "^---$"; then
|
|
@@ -210,16 +288,17 @@ fi
|
|
|
210
288
|
# Max 3 approved posts/day. Each slot proposed at most once.
|
|
211
289
|
# ═══════════════════════════════════════════════════════════════
|
|
212
290
|
COMPOSE_MAX="${COMPOSE_MAX:-3}"
|
|
291
|
+
COMPOSE_ACTIVITY_LIMIT="${SCOUT_COMPOSE_ACTIVITY_LIMIT:-30}"
|
|
213
292
|
COMPOSE_JSON=$("$NODE" "$VIVEWORKER" moltbook compose --max-daily "$COMPOSE_MAX" 2>/dev/null) || true
|
|
214
293
|
COMPOSE_STATUS=$(json_field "$COMPOSE_JSON" "status")
|
|
215
294
|
|
|
216
295
|
if [ "$COMPOSE_STATUS" = "material" ] && [ -n "$HARNESS_BIN" ]; then
|
|
217
296
|
COMPOSE_SLOT=$(json_field "$COMPOSE_JSON" "slot")
|
|
218
297
|
ACTIVITY_COUNT=$(json_field "$COMPOSE_JSON" "activityCount")
|
|
219
|
-
ACTIVITY_SUMMARY=$(echo "$COMPOSE_JSON" | "$NODE" -e "let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{try{const o=JSON.parse(d);const e=o.activitySummary||[];console.log(e.map(x=>'- ['+x.kind+'] '+x.title+(x.summary?' — '+x.summary:'')).join('\n'))}catch{}})")
|
|
220
|
-
RECENT_TITLES=$(echo "$COMPOSE_JSON" | "$NODE" -e "let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{try{const o=JSON.parse(d);
|
|
298
|
+
ACTIVITY_SUMMARY=$(echo "$COMPOSE_JSON" | "$NODE" -e "let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{try{const o=JSON.parse(d);const limit=Number('$COMPOSE_ACTIVITY_LIMIT')||30;const e=(o.activitySummary||[]).slice(0,limit);console.log(e.map(x=>'- ['+x.kind+'] '+x.title+(x.summary?' — '+x.summary:'')).join('\n'))}catch{}})")
|
|
299
|
+
RECENT_TITLES=$(echo "$COMPOSE_JSON" | "$NODE" -e "let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{try{const o=JSON.parse(d);const titles=(o.recentTitles||[]).map(x=>typeof x==='string'?x:(x&&x.title)||'').filter(Boolean).slice(0,10);console.log(titles.join(', '))}catch{}})")
|
|
221
300
|
|
|
222
|
-
echo "[scout-auto] compose ($COMPOSE_SLOT): ${ACTIVITY_COUNT} activities found"
|
|
301
|
+
echo "[scout-auto] compose ($COMPOSE_SLOT): ${ACTIVITY_COUNT} activities found (using ${COMPOSE_ACTIVITY_LIMIT})"
|
|
223
302
|
|
|
224
303
|
# Slot-specific tone instructions.
|
|
225
304
|
case "$COMPOSE_SLOT" in
|
|
@@ -275,14 +354,10 @@ INTENT: (1-2 sentences in Japanese: why this is worth posting from this persona
|
|
|
275
354
|
COMPOSE_EOF
|
|
276
355
|
)"
|
|
277
356
|
|
|
278
|
-
COMPOSE_DRAFT
|
|
279
|
-
if [ "$HARNESS" = "claude" ]; then
|
|
280
|
-
COMPOSE_DRAFT=$(portable_timeout 60 "$HARNESS_BIN" -p "$COMPOSE_PROMPT" --output-format text 2>/dev/null) || true
|
|
281
|
-
elif [ "$HARNESS" = "codex" ]; then
|
|
282
|
-
COMPOSE_DRAFT=$(portable_timeout 60 "$HARNESS_BIN" exec "$COMPOSE_PROMPT" 2>/dev/null) || true
|
|
283
|
-
fi
|
|
357
|
+
COMPOSE_DRAFT=$(run_harness_prompt "$DRAFT_TIMEOUT_SEC" "$COMPOSE_PROMPT" 2>/dev/null) || true
|
|
284
358
|
|
|
285
359
|
if [ -n "$COMPOSE_DRAFT" ]; then
|
|
360
|
+
abort_if_harness_error "compose ($COMPOSE_SLOT)" "$COMPOSE_DRAFT"
|
|
286
361
|
# Check for NO_MATCH signal (persona doesn't align with today's work).
|
|
287
362
|
if echo "$COMPOSE_DRAFT" | grep -q "NO_MATCH"; then
|
|
288
363
|
echo "[scout-auto] compose ($COMPOSE_SLOT): persona has no match with current activities — skipping"
|
|
@@ -393,17 +468,13 @@ PROMPT_EOF
|
|
|
393
468
|
)"
|
|
394
469
|
|
|
395
470
|
echo "[scout-auto] drafting via $HARNESS for '$BEST_TITLE'"
|
|
396
|
-
DRAFT_TEXT
|
|
397
|
-
if [ "$HARNESS" = "claude" ]; then
|
|
398
|
-
DRAFT_TEXT=$(portable_timeout "$DRAFT_TIMEOUT_SEC" "$HARNESS_BIN" -p "$DRAFT_PROMPT" --output-format text 2>/dev/null) || true
|
|
399
|
-
elif [ "$HARNESS" = "codex" ]; then
|
|
400
|
-
DRAFT_TEXT=$(portable_timeout "$DRAFT_TIMEOUT_SEC" "$HARNESS_BIN" exec "$DRAFT_PROMPT" 2>/dev/null) || true
|
|
401
|
-
fi
|
|
471
|
+
DRAFT_TEXT=$(run_harness_prompt "$DRAFT_TIMEOUT_SEC" "$DRAFT_PROMPT" 2>/dev/null) || true
|
|
402
472
|
|
|
403
473
|
if [ -z "$DRAFT_TEXT" ]; then
|
|
404
474
|
echo "[scout-auto] error: harness returned empty draft or timed out after ${DRAFT_TIMEOUT_SEC}s"
|
|
405
475
|
exit 1
|
|
406
476
|
fi
|
|
477
|
+
abort_if_harness_error "draft" "$DRAFT_TEXT"
|
|
407
478
|
|
|
408
479
|
# Parse INTENT and reply body
|
|
409
480
|
INTENT=""
|
|
@@ -498,11 +569,13 @@ Post body (first 500 chars): ${POST_CONTENT:0:500}
|
|
|
498
569
|
|
|
499
570
|
Reply with ONLY a number from 0 to 100. Nothing else."
|
|
500
571
|
|
|
501
|
-
SCORE_RAW
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
572
|
+
SCORE_RAW=$(run_harness_prompt 30 "$SCORE_PROMPT" 2>/dev/null) || true
|
|
573
|
+
abort_if_harness_error "score" "$SCORE_RAW"
|
|
574
|
+
|
|
575
|
+
if [ -z "$SCORE_RAW" ]; then
|
|
576
|
+
SKIP_COUNT=$((SKIP_COUNT + 1))
|
|
577
|
+
echo "[scout-auto] skipping (score failed): $POST_TITLE (by @$POST_AUTHOR) [$SKIP_COUNT/$MAX_SKIP]"
|
|
578
|
+
continue
|
|
506
579
|
fi
|
|
507
580
|
|
|
508
581
|
SCORE=$(echo "$SCORE_RAW" | tr -dc '0-9\n' | head -1)
|
|
@@ -515,11 +588,6 @@ Reply with ONLY a number from 0 to 100. Nothing else."
|
|
|
515
588
|
continue
|
|
516
589
|
fi
|
|
517
590
|
|
|
518
|
-
if [ -z "$SCORE_RAW" ]; then
|
|
519
|
-
SKIP_COUNT=$((SKIP_COUNT + 1))
|
|
520
|
-
echo "[scout-auto] skipping (score failed): $POST_TITLE (by @$POST_AUTHOR) [$SKIP_COUNT/$MAX_SKIP]"
|
|
521
|
-
continue
|
|
522
|
-
fi
|
|
523
591
|
else
|
|
524
592
|
SCORE=50
|
|
525
593
|
fi
|