Fix Signal messages and replies

This commit is contained in:
2026-03-03 15:51:58 +00:00
parent 56c620473f
commit d6bd56dace
31 changed files with 3317 additions and 668 deletions

View File

@@ -4,30 +4,44 @@
<section class="section">
<div class="container">
<h1 class="title is-4">Command Routing</h1>
<p class="subtitle is-6">Manage command profiles, channel bindings, business-plan outputs, and translation bridges.</p>
<p class="subtitle is-6">Configure commands, channel bindings, and per-command delivery in a predictable way.</p>
{% if scope_service and scope_identifier %}
<article class="notification is-info is-light">
Scoped to this chat only: <strong>{{ scope_service }}</strong> · <code>{{ scope_identifier }}</code>
</article>
{% endif %}
<article class="box">
<h2 class="title is-6">Create Command Profile</h2>
<p class="help">Create reusable command behavior. Example: <code>#bp#</code> reply command for business-plan extraction.</p>
<p class="help">Create reusable command behavior. <code>bp set</code> and <code>bp set range</code> are fixed bp subcommands and will appear automatically.</p>
<form method="post" aria-label="Create command profile">
{% csrf_token %}
<input type="hidden" name="action" value="profile_create">
<div class="columns">
<div class="column">
<label class="label is-size-7" for="create_slug">Slug</label>
<input id="create_slug" class="input is-small" name="slug" placeholder="slug (bp)" value="bp" aria-describedby="create_slug_help">
<p id="create_slug_help" class="help">Stable command id, e.g. <code>bp</code>.</p>
{% if scope_service and scope_identifier %}
<input type="hidden" name="service" value="{{ scope_service }}">
<input type="hidden" name="identifier" value="{{ scope_identifier }}">
{% endif %}
<div class="columns is-multiline">
<div class="column is-4">
<label class="label is-size-7" for="create_command_slug">Command</label>
<div class="select is-small is-fullwidth">
<select id="create_command_slug" name="command_slug">
{% for value, label in command_choices %}
<option value="{{ value }}">{{ label }}</option>
{% endfor %}
</select>
</div>
</div>
<div class="column">
<div class="column is-4">
<label class="label is-size-7" for="create_name">Name</label>
<input id="create_name" class="input is-small" name="name" placeholder="name" value="Business Plan">
</div>
<div class="column">
<label class="label is-size-7" for="create_trigger_token">Trigger Token</label>
<input id="create_trigger_token" class="input is-small" name="trigger_token" placeholder="trigger token" value="#bp#">
<div class="column is-4">
<label class="label is-size-7" for="create_trigger_token">Primary Trigger Token</label>
<input id="create_trigger_token" class="input is-small" name="trigger_token" value="#bp#" readonly>
</div>
</div>
<label class="label is-size-7" for="create_template_text">Template Text</label>
<label class="label is-size-7" for="create_template_text">BP Template (used only by <code>bp</code> in AI mode)</label>
<textarea id="create_template_text" class="textarea is-small" name="template_text" rows="4" placeholder="Business plan template"></textarea>
<button class="button is-link is-small" style="margin-top: 0.75rem;" type="submit">Create Profile</button>
</form>
@@ -37,53 +51,33 @@
<article class="box">
<h2 class="title is-6">{{ profile.name }} ({{ profile.slug }})</h2>
<div class="content is-size-7" style="margin-bottom: 0.6rem;">
<p><strong>Flag Definitions</strong></p>
<p><strong>Help</strong></p>
<ul>
<li><strong>enabled</strong>: master on/off switch for this command profile.</li>
<li><strong>reply required</strong>: command only runs when the trigger message is sent as a reply to another message.</li>
<li><strong>exact match</strong>: message text must be exactly the trigger token (for example <code>#bp#</code>) with no extra text.</li>
<li><strong>visibility = status_in_source</strong>: post command status updates back into the source channel.</li>
<li><strong>visibility = silent</strong>: do not post status updates in the source channel.</li>
<li><strong>binding direction ingress</strong>: channels where trigger messages are accepted.</li>
<li><strong>binding direction egress</strong>: channels where command outputs are posted.</li>
<li><strong>binding direction scratchpad_mirror</strong>: scratchpad/mirror channel used for relay-only behavior.</li>
<li><strong>action extract_bp</strong>: run AI extraction to produce business plan content.</li>
<li><strong>action save_document</strong>: save/editable document and revision history.</li>
<li><strong>action post_result</strong>: fan out generated result to enabled egress bindings.</li>
<li><strong>position</strong>: execution order (lower runs first).</li>
<li><strong>Send plan to egress</strong>: posts generated plan to enabled egress bindings.</li>
<li><strong>Send status to source</strong>: posts a short confirmation message in the source chat.</li>
<li><strong>Send status to egress</strong>: posts a short confirmation to egress channels.</li>
<li><strong>Template support</strong>: only <code>bp</code> uses the template, and only in AI mode.</li>
</ul>
{% if profile.slug == "bp" %}
<p><strong>Supported Triggers (BP)</strong></p>
<ul>
<li><code>#bp#</code>: primary BP trigger (uses the standard BP extraction flow).</li>
<li><code>#bp set#</code>: deterministic no-AI set/update from reply/addendum text.</li>
<li><code>#bp set range#</code>: deterministic no-AI set/update from reply-anchor to trigger range.</li>
</ul>
{% endif %}
</div>
<form method="post" style="margin-bottom: 0.75rem;" aria-label="Update command profile {{ profile.name }}">
{% csrf_token %}
<input type="hidden" name="action" value="profile_update">
<input type="hidden" name="profile_id" value="{{ profile.id }}">
{% if scope_service and scope_identifier %}
<input type="hidden" name="service" value="{{ scope_service }}">
<input type="hidden" name="identifier" value="{{ scope_identifier }}">
{% endif %}
<div class="columns is-multiline">
<div class="column is-3">
<label class="label is-size-7" for="profile_name_{{ profile.id }}">Name</label>
<input id="profile_name_{{ profile.id }}" class="input is-small" name="name" value="{{ profile.name }}">
</div>
<div class="column is-2">
<label class="label is-size-7" for="trigger_token_{{ profile.id }}">Trigger</label>
<input id="trigger_token_{{ profile.id }}" class="input is-small" name="trigger_token" value="{{ profile.trigger_token }}">
<div class="column is-3">
<label class="label is-size-7" for="trigger_token_{{ profile.id }}">Primary Trigger</label>
<input id="trigger_token_{{ profile.id }}" class="input is-small" name="trigger_token" value="{{ profile.trigger_token }}" readonly>
</div>
<div class="column is-2">
<label class="label is-size-7" for="visibility_mode_{{ profile.id }}">Visibility</label>
<div class="select is-small is-fullwidth">
<select id="visibility_mode_{{ profile.id }}" name="visibility_mode">
<option value="status_in_source" {% if profile.visibility_mode == 'status_in_source' %}selected{% endif %}>Show Status In Source Chat</option>
<option value="silent" {% if profile.visibility_mode == 'silent' %}selected{% endif %}>Silent (No Status Message)</option>
</select>
</div>
</div>
<div class="column is-5">
<div class="column is-6">
<fieldset>
<legend class="label is-size-7">Flags</legend>
<label class="checkbox is-size-7"><input type="checkbox" name="enabled" value="1" {% if profile.enabled %}checked{% endif %}> enabled</label>
@@ -93,26 +87,148 @@
</div>
</div>
<label class="label is-size-7" for="template_text_{{ profile.id }}">BP Template</label>
<textarea id="template_text_{{ profile.id }}" class="textarea is-small" name="template_text" rows="5">{{ profile.template_text }}</textarea>
<textarea id="template_text_{{ profile.id }}" class="textarea is-small" name="template_text" rows="4">{{ profile.template_text }}</textarea>
<div class="buttons" style="margin-top: 0.6rem;">
<button class="button is-link is-small" type="submit">Save Profile</button>
</div>
</form>
<div class="columns">
<div class="columns is-variable is-5">
<div class="column">
<h3 class="title is-7">Variant Policies</h3>
<p class="help">Delivery switches control where plan/status are posted. Egress bindings define destinations.</p>
<p class="help">Turn off <strong>Save Document</strong> to run/fanout without storing a business plan artifact.</p>
<table class="table is-fullwidth is-striped is-size-7">
<thead>
<tr>
<th>Variant</th>
<th>Trigger</th>
<th>Enabled</th>
<th>Generation</th>
<th>Save Document</th>
<th>Plan -> Egress</th>
<th>Status -> Source</th>
<th>Status -> Egress</th>
<th></th>
</tr>
</thead>
<tbody>
{% for variant in profile.variant_rows %}
<tr>
<form method="post" aria-label="Update variant policy {{ variant.variant_label }} for {{ profile.name }}">
{% csrf_token %}
<input type="hidden" name="action" value="variant_policy_update">
<input type="hidden" name="profile_id" value="{{ profile.id }}">
<input type="hidden" name="variant_key" value="{{ variant.variant_key }}">
{% if scope_service and scope_identifier %}
<input type="hidden" name="service" value="{{ scope_service }}">
<input type="hidden" name="identifier" value="{{ scope_identifier }}">
{% endif %}
<td>
{{ variant.variant_label }}
{% if not variant.template_supported %}
<span class="tag is-warning is-light is-small has-text-dark">no template</span>
{% endif %}
</td>
<td><code>{{ variant.trigger_token }}</code></td>
<td><input type="checkbox" name="enabled" value="1" {% if variant.row.enabled %}checked{% endif %}></td>
<td>
<div class="select is-small">
<select name="generation_mode">
<option value="ai" {% if variant.row.generation_mode == 'ai' %}selected{% endif %}>AI</option>
<option value="verbatim" {% if variant.row.generation_mode == 'verbatim' %}selected{% endif %}>Verbatim</option>
</select>
</div>
</td>
<td><input type="checkbox" name="store_document" value="1" {% if variant.row.store_document %}checked{% endif %}></td>
<td><input type="checkbox" name="send_plan_to_egress" value="1" {% if variant.row.send_plan_to_egress %}checked{% endif %}></td>
<td><input type="checkbox" name="send_status_to_source" value="1" {% if variant.row.send_status_to_source %}checked{% endif %}></td>
<td><input type="checkbox" name="send_status_to_egress" value="1" {% if variant.row.send_status_to_egress %}checked{% endif %}></td>
<td><button class="button is-small is-link is-light" type="submit">Save</button></td>
</form>
</tr>
{% if variant.warn_verbatim_plan %}
<tr>
<td colspan="9">
<p class="help has-text-warning-dark">
Warning: <strong>{{ variant.variant_label }}</strong> is in <code>verbatim</code> mode with plan fanout enabled.
Recipients will get raw transcript-style output.
</p>
</td>
</tr>
{% endif %}
{% empty %}
<tr><td colspan="9">No variants configured.</td></tr>
{% endfor %}
</tbody>
</table>
<div class="is-flex is-justify-content-space-between is-align-items-center" style="gap: 0.75rem; margin-top: 0.75rem; flex-wrap: wrap;">
<form method="post" aria-label="Preview delivery for {{ profile.name }}">
{% csrf_token %}
<input type="hidden" name="action" value="variant_preview">
<input type="hidden" name="profile_id" value="{{ profile.id }}">
{% if scope_service and scope_identifier %}
<input type="hidden" name="service" value="{{ scope_service }}">
<input type="hidden" name="identifier" value="{{ scope_identifier }}">
{% endif %}
<button class="button is-small is-info is-light" type="submit">Dry Run Preview</button>
</form>
<form method="post" aria-label="Reset variant defaults for {{ profile.name }}">
{% csrf_token %}
<input type="hidden" name="action" value="variant_policy_reset_defaults">
<input type="hidden" name="profile_id" value="{{ profile.id }}">
{% if scope_service and scope_identifier %}
<input type="hidden" name="service" value="{{ scope_service }}">
<input type="hidden" name="identifier" value="{{ scope_identifier }}">
{% endif %}
<button class="button is-small is-warning is-light" type="submit">Reset Variant Defaults</button>
</form>
</div>
<h4 class="title is-7" style="margin-top: 0.95rem;">Effective Destinations</h4>
{% if profile.enabled_egress_bindings %}
<ul class="is-size-7">
{% for row in profile.enabled_egress_bindings %}
<li>{{ row.service }} · <code>{{ row.channel_identifier }}</code></li>
{% endfor %}
</ul>
<p class="help">{{ profile.enabled_egress_bindings|length }} enabled egress destination{{ profile.enabled_egress_bindings|length|pluralize }}.</p>
{% else %}
<article class="notification is-warning is-light is-size-7">No enabled egress destinations. Plan fanout will show sent:0.</article>
{% endif %}
{% if preview_profile_id and preview_profile_id == profile.id|stringformat:"s" %}
<article class="notification is-info is-light is-size-7" style="margin-top: 0.65rem;">
<p><strong>Dry Run Preview</strong></p>
<ul>
{% for variant in profile.variant_rows %}
<li>
{{ variant.variant_label }}: {% if variant.row.enabled %}enabled{% else %}disabled{% endif %}, mode={{ variant.row.generation_mode }},
save_document={{ variant.row.store_document }},
plan->egress={{ variant.row.send_plan_to_egress }},
status->source={{ variant.row.send_status_to_source }},
status->egress={{ variant.row.send_status_to_egress }}
</li>
{% endfor %}
</ul>
</article>
{% endif %}
</div>
<div class="column">
<h3 class="title is-7">Channel Bindings</h3>
<p class="help">A command runs only when the source channel is in <code>ingress</code>. Output is sent to all enabled <code>egress</code> bindings.</p>
<p class="help">Ingress accepts triggers. Egress receives plan/status fanout if enabled in variant policy.</p>
<table class="table is-fullwidth is-striped is-size-7">
<thead>
<tr><th scope="col">Direction</th><th scope="col">Service</th><th scope="col">Channel</th><th scope="col">Actions</th></tr>
</thead>
<tbody>
{% for binding in profile.channel_bindings.all %}
{% for binding in profile.visible_bindings %}
<tr>
<td>
{% if binding.direction == "ingress" %}Ingress (Accept Triggers)
{% elif binding.direction == "egress" %}Egress (Post Results)
{% elif binding.direction == "egress" %}Egress (Delivery Destination)
{% else %}Scratchpad Mirror
{% endif %}
</td>
@@ -123,6 +239,10 @@
{% csrf_token %}
<input type="hidden" name="action" value="binding_delete">
<input type="hidden" name="binding_id" value="{{ binding.id }}">
{% if scope_service and scope_identifier %}
<input type="hidden" name="service" value="{{ scope_service }}">
<input type="hidden" name="identifier" value="{{ scope_identifier }}">
{% endif %}
<button class="button is-danger is-light is-small" type="submit">Delete</button>
</form>
</td>
@@ -136,6 +256,10 @@
{% csrf_token %}
<input type="hidden" name="action" value="binding_create">
<input type="hidden" name="profile_id" value="{{ profile.id }}">
{% if scope_service and scope_identifier %}
<input type="hidden" name="service" value="{{ scope_service }}">
<input type="hidden" name="identifier" value="{{ scope_identifier }}">
{% endif %}
<div class="columns">
<div class="column">
<label class="label is-size-7" for="binding_direction_{{ profile.id }}">Direction</label>
@@ -144,7 +268,7 @@
{% for value in directions %}
<option value="{{ value }}">
{% if value == "ingress" %}Ingress (Accept Triggers)
{% elif value == "egress" %}Egress (Post Results)
{% elif value == "egress" %}Egress (Delivery Destination)
{% else %}Scratchpad Mirror
{% endif %}
</option>
@@ -155,16 +279,19 @@
<div class="column">
<label class="label is-size-7" for="binding_service_{{ profile.id }}">Service</label>
<div class="select is-small is-fullwidth">
<select id="binding_service_{{ profile.id }}" name="service">
<select id="binding_service_{{ profile.id }}" name="service" {% if scope_service %}disabled{% endif %}>
{% for value in channel_services %}
<option value="{{ value }}">{{ value }}</option>
<option value="{{ value }}" {% if value == scope_service %}selected{% endif %}>{{ value }}</option>
{% endfor %}
</select>
</div>
{% if scope_service %}
<input type="hidden" name="service" value="{{ scope_service }}">
{% endif %}
</div>
<div class="column">
<label class="label is-size-7" for="binding_channel_identifier_{{ profile.id }}">Channel Identifier</label>
<input id="binding_channel_identifier_{{ profile.id }}" class="input is-small" name="channel_identifier" placeholder="channel identifier">
<input id="binding_channel_identifier_{{ profile.id }}" class="input is-small" name="channel_identifier" placeholder="channel identifier" value="{{ scope_identifier }}" {% if scope_identifier %}readonly{% endif %}>
</div>
<div class="column is-narrow">
<button class="button is-link is-small" type="submit">Add</button>
@@ -172,13 +299,15 @@
</div>
</form>
</div>
</div>
<div class="columns">
<div class="column">
<h3 class="title is-7">Actions</h3>
<p class="help">Enable/disable each step and set execution order with <code>position</code>.</p>
<p class="help">Enable/disable each step and use the reorder capsule to change execution order.</p>
<table class="table is-fullwidth is-striped is-size-7">
<thead>
<tr><th scope="col">Type</th><th scope="col">Enabled</th><th scope="col">Order</th><th scope="col">Actions</th></tr>
<tr><th scope="col">Type</th><th scope="col">Enabled</th><th scope="col">Reorder</th><th scope="col">Actions</th></tr>
</thead>
<tbody>
{% for action_row in profile.actions.all %}
@@ -191,28 +320,41 @@
{% endif %}
</td>
<td>{{ action_row.enabled }}</td>
<td>{{ forloop.counter }}</td>
<td>
<div class="buttons are-small" style="margin-bottom: 0.35rem;">
<form method="post" style="display:inline;" aria-label="Move action {{ action_row.action_type }} up for {{ profile.name }}">
<span class="command-order-capsule">
<form method="post" class="command-order-capsule-form" aria-label="Move action {{ action_row.action_type }} up for {{ profile.name }}">
{% csrf_token %}
<input type="hidden" name="action" value="action_move">
<input type="hidden" name="command_action_id" value="{{ action_row.id }}">
<input type="hidden" name="direction" value="up">
<button class="button is-light" type="submit" {% if forloop.first %}disabled{% endif %} aria-label="Move up">Up</button>
{% if scope_service and scope_identifier %}
<input type="hidden" name="service" value="{{ scope_service }}">
<input type="hidden" name="identifier" value="{{ scope_identifier }}">
{% endif %}
<button class="button is-small is-light command-order-btn" type="submit" {% if forloop.first %}disabled{% endif %} aria-label="Move up"></button>
</form>
<form method="post" style="display:inline;" aria-label="Move action {{ action_row.action_type }} down for {{ profile.name }}">
<form method="post" class="command-order-capsule-form" aria-label="Move action {{ action_row.action_type }} down for {{ profile.name }}">
{% csrf_token %}
<input type="hidden" name="action" value="action_move">
<input type="hidden" name="command_action_id" value="{{ action_row.id }}">
<input type="hidden" name="direction" value="down">
<button class="button is-light" type="submit" {% if forloop.last %}disabled{% endif %} aria-label="Move down">Down</button>
{% if scope_service and scope_identifier %}
<input type="hidden" name="service" value="{{ scope_service }}">
<input type="hidden" name="identifier" value="{{ scope_identifier }}">
{% endif %}
<button class="button is-small is-light command-order-btn" type="submit" {% if forloop.last %}disabled{% endif %} aria-label="Move down"></button>
</form>
</div>
</span>
</td>
<td>
<form method="post" aria-label="Update action {{ action_row.action_type }} for {{ profile.name }}">
{% csrf_token %}
<input type="hidden" name="action" value="action_update">
<input type="hidden" name="command_action_id" value="{{ action_row.id }}">
{% if scope_service and scope_identifier %}
<input type="hidden" name="service" value="{{ scope_service }}">
<input type="hidden" name="identifier" value="{{ scope_identifier }}">
{% endif %}
<label class="checkbox is-size-7"><input type="checkbox" name="enabled" value="1" {% if action_row.enabled %}checked{% endif %}> enabled</label>
<button class="button is-link is-light is-small" type="submit">Save</button>
</form>
@@ -230,6 +372,10 @@
{% csrf_token %}
<input type="hidden" name="action" value="profile_delete">
<input type="hidden" name="profile_id" value="{{ profile.id }}">
{% if scope_service and scope_identifier %}
<input type="hidden" name="service" value="{{ scope_service }}">
<input type="hidden" name="identifier" value="{{ scope_identifier }}">
{% endif %}
<button class="button is-danger is-light is-small" type="submit" aria-label="Delete profile {{ profile.name }}">Delete Profile</button>
</form>
</article>
@@ -261,4 +407,34 @@
</div>
</section>
<style>
.command-order-capsule {
display: inline-flex;
flex-direction: column;
border: 1px solid #dbdbdb;
border-radius: 6px;
overflow: hidden;
white-space: nowrap;
flex-wrap: nowrap;
width: 2.15rem;
min-width: 2.15rem;
}
.command-order-capsule-form {
margin: 0;
line-height: 1;
}
.command-order-btn {
border: 0;
border-radius: 0;
width: 100%;
min-width: 2.15rem;
height: 1.35rem;
padding: 0;
font-size: 0.72rem;
line-height: 1;
}
.command-order-capsule-form + .command-order-capsule-form .command-order-btn {
border-top: 1px solid #dbdbdb;
}
</style>
{% endblock %}