subconscious-cli 0.2.0 → 0.3.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 +237 -54
- package/bin/agents.js +634 -114
- package/bin/auth.js +94 -20
- package/bin/branding.js +39 -0
- package/bin/cli.js +187 -17
- package/bin/colors.js +22 -10
- package/bin/profiles.js +852 -0
- package/bin/registry.generated.json +268 -0
- package/bin/runbook/README.md +25 -0
- package/bin/runbook/claude-code/install.sh +316 -0
- package/bin/runbook/claude-code/run.sh +164 -0
- package/bin/runbook/codex/hook.sh +112 -0
- package/bin/runbook/codex/hooks.json +29 -0
- package/bin/runbook/codex/install.sh +524 -0
- package/bin/runbook/codex/run.sh +264 -0
- package/bin/runbook/copilot/hook.sh +173 -0
- package/bin/runbook/copilot/hooks.json +19 -0
- package/bin/runbook/copilot/install.sh +469 -0
- package/bin/runbook/cursor/hook.sh +145 -0
- package/bin/runbook/cursor/hooks.json +17 -0
- package/bin/runbook/cursor/install.sh +259 -0
- package/bin/runbook/opencode/install.sh +298 -0
- package/bin/runbook/opencode/run.sh +107 -0
- package/bin/runbook/opencode/subconscious-compaction.ts +100 -0
- package/bin/runbook/pi/install.sh +282 -0
- package/bin/runbook/pi/run.sh +25 -0
- package/bin/runbook/pi/subconscious-compaction.ts +151 -0
- package/package.json +10 -5
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Point the `codex` CLI at the Subconscious gateway — ephemerally.
|
|
3
|
+
#
|
|
4
|
+
# Uses `codex -c key=value` overrides so nothing is written to ~/.codex/config.toml.
|
|
5
|
+
# web_search is disabled so Codex doesn't send hosted tools the gateway can't execute.
|
|
6
|
+
# The model catalog (needed to suppress the "model metadata not found" warning)
|
|
7
|
+
# is written to a temp file that is cleaned up on exit.
|
|
8
|
+
#
|
|
9
|
+
# Subagents are OFF by default. Current Codex (>=0.144) wraps subagent tools in a
|
|
10
|
+
# `type: "namespace"` wire format that non-OpenAI providers can't resolve, causing
|
|
11
|
+
# "unsupported call: spawn_agent" (upstream #32318, #26977). The fix (PR #29602) is
|
|
12
|
+
# not yet merged.
|
|
13
|
+
#
|
|
14
|
+
# To run with subagents, pass --subagents — this runs the pinned legacy
|
|
15
|
+
# codex@0.132.0 with multi-agent v1 config (plain tool names) that the model can
|
|
16
|
+
# resolve. Requires npx.
|
|
17
|
+
#
|
|
18
|
+
# Usage:
|
|
19
|
+
# ./run.sh # uses GATEWAY_URL/API_KEY from ../.env
|
|
20
|
+
# ./run.sh --context-window 5000000 -- --resume
|
|
21
|
+
# ./run.sh --subagents # run codex@0.132.0 with subagents enabled
|
|
22
|
+
# ./run.sh --subagents -- --resume # subagents + passthrough args
|
|
23
|
+
# ./run.sh --external-tools # include Codex apps/plugins (may exceed gateway tool limits)
|
|
24
|
+
#
|
|
25
|
+
# Config: copy ../env.example to ../.env and edit. .env is gitignored.
|
|
26
|
+
# All agents share one coding-agents/.env file.
|
|
27
|
+
#
|
|
28
|
+
# Or source it to just export the env:
|
|
29
|
+
# source run.sh
|
|
30
|
+
|
|
31
|
+
set -euo pipefail
|
|
32
|
+
|
|
33
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
34
|
+
|
|
35
|
+
# Load shared env from coding-agents/.env (gitignored) or env.example.
|
|
36
|
+
SHARED_ENV="${MBTA_ENV_FILE:-${SCRIPT_DIR}/../.env}"
|
|
37
|
+
[[ -f "$SHARED_ENV" ]] || SHARED_ENV="${SCRIPT_DIR}/../env.example"
|
|
38
|
+
if [[ -f "$SHARED_ENV" ]]; then set -a; source "$SHARED_ENV"; set +a; fi
|
|
39
|
+
|
|
40
|
+
GATEWAY_URL="${GATEWAY_URL:-}"
|
|
41
|
+
API_KEY="${CODEX_API_KEY:-${API_KEY:-}}"
|
|
42
|
+
MODEL="${MODEL:-subconscious/glm-5.2}"
|
|
43
|
+
MAX_CONCURRENT_SUBAGENTS="${MAX_CONCURRENT_SUBAGENTS:-4}"
|
|
44
|
+
SUBAGENTS=false
|
|
45
|
+
EXTERNAL_TOOLS="${CODEX_EXTERNAL_TOOLS:-false}"
|
|
46
|
+
CODEX_CONTEXT_WINDOW="${CODEX_CONTEXT_WINDOW:-5000000}"
|
|
47
|
+
CODEX_MAX_CONTEXT_WINDOW="${CODEX_MAX_CONTEXT_WINDOW:-}"
|
|
48
|
+
CODEX_AUTO_COMPACT_TOKEN_LIMIT="${CODEX_AUTO_COMPACT_TOKEN_LIMIT:-4500000}"
|
|
49
|
+
CODEX_REASONING_EFFORT="${CODEX_REASONING_EFFORT:-max}"
|
|
50
|
+
|
|
51
|
+
# Codex version that supports the legacy multi-agent v1 config (plain tool names).
|
|
52
|
+
SUBAGENT_CODEX_VERSION="0.132.0"
|
|
53
|
+
|
|
54
|
+
# Parse args (only when executed, not sourced)
|
|
55
|
+
PASSTHRU=()
|
|
56
|
+
if [[ "${BASH_SOURCE[0]:-$0}" == "${0}" ]]; then
|
|
57
|
+
while [[ $# -gt 0 ]]; do
|
|
58
|
+
case "$1" in
|
|
59
|
+
--context-window)
|
|
60
|
+
CODEX_CONTEXT_WINDOW="${2:-}"
|
|
61
|
+
shift 2
|
|
62
|
+
;;
|
|
63
|
+
--max-context-window)
|
|
64
|
+
CODEX_MAX_CONTEXT_WINDOW="${2:-}"
|
|
65
|
+
shift 2
|
|
66
|
+
;;
|
|
67
|
+
--auto-compact-token-limit)
|
|
68
|
+
CODEX_AUTO_COMPACT_TOKEN_LIMIT="${2:-}"
|
|
69
|
+
shift 2
|
|
70
|
+
;;
|
|
71
|
+
--reasoning-effort)
|
|
72
|
+
CODEX_REASONING_EFFORT="${2:-}"
|
|
73
|
+
shift 2
|
|
74
|
+
;;
|
|
75
|
+
--subagents)
|
|
76
|
+
SUBAGENTS=true
|
|
77
|
+
shift
|
|
78
|
+
;;
|
|
79
|
+
--external-tools)
|
|
80
|
+
EXTERNAL_TOOLS=true
|
|
81
|
+
shift
|
|
82
|
+
;;
|
|
83
|
+
--)
|
|
84
|
+
shift
|
|
85
|
+
PASSTHRU+=("$@")
|
|
86
|
+
break
|
|
87
|
+
;;
|
|
88
|
+
*)
|
|
89
|
+
PASSTHRU+=("$1")
|
|
90
|
+
shift
|
|
91
|
+
;;
|
|
92
|
+
esac
|
|
93
|
+
done
|
|
94
|
+
fi
|
|
95
|
+
|
|
96
|
+
CODEX_MAX_CONTEXT_WINDOW="${CODEX_MAX_CONTEXT_WINDOW:-${CODEX_CONTEXT_WINDOW}}"
|
|
97
|
+
case "$CODEX_REASONING_EFFORT" in
|
|
98
|
+
none|low|medium|high|max) ;;
|
|
99
|
+
*)
|
|
100
|
+
echo "error: reasoning effort must be one of: none, low, medium, high, max" >&2
|
|
101
|
+
exit 1
|
|
102
|
+
;;
|
|
103
|
+
esac
|
|
104
|
+
|
|
105
|
+
DEFAULT_SUBCONSCIOUS_MODELS="subconscious/glm-5.2
|
|
106
|
+
subconscious/tim-qwen3.6-27b
|
|
107
|
+
subconscious/deepseek-v4-flash-marathon"
|
|
108
|
+
SUPPORTED_MODELS=()
|
|
109
|
+
|
|
110
|
+
add_supported_model() {
|
|
111
|
+
local model_id="$1" existing
|
|
112
|
+
[[ -n "$model_id" ]] || return 0
|
|
113
|
+
if [[ ! "$model_id" =~ ^[-A-Za-z0-9._:/+]+$ ]]; then
|
|
114
|
+
echo "error: invalid model id: $model_id" >&2
|
|
115
|
+
exit 1
|
|
116
|
+
fi
|
|
117
|
+
if [[ "${#SUPPORTED_MODELS[@]}" -gt 0 ]]; then
|
|
118
|
+
for existing in "${SUPPORTED_MODELS[@]}"; do
|
|
119
|
+
[[ "$existing" == "$model_id" ]] && return 0
|
|
120
|
+
done
|
|
121
|
+
fi
|
|
122
|
+
SUPPORTED_MODELS+=("$model_id")
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
# Keep the requested model first while exposing the complete Subconscious
|
|
126
|
+
# catalog in Codex's /model picker. The CLI supplies this list from the registry;
|
|
127
|
+
# the fallback keeps the vendored runbook useful on its own.
|
|
128
|
+
add_supported_model "$MODEL"
|
|
129
|
+
while IFS= read -r model_id; do
|
|
130
|
+
add_supported_model "$model_id"
|
|
131
|
+
done <<< "${SUBCONSCIOUS_MODELS:-$DEFAULT_SUBCONSCIOUS_MODELS}"
|
|
132
|
+
|
|
133
|
+
write_model_catalog() {
|
|
134
|
+
local catalog_file="$1" model_id index=0
|
|
135
|
+
{
|
|
136
|
+
printf '{\n "models": [\n'
|
|
137
|
+
for model_id in "${SUPPORTED_MODELS[@]}"; do
|
|
138
|
+
if [[ "$index" -gt 0 ]]; then
|
|
139
|
+
printf ',\n'
|
|
140
|
+
fi
|
|
141
|
+
cat <<EOF
|
|
142
|
+
{
|
|
143
|
+
"slug": "${model_id}",
|
|
144
|
+
"display_name": "${model_id}",
|
|
145
|
+
"description": "Subconscious API Gateway model ${model_id}",
|
|
146
|
+
"context_window": ${CODEX_CONTEXT_WINDOW},
|
|
147
|
+
"max_context_window": ${CODEX_MAX_CONTEXT_WINDOW},
|
|
148
|
+
"auto_compact_token_limit": ${CODEX_AUTO_COMPACT_TOKEN_LIMIT},
|
|
149
|
+
"effective_context_window_percent": 95,
|
|
150
|
+
"supported_reasoning_levels": [],
|
|
151
|
+
"shell_type": "shell_command",
|
|
152
|
+
"visibility": "list",
|
|
153
|
+
"supported_in_api": true,
|
|
154
|
+
"priority": 0,
|
|
155
|
+
"availability_nux": null,
|
|
156
|
+
"upgrade": null,
|
|
157
|
+
"base_instructions": "You are Codex, a coding agent.",
|
|
158
|
+
"supports_reasoning_summaries": false,
|
|
159
|
+
"support_verbosity": false,
|
|
160
|
+
"default_verbosity": null,
|
|
161
|
+
"apply_patch_tool_type": "freeform",
|
|
162
|
+
"truncation_policy": { "mode": "tokens", "limit": 10000 },
|
|
163
|
+
"supports_parallel_tool_calls": true,
|
|
164
|
+
"experimental_supported_tools": []
|
|
165
|
+
}
|
|
166
|
+
EOF
|
|
167
|
+
index=$((index + 1))
|
|
168
|
+
done
|
|
169
|
+
printf '\n ]\n}\n'
|
|
170
|
+
} >"$catalog_file"
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
# The gateway accepts at most 128 tools per request. Codex apps and installed
|
|
174
|
+
# plugins can collectively exceed that before any core coding tools are added,
|
|
175
|
+
# so keep those external catalogs off for Subconscious by default. Users can
|
|
176
|
+
# opt back in with --external-tools if their gateway supports a larger limit.
|
|
177
|
+
EXTERNAL_TOOL_ARGS=()
|
|
178
|
+
if [[ "$EXTERNAL_TOOLS" != "true" ]]; then
|
|
179
|
+
EXTERNAL_TOOL_ARGS=(
|
|
180
|
+
-c features.apps=false
|
|
181
|
+
-c features.plugins=false
|
|
182
|
+
-c apps._default.enabled=false
|
|
183
|
+
)
|
|
184
|
+
fi
|
|
185
|
+
|
|
186
|
+
if [[ -z "$GATEWAY_URL" || -z "$API_KEY" ]]; then
|
|
187
|
+
echo "error: GATEWAY_URL and API_KEY must be set in ../.env" >&2
|
|
188
|
+
exit 1
|
|
189
|
+
fi
|
|
190
|
+
|
|
191
|
+
export SUBCONSCIOUS_API_KEY="$API_KEY"
|
|
192
|
+
export SUBCONSCIOUS_GATEWAY_URL="${GATEWAY_URL%/}"
|
|
193
|
+
|
|
194
|
+
# Ensure compaction hooks are present for ephemeral runs too. Codex discovers
|
|
195
|
+
# ~/.codex/hooks.json alongside config layers; trust them once via /hooks.
|
|
196
|
+
HOOK_SRC="${SCRIPT_DIR}/hook.sh"
|
|
197
|
+
HOOKS_TEMPLATE="${SCRIPT_DIR}/hooks.json"
|
|
198
|
+
CODEX_DIR="${HOME}/.codex"
|
|
199
|
+
HOOK_DST="${CODEX_DIR}/subconscious-hook.sh"
|
|
200
|
+
HOOKS_JSON="${CODEX_DIR}/hooks.json"
|
|
201
|
+
HOOKS_ENV_FILE="${CODEX_DIR}/subconscious-hooks.env"
|
|
202
|
+
if [[ -f "$HOOK_SRC" && -f "$HOOKS_TEMPLATE" ]]; then
|
|
203
|
+
mkdir -p "$CODEX_DIR"
|
|
204
|
+
cp "$HOOK_SRC" "$HOOK_DST"
|
|
205
|
+
chmod +x "$HOOK_DST"
|
|
206
|
+
sed "s|HOOK_SH_PATH|${HOOK_DST}|g" "$HOOKS_TEMPLATE" >"$HOOKS_JSON"
|
|
207
|
+
umask 077
|
|
208
|
+
cat >"$HOOKS_ENV_FILE" <<EOF
|
|
209
|
+
# Generated by ol-runbook/coding-agents/codex/run.sh — do not commit secrets.
|
|
210
|
+
export SUBCONSCIOUS_GATEWAY_URL='${GATEWAY_URL%/}'
|
|
211
|
+
export SUBCONSCIOUS_API_KEY='${API_KEY}'
|
|
212
|
+
EOF
|
|
213
|
+
chmod 600 "$HOOKS_ENV_FILE"
|
|
214
|
+
fi
|
|
215
|
+
|
|
216
|
+
# Write a temp model catalog so Codex doesn't print "model metadata not found".
|
|
217
|
+
# This is the one thing that can't be passed via -c flags.
|
|
218
|
+
CATALOG_FILE="$(mktemp -t codex-model-catalog.XXXXXX.json)"
|
|
219
|
+
cleanup() { rm -f "$CATALOG_FILE"; }
|
|
220
|
+
trap cleanup EXIT
|
|
221
|
+
write_model_catalog "$CATALOG_FILE"
|
|
222
|
+
|
|
223
|
+
# If sourced, just export env and return.
|
|
224
|
+
if [[ "${BASH_SOURCE[0]:-$0}" != "${0}" ]]; then
|
|
225
|
+
export GATEWAY_URL CATALOG_FILE MAX_CONCURRENT_SUBAGENTS SUBAGENTS
|
|
226
|
+
return 0 2>/dev/null || true
|
|
227
|
+
fi
|
|
228
|
+
|
|
229
|
+
# Ephemeral config via -c flags — nothing is written to ~/.codex/config.toml.
|
|
230
|
+
if [[ "$SUBAGENTS" == "true" ]]; then
|
|
231
|
+
# Legacy multi-agent v1 config — plain tool names the model can resolve.
|
|
232
|
+
echo "Starting codex@${SUBAGENT_CODEX_VERSION} with subagents enabled (max ${MAX_CONCURRENT_SUBAGENTS} threads)..." >&2
|
|
233
|
+
exec npx -y "@openai/codex@${SUBAGENT_CODEX_VERSION}" \
|
|
234
|
+
-c model="${MODEL}" \
|
|
235
|
+
-c model_provider=subconscious \
|
|
236
|
+
-c model_catalog_json="${CATALOG_FILE}" \
|
|
237
|
+
-c model_reasoning_effort="${CODEX_REASONING_EFFORT}" \
|
|
238
|
+
-c web_search=disabled \
|
|
239
|
+
${EXTERNAL_TOOL_ARGS[@]+"${EXTERNAL_TOOL_ARGS[@]}"} \
|
|
240
|
+
-c features.multi_agent=true \
|
|
241
|
+
-c agents.max_threads="${MAX_CONCURRENT_SUBAGENTS}" \
|
|
242
|
+
-c agents.max_depth=1 \
|
|
243
|
+
-c agents.interrupt_message=true \
|
|
244
|
+
-c model_providers.subconscious.name=Subconscious \
|
|
245
|
+
-c model_providers.subconscious.base_url="${GATEWAY_URL}/v1" \
|
|
246
|
+
-c model_providers.subconscious.wire_api=responses \
|
|
247
|
+
-c model_providers.subconscious.env_key=SUBCONSCIOUS_API_KEY \
|
|
248
|
+
-c model_providers.subconscious.stream_idle_timeout_ms=300000 \
|
|
249
|
+
${PASSTHRU[@]+"${PASSTHRU[@]}"}
|
|
250
|
+
else
|
|
251
|
+
exec codex \
|
|
252
|
+
-c model="${MODEL}" \
|
|
253
|
+
-c model_provider=subconscious \
|
|
254
|
+
-c model_catalog_json="${CATALOG_FILE}" \
|
|
255
|
+
-c model_reasoning_effort="${CODEX_REASONING_EFFORT}" \
|
|
256
|
+
-c web_search=disabled \
|
|
257
|
+
${EXTERNAL_TOOL_ARGS[@]+"${EXTERNAL_TOOL_ARGS[@]}"} \
|
|
258
|
+
-c model_providers.subconscious.name=Subconscious \
|
|
259
|
+
-c model_providers.subconscious.base_url="${GATEWAY_URL}/v1" \
|
|
260
|
+
-c model_providers.subconscious.wire_api=responses \
|
|
261
|
+
-c model_providers.subconscious.env_key=SUBCONSCIOUS_API_KEY \
|
|
262
|
+
-c model_providers.subconscious.stream_idle_timeout_ms=300000 \
|
|
263
|
+
${PASSTHRU[@]+"${PASSTHRU[@]}"}
|
|
264
|
+
fi
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Fail-open VS Code Copilot hook: announce prompts and auto-compactions to the
|
|
3
|
+
# gateway. Never blocks the agent.
|
|
4
|
+
# Stdin: VS Code hook JSON. Stdout: permissive JSON for VS Code.
|
|
5
|
+
#
|
|
6
|
+
# Docs: https://code.visualstudio.com/docs/copilot/customization/hooks
|
|
7
|
+
# PreCompact: https://code.visualstudio.com/docs/agents/reference/hooks-reference#precompact
|
|
8
|
+
#
|
|
9
|
+
# Two events:
|
|
10
|
+
# UserPromptSubmit -> conversation_ensure { conversation_id, prompt }
|
|
11
|
+
# + conversation_compaction phase "end" when a pending
|
|
12
|
+
# auto-compact marker exists for this session
|
|
13
|
+
# PreCompact -> conversation_compaction { conversation_id, phase: start }
|
|
14
|
+
#
|
|
15
|
+
# Copilot compaction is an LLM request through the Custom Endpoint. There is no
|
|
16
|
+
# PostCompact, so PreCompact opens a window and the next UserPromptSubmit closes
|
|
17
|
+
# it. That brackets the summarization turn between the two signals.
|
|
18
|
+
#
|
|
19
|
+
# Known gap: PreCompact does not fire for manual compact. Auto-compact only.
|
|
20
|
+
#
|
|
21
|
+
# Deliberately NOT registered: SessionStart / SubagentStart / SubagentStop /
|
|
22
|
+
# Stop / PreToolUse / PostToolUse. UserPromptSubmit already fires for subagent
|
|
23
|
+
# prompts, and subagents are correlated gateway-side from the parent's
|
|
24
|
+
# runSubagent tool call.
|
|
25
|
+
|
|
26
|
+
set -u
|
|
27
|
+
|
|
28
|
+
CONFIG="${SUBCONSCIOUS_HOOKS_ENV:-${HOME}/.copilot/subconscious-hooks.env}"
|
|
29
|
+
if [[ -f "$CONFIG" ]]; then
|
|
30
|
+
# shellcheck disable=SC1090
|
|
31
|
+
source "$CONFIG"
|
|
32
|
+
fi
|
|
33
|
+
|
|
34
|
+
GATEWAY_URL="${SUBCONSCIOUS_GATEWAY_URL:-}"
|
|
35
|
+
API_KEY="${SUBCONSCIOUS_API_KEY:-}"
|
|
36
|
+
PENDING_DIR="${SUBCONSCIOUS_COMPACT_PENDING_DIR:-${HOME}/.copilot/subconscious-compact-pending}"
|
|
37
|
+
|
|
38
|
+
fail_open() {
|
|
39
|
+
printf '%s\n' '{"continue":true}'
|
|
40
|
+
exit 0
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
post_hook() {
|
|
44
|
+
local payload="$1"
|
|
45
|
+
if [[ -z "$payload" ]]; then
|
|
46
|
+
return 0
|
|
47
|
+
fi
|
|
48
|
+
curl -sS -m 2 \
|
|
49
|
+
-H "Authorization: Bearer ${API_KEY}" \
|
|
50
|
+
-H "Content-Type: application/json" \
|
|
51
|
+
-H "x-subconscious-client: copilot" \
|
|
52
|
+
-d "$payload" \
|
|
53
|
+
"${GATEWAY_URL%/}/v1/agent-hooks" >/dev/null 2>&1 || true
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
pending_path() {
|
|
57
|
+
local session_id="$1"
|
|
58
|
+
# Session ids are opaque strings; keep the filename filesystem-safe.
|
|
59
|
+
local safe
|
|
60
|
+
safe="$(printf '%s' "$session_id" | tr -c 'A-Za-z0-9._-' '_')"
|
|
61
|
+
printf '%s/%s' "$PENDING_DIR" "$safe"
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if [[ -z "$GATEWAY_URL" || -z "$API_KEY" ]]; then
|
|
65
|
+
echo "subconscious hook: missing SUBCONSCIOUS_GATEWAY_URL or SUBCONSCIOUS_API_KEY" >&2
|
|
66
|
+
fail_open
|
|
67
|
+
fi
|
|
68
|
+
|
|
69
|
+
for tool in jq curl; do
|
|
70
|
+
if ! command -v "$tool" >/dev/null 2>&1; then
|
|
71
|
+
echo "subconscious hook: ${tool} is required" >&2
|
|
72
|
+
fail_open
|
|
73
|
+
fi
|
|
74
|
+
done
|
|
75
|
+
|
|
76
|
+
INPUT="$(cat || true)"
|
|
77
|
+
if [[ -z "$INPUT" ]]; then
|
|
78
|
+
fail_open
|
|
79
|
+
fi
|
|
80
|
+
|
|
81
|
+
HOOK_EVENT="$(printf '%s' "$INPUT" | jq -r '.hook_event_name // empty' 2>/dev/null || true)"
|
|
82
|
+
case "$HOOK_EVENT" in
|
|
83
|
+
UserPromptSubmit | PreCompact) ;;
|
|
84
|
+
*) fail_open ;;
|
|
85
|
+
esac
|
|
86
|
+
|
|
87
|
+
SESSION_ID="$(printf '%s' "$INPUT" | jq -r '.session_id // empty' 2>/dev/null || true)"
|
|
88
|
+
if [[ -z "$SESSION_ID" ]]; then
|
|
89
|
+
fail_open
|
|
90
|
+
fi
|
|
91
|
+
|
|
92
|
+
if [[ "$HOOK_EVENT" == "PreCompact" ]]; then
|
|
93
|
+
TRIGGER="$(printf '%s' "$INPUT" | jq -r '.trigger // "auto"' 2>/dev/null || true)"
|
|
94
|
+
# Unique per event; wall clock alone can collide within one second.
|
|
95
|
+
NOW_MS="$(date +%s)-$$-$RANDOM"
|
|
96
|
+
PAYLOAD="$(jq -n \
|
|
97
|
+
--arg conversation_id "$SESSION_ID" \
|
|
98
|
+
--arg hook_event_name "$HOOK_EVENT" \
|
|
99
|
+
--arg trigger "$TRIGGER" \
|
|
100
|
+
--arg dedupe_key "${SESSION_ID}:start:${TRIGGER}:${NOW_MS}" \
|
|
101
|
+
'{
|
|
102
|
+
event: "conversation_compaction",
|
|
103
|
+
conversation_id: $conversation_id,
|
|
104
|
+
phase: "start",
|
|
105
|
+
hook_event_name: $hook_event_name,
|
|
106
|
+
dedupe_key: $dedupe_key,
|
|
107
|
+
metadata: { trigger: $trigger }
|
|
108
|
+
}'
|
|
109
|
+
)"
|
|
110
|
+
post_hook "$PAYLOAD"
|
|
111
|
+
|
|
112
|
+
mkdir -p "$PENDING_DIR"
|
|
113
|
+
# Marker tells the next UserPromptSubmit to close this compaction window.
|
|
114
|
+
printf '%s\n' "$NOW_MS" >"$(pending_path "$SESSION_ID")" 2>/dev/null || true
|
|
115
|
+
fail_open
|
|
116
|
+
fi
|
|
117
|
+
|
|
118
|
+
# UserPromptSubmit
|
|
119
|
+
PROMPT="$(printf '%s' "$INPUT" | jq -r '.prompt // empty' 2>/dev/null || true)"
|
|
120
|
+
CWD="$(printf '%s' "$INPUT" | jq -r '.cwd // empty' 2>/dev/null || true)"
|
|
121
|
+
WORKSPACE=""
|
|
122
|
+
if [[ -n "$CWD" ]]; then
|
|
123
|
+
WORKSPACE="$(basename "$CWD")"
|
|
124
|
+
fi
|
|
125
|
+
|
|
126
|
+
# Nothing to anchor on without a prompt.
|
|
127
|
+
if [[ -z "$PROMPT" ]]; then
|
|
128
|
+
fail_open
|
|
129
|
+
fi
|
|
130
|
+
|
|
131
|
+
ENSURE_PAYLOAD="$(jq -n \
|
|
132
|
+
--arg event "conversation_ensure" \
|
|
133
|
+
--arg conversation_id "$SESSION_ID" \
|
|
134
|
+
--arg prompt "$PROMPT" \
|
|
135
|
+
--arg workspace "$WORKSPACE" \
|
|
136
|
+
--arg hook_event_name "$HOOK_EVENT" \
|
|
137
|
+
'{
|
|
138
|
+
event: $event,
|
|
139
|
+
conversation_id: $conversation_id,
|
|
140
|
+
prompt: $prompt,
|
|
141
|
+
workspace: (if $workspace == "" then null else $workspace end),
|
|
142
|
+
hook_event_name: $hook_event_name
|
|
143
|
+
} | with_entries(select(.value != null))'
|
|
144
|
+
)"
|
|
145
|
+
|
|
146
|
+
# Response body is intentionally ignored: the gateway resolves the VS Code
|
|
147
|
+
# session id to its own UUID on every call, so there is no mapping to cache.
|
|
148
|
+
post_hook "$ENSURE_PAYLOAD"
|
|
149
|
+
|
|
150
|
+
MARKER="$(pending_path "$SESSION_ID")"
|
|
151
|
+
if [[ -f "$MARKER" ]]; then
|
|
152
|
+
START_TOKEN="$(tr -d '[:space:]' <"$MARKER" 2>/dev/null || true)"
|
|
153
|
+
rm -f "$MARKER" 2>/dev/null || true
|
|
154
|
+
NOW_MS="$(date +%s)-$$-$RANDOM"
|
|
155
|
+
END_PAYLOAD="$(jq -n \
|
|
156
|
+
--arg conversation_id "$SESSION_ID" \
|
|
157
|
+
--arg hook_event_name "$HOOK_EVENT" \
|
|
158
|
+
--arg start_token "${START_TOKEN:-}" \
|
|
159
|
+
--arg dedupe_key "${SESSION_ID}:end:${START_TOKEN:-0}:${NOW_MS}" \
|
|
160
|
+
'{
|
|
161
|
+
event: "conversation_compaction",
|
|
162
|
+
conversation_id: $conversation_id,
|
|
163
|
+
phase: "end",
|
|
164
|
+
hook_event_name: $hook_event_name,
|
|
165
|
+
dedupe_key: $dedupe_key,
|
|
166
|
+
metadata: { closes_start_token: (if $start_token == "" then null else $start_token end) }
|
|
167
|
+
| with_entries(select(.value != null))
|
|
168
|
+
}'
|
|
169
|
+
)"
|
|
170
|
+
post_hook "$END_PAYLOAD"
|
|
171
|
+
fi
|
|
172
|
+
|
|
173
|
+
fail_open
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 1,
|
|
3
|
+
"hooks": {
|
|
4
|
+
"UserPromptSubmit": [
|
|
5
|
+
{
|
|
6
|
+
"type": "command",
|
|
7
|
+
"command": "HOOK_SH_PATH",
|
|
8
|
+
"timeout": 2
|
|
9
|
+
}
|
|
10
|
+
],
|
|
11
|
+
"PreCompact": [
|
|
12
|
+
{
|
|
13
|
+
"type": "command",
|
|
14
|
+
"command": "HOOK_SH_PATH",
|
|
15
|
+
"timeout": 2
|
|
16
|
+
}
|
|
17
|
+
]
|
|
18
|
+
}
|
|
19
|
+
}
|