People lists every extension with its status dot and a search box: a
right-hand column on wide screens, /me/people on small ones. Call uses the
browser softphone when it's on, else POST /api/me/click-to-call, which
originates to the user's desk phone first (409 if none is registered).
During a call: Transfer (blind), Talk first (consult), or drag the call
card onto a person; a held call card offers Complete transfer whenever
another call is active. useSoftphone moved to its own module.
e2e m5-t3: the dot changes ~30 ms after Asterisk's hint; click-to-call
rings 174 before 172. cdp click() takes a scope (People also has
Call/Transfer buttons). trunk-test ignores the RTCP-at-teardown EPERM
error seen once.
Task: M5-T3
Generate voicemail_selfphoneng.conf (03 §7) with one mailbox per extension
that has voicemail on, turn on ARG3 in sp-sub-user, and add *97/*98 to
sp-featurecodes (which replaces the pending placeholder). Endpoints with a
mailbox get mwi_subscribe_replaces_unsolicited so phones that SUBSCRIBE for
MWI aren't refused with a 500. A new test:asterisk check leaves a message,
sees MWI on a baresip phone, plays it with *97 and sees MWI turn off.
Task: M4-T1
Dialplan: sp-emergency-dial (911, 9911) first in sp-from-ext;
sp-emergency has no class, channel-limit or DND check, dials every
enabled trunk in name order with the E911 caller ID, and raises
UserEvent SpEmergency. Static sp-emergency-alert (_X! so "0" matches)
for the ring-out.
api: SpEmergency -> emergency_events row, live emergency.call (admins
and notify users only), email via nodemailer (SMTP from settings) and
an Originate to every notify extension, each outcome recorded. Test
button runs the same with TEST in front, records and dials nothing.
GET/PATCH /api/settings (smtp password write-only, references
checked, read-only system keys), SMTP test, /api/system/warnings.
Fix: clearing a nullable setting stored SQL NULL (now JSON null).
UI: Settings page (Email, Emergency with the 06 §6 notice), user
Emergency tab, dashboard warnings banner, red 911 banner.
Verified: generator/emergency.test.ts; trunk-test.ts dials 911 from a
caller with no class to the fake provider (E911 caller ID in RPID,
SpEmergency on AMI, alert plays on baresip 101); scripts/e2e/m3-t4.mjs
(SMTP sink receives the test email, 101 rings, banner shows).
nodemailer pinned to 10.0.11 (newest release over two weeks old).
Task: M3-T4
Outbound routes CRUD, reorder and POST /test (mirrors the dialplan:
911 and internal numbers first, then the first matching enabled route,
class check, usable trunks, caller ID priority route > user > default).
The first trunk creates the default rules: toll-free first (because
1NXXNXXXXXX also matches 1800...), US/Canada, international disabled.
Generator: Asterisk orders patterns in one context by specificity, not
by file order, so each route gets its own sp-or-{id} context and
sp-outbound-routes includes them in position order (spec 03 §6.12
updated). sp-route-{id}: class check -> ss-noservice, caller ID
priority, per-trunk GROUP channel limit, failover on CHANUNAVAIL or
CONGESTION, all-circuits-busy-now when every trunk is full or off.
UI: Outbound rules page (↑/↓ reorder, pattern rows with help, ordered
trunk list, caller ID override, test box) and a Calling tab on users
(calling permission, outbound caller ID).
trunk-test.ts: 101 dials 3125550000 -> the fake provider receives
13125550000 with the DID in Remote-Party-ID (From user is the
sub-account), two-way audio; a caller without a class hears
ss-noservice. scripts/e2e/m3-t3.mjs covers the UI. With real voip.ms
still to do: whether per-call caller ID is honored (06 §4).
Task: M3-T3
DIDs CRUD (any input format, normalized; 409 on duplicates; a number
that is an emergency or default caller ID can't be removed). Shared
destination service: assertDestinationExists, describeDestination
("Ring Dan Doe (101)") and findReferences over every destination
column in 02 §1 plus the unmatched-number setting; deleting an
extension a destination points at is now a 409 naming the places.
Generator: destApp (user, external, hangup for now), sp-from-trunk-*
per enabled trunk, sp-did-exists (left out when empty) and sp-inbound
with caller-name prefix. Golden case voipms-basic. Phone numbers page
with a minimal DestinationPicker (user, outside number, hang up).
trunk-test.ts: the fake provider calls the DID with a 10-digit
Request-URI like voip.ms; baresip 101 answers, caller ID arrives
normalized, 500 packets each way on both legs. scripts/e2e/m3-t2.mjs
checks the UI → Asterisk path. Still to do with real voip.ms: the
mobile-to-DID call and recording which header carries the DID (06 §3).
Task: M3-T2
voip.ms-first trunk form with the portal checklist, POP server list
and Test button; passwords are write-only and encrypted. Registration
and IP-auth trunks render per 03 §5.3 (TCP/TLS add ;transport=). The
API refuses a server name that doesn't resolve and transports that are
off, because either makes Asterisk drop the trunk.
Live state: registration from PJSIPShowRegistrationsOutbound (re-read
on every Registry event: two sub-accounts on one server share
Username/Domain), reachability from PJSIPShowEndpoint and trunk
ContactStatus (static contacts aren't in PJSIPShowContacts). Trunk
state is admin-only. Dashboard gets a "Trunks up" card.
trunk-test.ts (in pnpm test:asterisk) runs a fake voip.ms (second
Asterisk): the good trunk registers, a wrong password is Rejected,
qualify reaches the provider; its recorded AMI drives the unit tests.
scripts/e2e/m3-t1.mjs does the same through the UI. The acceptance
check with a real voip.ms sub-account is still to do.
Tests: settle() now awaits an apply instead of a fixed sleep (flaked
under full-suite load).
Task: M3-T1