@indigoai-us/hq-cli 5.108.1 → 5.108.2
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/CHANGELOG.md +77 -0
- package/assets/mesh-daemon/README.md +23 -0
- package/assets/mesh-daemon/launchd.plist.template +33 -0
- package/assets/mesh-daemon/systemd.system.service.template +27 -0
- package/assets/mesh-daemon/systemd.user.service.template +18 -0
- package/assets/scaffold/core/scripts/lib/work-mesh-enqueue.sh +267 -0
- package/dist/commands/core.js +5 -4
- package/dist/commands/mesh.d.ts +15 -0
- package/dist/commands/mesh.js +732 -4
- package/dist/commands/search.d.ts +23 -0
- package/dist/commands/search.js +42 -1
- package/dist/lib/doctor/checks/work-context.d.ts +23 -0
- package/dist/lib/doctor/checks/work-context.js +195 -0
- package/dist/lib/doctor/registry.js +4 -0
- package/dist/lib/mesh/api.d.ts +18 -0
- package/dist/lib/mesh/api.js +55 -0
- package/dist/lib/mesh/cache.js +35 -4
- package/dist/lib/mesh/client.d.ts +118 -0
- package/dist/lib/mesh/client.js +268 -0
- package/dist/lib/mesh/live/backoff.d.ts +16 -0
- package/dist/lib/mesh/live/backoff.js +20 -0
- package/dist/lib/mesh/live/daemon/board-refresh.d.ts +49 -0
- package/dist/lib/mesh/live/daemon/board-refresh.js +185 -0
- package/dist/lib/mesh/live/daemon/credentials.d.ts +71 -0
- package/dist/lib/mesh/live/daemon/credentials.js +227 -0
- package/dist/lib/mesh/live/daemon/doctor.d.ts +37 -0
- package/dist/lib/mesh/live/daemon/doctor.js +139 -0
- package/dist/lib/mesh/live/daemon/index.d.ts +24 -0
- package/dist/lib/mesh/live/daemon/index.js +14 -0
- package/dist/lib/mesh/live/daemon/install.d.ts +83 -0
- package/dist/lib/mesh/live/daemon/install.js +330 -0
- package/dist/lib/mesh/live/daemon/log.d.ts +11 -0
- package/dist/lib/mesh/live/daemon/log.js +79 -0
- package/dist/lib/mesh/live/daemon/paths.d.ts +18 -0
- package/dist/lib/mesh/live/daemon/paths.js +31 -0
- package/dist/lib/mesh/live/daemon/pid-lock.d.ts +48 -0
- package/dist/lib/mesh/live/daemon/pid-lock.js +134 -0
- package/dist/lib/mesh/live/daemon/presence.d.ts +101 -0
- package/dist/lib/mesh/live/daemon/presence.js +250 -0
- package/dist/lib/mesh/live/daemon/presign.d.ts +18 -0
- package/dist/lib/mesh/live/daemon/presign.js +65 -0
- package/dist/lib/mesh/live/daemon/run.d.ts +74 -0
- package/dist/lib/mesh/live/daemon/run.js +400 -0
- package/dist/lib/mesh/live/daemon/state.d.ts +24 -0
- package/dist/lib/mesh/live/daemon/state.js +57 -0
- package/dist/lib/mesh/live/daemon/transcript-watch.d.ts +170 -0
- package/dist/lib/mesh/live/daemon/transcript-watch.js +520 -0
- package/dist/lib/mesh/live/enqueue.d.ts +48 -0
- package/dist/lib/mesh/live/enqueue.js +100 -0
- package/dist/lib/mesh/live/flush.d.ts +56 -0
- package/dist/lib/mesh/live/flush.js +360 -0
- package/dist/lib/mesh/live/format-spool-line.d.ts +42 -0
- package/dist/lib/mesh/live/format-spool-line.js +106 -0
- package/dist/lib/mesh/live/index.d.ts +22 -0
- package/dist/lib/mesh/live/index.js +16 -0
- package/dist/lib/mesh/live/paths.d.ts +13 -0
- package/dist/lib/mesh/live/paths.js +30 -0
- package/dist/lib/mesh/live/session-events-client.d.ts +58 -0
- package/dist/lib/mesh/live/session-events-client.js +120 -0
- package/dist/lib/mesh/live/session-identity.d.ts +39 -0
- package/dist/lib/mesh/live/session-identity.js +91 -0
- package/dist/lib/mesh/live/spool.d.ts +52 -0
- package/dist/lib/mesh/live/spool.js +193 -0
- package/dist/lib/mesh/live/ulid.d.ts +12 -0
- package/dist/lib/mesh/live/ulid.js +39 -0
- package/dist/lib/mesh/live/validate-session-event.d.ts +39 -0
- package/dist/lib/mesh/live/validate-session-event.js +128 -0
- package/dist/lib/search-index/index.d.ts +45 -0
- package/dist/lib/search-index/index.js +76 -0
- package/dist/lib/work-context/atomic.d.ts +18 -0
- package/dist/lib/work-context/atomic.js +132 -0
- package/dist/lib/work-context/company.d.ts +103 -0
- package/dist/lib/work-context/company.js +274 -0
- package/dist/lib/work-context/config.d.ts +59 -0
- package/dist/lib/work-context/config.js +172 -0
- package/dist/lib/work-context/contract.d.ts +81 -0
- package/dist/lib/work-context/contract.js +65 -0
- package/dist/lib/work-context/errors.d.ts +27 -0
- package/dist/lib/work-context/errors.js +46 -0
- package/dist/lib/work-context/index.d.ts +23 -0
- package/dist/lib/work-context/index.js +20 -0
- package/dist/lib/work-context/migrate.d.ts +84 -0
- package/dist/lib/work-context/migrate.js +287 -0
- package/dist/lib/work-context/organize.d.ts +130 -0
- package/dist/lib/work-context/organize.js +471 -0
- package/dist/lib/work-context/outbox.d.ts +97 -0
- package/dist/lib/work-context/outbox.js +357 -0
- package/dist/lib/work-context/paths.d.ts +14 -0
- package/dist/lib/work-context/paths.js +39 -0
- package/dist/lib/work-context/project.d.ts +78 -0
- package/dist/lib/work-context/project.js +247 -0
- package/dist/lib/work-context/reconcile.d.ts +85 -0
- package/dist/lib/work-context/reconcile.js +651 -0
- package/dist/lib/work-context/repo-remote.d.ts +43 -0
- package/dist/lib/work-context/repo-remote.js +200 -0
- package/dist/lib/work-context/state.d.ts +112 -0
- package/dist/lib/work-context/state.js +162 -0
- package/dist/main.js +40 -3
- package/dist/utils/cognito-session.d.ts +85 -9
- package/dist/utils/cognito-session.js +216 -9
- package/dist/utils/qmd-model-download-error.d.ts +27 -0
- package/dist/utils/qmd-model-download-error.js +114 -0
- package/dist/utils/qmd-query-document-error.d.ts +23 -0
- package/dist/utils/qmd-query-document-error.js +117 -0
- package/dist/utils/sentry-fingerprint.js +2 -0
- package/package.json +5 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,83 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [5.108.2] — 2026-09-04
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- Hybrid search (`hq search --mode hybrid`) no longer fails on a multi-line
|
|
10
|
+
query. A whole multi-line message (for example a Slack message forwarded as
|
|
11
|
+
the query) is now collapsed to a single line before it reaches the search
|
|
12
|
+
engine, and a genuinely malformed structured query prints a short "put your
|
|
13
|
+
terms on one line" hint instead of crashing (HQ-CLI-1B).
|
|
14
|
+
- An interrupted local search-model download now reports an actionable "re-run,
|
|
15
|
+
it resumes" message instead of crashing, so two searches racing to download
|
|
16
|
+
the same model on one machine no longer file a crash (HQ-CLI-1B).
|
|
17
|
+
- Machine-credential auth is opt-in via `HQ_MACHINE_CREDS_FILE` / the daemon
|
|
18
|
+
(`{ tokenSource: "machine" }`); the mere presence of
|
|
19
|
+
`~/.hq-agent/machine-creds.json` no longer flips other CLI commands onto
|
|
20
|
+
Cognito machine-token minting.
|
|
21
|
+
|
|
22
|
+
### Added
|
|
23
|
+
|
|
24
|
+
- Machine-credential auth for the mesh daemon on fleet agent boxes: when
|
|
25
|
+
`HQ_MACHINE_CREDS_FILE` is set (or the daemon passes
|
|
26
|
+
`{ tokenSource: "machine" }`), `ensureCognitoToken` mints/refreshes via Cognito
|
|
27
|
+
`USER_PASSWORD_AUTH`, caches under the daemon state dir (never a person's
|
|
28
|
+
`~/.hq/cognito-tokens.json`), and `HQ_AGENT_IDENTITY_FILE` supplies
|
|
29
|
+
`companyUid` as trusted_explicit company scope; `hq mesh daemon doctor`
|
|
30
|
+
reports token source and actor kind.
|
|
31
|
+
- Work Mesh Live session spool: `hq mesh session
|
|
32
|
+
start|turn-start|turn-end|end|task-status|blocked|note --enqueue` appends
|
|
33
|
+
one event line under `~/.hq/work-mesh/` with no network I/O, so hooks stay
|
|
34
|
+
fast. `hq mesh session flush` claims the spool and held queue by rename,
|
|
35
|
+
holds events that still need company context, drops untracked sessions,
|
|
36
|
+
dead-letters non-retryable failures, and POSTs batches of up to 100 to the
|
|
37
|
+
vault API (default base `https://hqapi.hq.computer`). A matching
|
|
38
|
+
printf-only shell helper ships at
|
|
39
|
+
`assets/scaffold/core/scripts/lib/work-mesh-enqueue.sh` for hq-core hooks.
|
|
40
|
+
- Work context: `hq mesh context reconcile`, `default get|set|clear`,
|
|
41
|
+
`untracked`, `organize`, and `correct`, plus `hq mesh session status --company`.
|
|
42
|
+
Company resolution follows a fixed precedence (trusted explicit → existing
|
|
43
|
+
scope → session meta → device default → deterministic cwd/remote), gates on
|
|
44
|
+
conflict without tenant I/O, asks once for project/task after work signals,
|
|
45
|
+
and trusted or deterministic evidence auto-binds without prompting. Device
|
|
46
|
+
default company set validates current membership and ships dark behind
|
|
47
|
+
`DEFAULT_COMPANY_LOCKED` until `GET /v1/work-mesh/capabilities` reports
|
|
48
|
+
`migration: true` for every company the caller belongs to.
|
|
49
|
+
`hq mesh context correct` is the explicit, receipted cross-company migrate
|
|
50
|
+
path (confirmation gate, `migration_pending` until destination receipt).
|
|
51
|
+
- Resident mesh daemon: `hq mesh daemon run|install|uninstall|status|doctor`.
|
|
52
|
+
Publishes MQTT presence with keepalive 15 s and retained online (offline is
|
|
53
|
+
server-only via IoT lifecycle), drains the spool and outbox, and installs as
|
|
54
|
+
LaunchAgent `ai.getindigo.hq-mesh-daemon` on macOS or a systemd user unit on
|
|
55
|
+
Linux (`--system-user` writes a system-unit fallback for agent boxes).
|
|
56
|
+
`hq doctor` gained a `work-context` check family covering sessions, outbox,
|
|
57
|
+
spool/held/dead-letter, and daemon health.
|
|
58
|
+
- Transcript-watch fallback for hosts without hooks: the daemon enumerates
|
|
59
|
+
Claude/Codex transcript paths every 15 s with readdir+stat only (mtime),
|
|
60
|
+
never opens transcript bytes, and synthesizes
|
|
61
|
+
session_start/turn_end/session_end when hooks are absent.
|
|
62
|
+
- Session-event schema copy (verbatim from hq-pro) with Ajv draft 2020-12
|
|
63
|
+
validation, a fixture corpus, and a cross-repo SHA-256 checksum test. New
|
|
64
|
+
dependencies: `ajv`, and `mqtt` pinned at 5.15.2.
|
|
65
|
+
|
|
66
|
+
### Changed
|
|
67
|
+
|
|
68
|
+
- hq-core hooks from the matching Work Mesh Live release require hq-cli
|
|
69
|
+
5.109.0 or newer (spool enqueue format and flush semantics).
|
|
70
|
+
- Mesh daemon presence: offline is now **server-only**. Graceful stop
|
|
71
|
+
flushes once more and issues a clean MQTT `DISCONNECT` (broker lifecycle
|
|
72
|
+
event); the client no longer publishes retained offline. Retained online
|
|
73
|
+
on connect/renew is unchanged.
|
|
74
|
+
|
|
75
|
+
### Fixed
|
|
76
|
+
|
|
77
|
+
- Session-event flush now accounts 2xx batch responses honestly: parses
|
|
78
|
+
`accepted` / `duplicates` / `rejected[{eventId,code}]`, dead-letters each
|
|
79
|
+
rejected event with its code, and retains any eventId not accounted for
|
|
80
|
+
on the spool instead of deleting the whole chunk as posted.
|
|
81
|
+
|
|
5
82
|
## [5.108.1] — 2026-09-04
|
|
6
83
|
|
|
7
84
|
## [5.108.0] — 2026-09-03
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# HQ mesh daemon service templates
|
|
2
|
+
|
|
3
|
+
Rendered at install time by `hq mesh daemon install` with the resolved `hq`
|
|
4
|
+
and `node` binaries written into the unit.
|
|
5
|
+
|
|
6
|
+
| Platform | Unit | Label / name |
|
|
7
|
+
|----------|------|----------------|
|
|
8
|
+
| macOS | LaunchAgent | `ai.getindigo.hq-mesh-daemon` |
|
|
9
|
+
| Linux (user) | systemd user unit | `hq-mesh-daemon.service` |
|
|
10
|
+
| Linux (agent box) | systemd system unit | `hq-mesh-daemon.service` as `ec2-user` |
|
|
11
|
+
|
|
12
|
+
## Agent-box fallback
|
|
13
|
+
|
|
14
|
+
When no user session bus exists (typical EC2 agent boxes):
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
hq mesh daemon install --system-user ec2-user
|
|
18
|
+
sudo cp ~/.hq/work-mesh/daemon/hq-mesh-daemon.service /etc/systemd/system/
|
|
19
|
+
sudo systemctl daemon-reload
|
|
20
|
+
sudo systemctl enable --now hq-mesh-daemon.service
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Logs: `~/.hq/work-mesh/daemon/log` (rotated at 5MB by the daemon process).
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
3
|
+
<plist version="1.0">
|
|
4
|
+
<dict>
|
|
5
|
+
<key>Label</key>
|
|
6
|
+
<string>ai.getindigo.hq-mesh-daemon</string>
|
|
7
|
+
<key>ProgramArguments</key>
|
|
8
|
+
<array>
|
|
9
|
+
<string>{{NODE_BINARY}}</string>
|
|
10
|
+
<string>{{HQ_BINARY}}</string>
|
|
11
|
+
<string>mesh</string>
|
|
12
|
+
<string>daemon</string>
|
|
13
|
+
<string>run</string>
|
|
14
|
+
</array>
|
|
15
|
+
<key>WorkingDirectory</key>
|
|
16
|
+
<string>{{DAEMON_DIR}}</string>
|
|
17
|
+
<key>RunAtLoad</key>
|
|
18
|
+
<true/>
|
|
19
|
+
<key>KeepAlive</key>
|
|
20
|
+
<true/>
|
|
21
|
+
<key>StandardOutPath</key>
|
|
22
|
+
<string>{{LOG_PATH}}</string>
|
|
23
|
+
<key>StandardErrorPath</key>
|
|
24
|
+
<string>{{LOG_PATH}}</string>
|
|
25
|
+
<key>EnvironmentVariables</key>
|
|
26
|
+
<dict>
|
|
27
|
+
<key>HOME</key>
|
|
28
|
+
<string>{{HOME}}</string>
|
|
29
|
+
<key>PATH</key>
|
|
30
|
+
<string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin</string>
|
|
31
|
+
</dict>
|
|
32
|
+
</dict>
|
|
33
|
+
</plist>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
[Unit]
|
|
2
|
+
Description=HQ Work Mesh Live daemon (system, user={{SYSTEM_USER}})
|
|
3
|
+
After=network-online.target
|
|
4
|
+
Wants=network-online.target
|
|
5
|
+
|
|
6
|
+
# Fallback for agent boxes where no user session bus exists:
|
|
7
|
+
# hq mesh daemon install --system-user {{SYSTEM_USER}}
|
|
8
|
+
# sudo cp ~/.hq/work-mesh/daemon/hq-mesh-daemon.service /etc/systemd/system/
|
|
9
|
+
# sudo systemctl daemon-reload && sudo systemctl enable --now hq-mesh-daemon.service
|
|
10
|
+
|
|
11
|
+
[Service]
|
|
12
|
+
Type=simple
|
|
13
|
+
User={{SYSTEM_USER}}
|
|
14
|
+
Group={{SYSTEM_USER}}
|
|
15
|
+
ExecStart={{NODE_BINARY}} {{HQ_BINARY}} mesh daemon run
|
|
16
|
+
WorkingDirectory=/home/{{SYSTEM_USER}}/.hq/work-mesh/daemon
|
|
17
|
+
Restart=on-failure
|
|
18
|
+
RestartSec=5
|
|
19
|
+
Environment=HOME=/home/{{SYSTEM_USER}}
|
|
20
|
+
Environment=PATH=/usr/local/bin:/usr/bin:/bin
|
|
21
|
+
Environment=HQ_MACHINE_CREDS_FILE=/home/{{SYSTEM_USER}}/.hq-agent/machine-creds.json
|
|
22
|
+
Environment=HQ_AGENT_IDENTITY_FILE=/var/lib/hq-agent/identity.json
|
|
23
|
+
StandardOutput=append:/home/{{SYSTEM_USER}}/.hq/work-mesh/daemon/log
|
|
24
|
+
StandardError=append:/home/{{SYSTEM_USER}}/.hq/work-mesh/daemon/log
|
|
25
|
+
|
|
26
|
+
[Install]
|
|
27
|
+
WantedBy=multi-user.target
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
[Unit]
|
|
2
|
+
Description=HQ Work Mesh Live daemon
|
|
3
|
+
After=network-online.target
|
|
4
|
+
Wants=network-online.target
|
|
5
|
+
|
|
6
|
+
[Service]
|
|
7
|
+
Type=simple
|
|
8
|
+
ExecStart={{NODE_BINARY}} {{HQ_BINARY}} mesh daemon run
|
|
9
|
+
WorkingDirectory={{DAEMON_DIR}}
|
|
10
|
+
Restart=on-failure
|
|
11
|
+
RestartSec=5
|
|
12
|
+
Environment=HOME={{HOME}}
|
|
13
|
+
Environment=PATH=/usr/local/bin:/usr/bin:/bin
|
|
14
|
+
StandardOutput=append:{{LOG_PATH}}
|
|
15
|
+
StandardError=append:{{LOG_PATH}}
|
|
16
|
+
|
|
17
|
+
[Install]
|
|
18
|
+
WantedBy=default.target
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# hq-core: public
|
|
3
|
+
# work-mesh-enqueue.sh — append one Work Mesh Live spool line with printf only.
|
|
4
|
+
#
|
|
5
|
+
# Sourced by hooks. Never execute directly.
|
|
6
|
+
# No jq. No node. One write under PIPE_BUF via printf >> spool (O_APPEND).
|
|
7
|
+
#
|
|
8
|
+
# Line format is byte-identical to hq-cli formatSpoolLine() for the same inputs
|
|
9
|
+
# (fixed key order, compact JSON). See src/lib/mesh/live/format-spool-line.ts.
|
|
10
|
+
#
|
|
11
|
+
# Usage:
|
|
12
|
+
# source "$HQ_ROOT/core/scripts/lib/work-mesh-enqueue.sh"
|
|
13
|
+
# work_mesh_enqueue \
|
|
14
|
+
# --kind session_start \
|
|
15
|
+
# --session-id "$sid" \
|
|
16
|
+
# --harness claude-code \
|
|
17
|
+
# --adapter-version 1.0.0 \
|
|
18
|
+
# --seq 1 \
|
|
19
|
+
# [--event-id ULID] [--at ISO8601] [--runtime-version V] \
|
|
20
|
+
# [--task-id T] [--status S] [--reason R] [--summary S] \
|
|
21
|
+
# [--cwd P] [--hq-root P] [--company-slug S] [--project P] [--task T] \
|
|
22
|
+
# [--tool-writes N] [--spool PATH]
|
|
23
|
+
|
|
24
|
+
# Crockford base32 alphabet (ULID) — no I L O U
|
|
25
|
+
_WORK_MESH_CROCKFORD='0123456789ABCDEFGHJKMNPQRSTVWXYZ'
|
|
26
|
+
|
|
27
|
+
# work_mesh_json_quote <string>
|
|
28
|
+
# Set REPLY to a JSON string value (with surrounding quotes). No subshell.
|
|
29
|
+
# Fast path for values without \ " or controls (typical session metadata).
|
|
30
|
+
work_mesh_json_quote() {
|
|
31
|
+
local s=$1
|
|
32
|
+
case "$s" in
|
|
33
|
+
*'\\'*|*'\"'*|*$'\n'*|*$'\r'*|*$'\t'*)
|
|
34
|
+
s=${s//'\'/'\\'}
|
|
35
|
+
s=${s//'"'/'\"'}
|
|
36
|
+
s=${s//$'\n'/'\n'}
|
|
37
|
+
s=${s//$'\r'/'\r'}
|
|
38
|
+
s=${s//$'\t'/'\t'}
|
|
39
|
+
REPLY="\"${s}\""
|
|
40
|
+
;;
|
|
41
|
+
*)
|
|
42
|
+
REPLY="\"${s}\""
|
|
43
|
+
;;
|
|
44
|
+
esac
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
# work_mesh_json_escape <string>
|
|
48
|
+
# Print a JSON string value (compat wrapper for tests / callers).
|
|
49
|
+
work_mesh_json_escape() {
|
|
50
|
+
work_mesh_json_quote "$1"
|
|
51
|
+
printf '%s' "$REPLY"
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
# Cached: bash 5.1+ has $SRANDOM (re-read yields new entropy).
|
|
55
|
+
_WORK_MESH_HAS_SRANDOM=0
|
|
56
|
+
if [[ ${BASH_VERSINFO[0]} -gt 5 || ( ${BASH_VERSINFO[0]} -eq 5 && ${BASH_VERSINFO[1]} -ge 1 ) ]]; then
|
|
57
|
+
_WORK_MESH_HAS_SRANDOM=1
|
|
58
|
+
fi
|
|
59
|
+
|
|
60
|
+
# work_mesh_ulid
|
|
61
|
+
# Print a 26-char Crockford ULID (48-bit ms time + 80-bit entropy).
|
|
62
|
+
# Prefer bash 5+ builtins ($EPOCHREALTIME, $SRANDOM) to stay under the
|
|
63
|
+
# 20ms hook budget; fall back to date/dd/od on older bash.
|
|
64
|
+
# Also sets REPLY (avoids command-substitution fork when callers use it).
|
|
65
|
+
work_mesh_ulid() {
|
|
66
|
+
local alphabet=$_WORK_MESH_CROCKFORD
|
|
67
|
+
local now_ms time_part="" rand_part=""
|
|
68
|
+
local -i i rem val
|
|
69
|
+
|
|
70
|
+
if [[ -n "${EPOCHREALTIME-}" ]]; then
|
|
71
|
+
local sec=${EPOCHREALTIME%.*}
|
|
72
|
+
local frac=${EPOCHREALTIME#*.}000
|
|
73
|
+
frac=${frac:0:3}
|
|
74
|
+
now_ms=$((10#$sec * 1000 + 10#$frac))
|
|
75
|
+
else
|
|
76
|
+
now_ms=$(date +%s%3N 2>/dev/null || true)
|
|
77
|
+
case "$now_ms" in
|
|
78
|
+
""|*[!0-9]*)
|
|
79
|
+
now_ms=$(date +%s)
|
|
80
|
+
now_ms=$((now_ms * 1000))
|
|
81
|
+
;;
|
|
82
|
+
esac
|
|
83
|
+
fi
|
|
84
|
+
|
|
85
|
+
# 48-bit timestamp → 10 Crockford chars (fits in signed 64-bit).
|
|
86
|
+
val=$((now_ms & 0xFFFFFFFFFFFF))
|
|
87
|
+
for ((i = 0; i < 10; i++)); do
|
|
88
|
+
rem=$((val & 31))
|
|
89
|
+
time_part="${alphabet:rem:1}${time_part}"
|
|
90
|
+
val=$((val >> 5))
|
|
91
|
+
done
|
|
92
|
+
|
|
93
|
+
local -a b=()
|
|
94
|
+
if [ "$_WORK_MESH_HAS_SRANDOM" -eq 1 ]; then
|
|
95
|
+
for ((i = 0; i < 10; i++)); do
|
|
96
|
+
b+=($((SRANDOM & 255)))
|
|
97
|
+
done
|
|
98
|
+
else
|
|
99
|
+
local bytes
|
|
100
|
+
bytes=$(dd if=/dev/urandom bs=10 count=1 2>/dev/null | od -An -tu1 | tr -s ' ' '\n' | grep -E '^[0-9]+$' | head -n 10 | tr '\n' ' ')
|
|
101
|
+
# shellcheck disable=SC2206
|
|
102
|
+
local -a bb=($bytes)
|
|
103
|
+
b=("${bb[@]}")
|
|
104
|
+
while [ "${#b[@]}" -lt 10 ]; do
|
|
105
|
+
b+=($((RANDOM % 256)))
|
|
106
|
+
done
|
|
107
|
+
fi
|
|
108
|
+
|
|
109
|
+
# Encode 80 bits MSB-first as 16×5-bit Crockford chars without a 80-bit int:
|
|
110
|
+
# maintain a bit buffer that never exceeds ~16 bits of pending state.
|
|
111
|
+
local -i bitbuf=0 bits=0 bi=0 byte
|
|
112
|
+
rand_part=
|
|
113
|
+
for ((i = 0; i < 16; i++)); do
|
|
114
|
+
while [ "$bits" -lt 5 ]; do
|
|
115
|
+
if [ "$bi" -lt 10 ]; then
|
|
116
|
+
byte=${b[bi]}
|
|
117
|
+
bi=$((bi + 1))
|
|
118
|
+
bitbuf=$(((bitbuf << 8) | (byte & 255)))
|
|
119
|
+
bits=$((bits + 8))
|
|
120
|
+
else
|
|
121
|
+
bitbuf=$((bitbuf << 5))
|
|
122
|
+
bits=$((bits + 5))
|
|
123
|
+
fi
|
|
124
|
+
done
|
|
125
|
+
rem=$(((bitbuf >> (bits - 5)) & 31))
|
|
126
|
+
bits=$((bits - 5))
|
|
127
|
+
rand_part+="${alphabet:rem:1}"
|
|
128
|
+
done
|
|
129
|
+
|
|
130
|
+
REPLY="${time_part}${rand_part}"
|
|
131
|
+
printf '%s' "$REPLY"
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
# work_mesh_ensure_spool <path>
|
|
135
|
+
# Create parent dir 0700 and spool file 0600 when missing.
|
|
136
|
+
# Hot path: existing spool returns immediately (no chmod/stat storms).
|
|
137
|
+
work_mesh_ensure_spool() {
|
|
138
|
+
local spool=$1 dir
|
|
139
|
+
if [ -f "$spool" ]; then
|
|
140
|
+
return 0
|
|
141
|
+
fi
|
|
142
|
+
dir=$(dirname -- "$spool")
|
|
143
|
+
mkdir -p -- "$dir" 2>/dev/null || true
|
|
144
|
+
chmod 700 -- "$dir" 2>/dev/null || true
|
|
145
|
+
(umask 077; printf '' >"$spool") || return 1
|
|
146
|
+
chmod 600 -- "$spool" 2>/dev/null || true
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
# work_mesh_enqueue [options]
|
|
150
|
+
# Build one compact JSON line and append with a single printf write.
|
|
151
|
+
work_mesh_enqueue() {
|
|
152
|
+
local kind="" session_id="" harness="" adapter_version="" runtime_version=""
|
|
153
|
+
local at="" seq="" event_id="" task_id="" status="" reason="" summary=""
|
|
154
|
+
local cwd="" hq_root="" company_slug="" project="" task="" tool_writes=""
|
|
155
|
+
local spool=${WORK_MESH_SPOOL:-"${HOME}/.hq/work-mesh/spool.jsonl"}
|
|
156
|
+
|
|
157
|
+
while [ $# -gt 0 ]; do
|
|
158
|
+
case "$1" in
|
|
159
|
+
--kind) kind=$2; shift 2 ;;
|
|
160
|
+
--session-id) session_id=$2; shift 2 ;;
|
|
161
|
+
--harness) harness=$2; shift 2 ;;
|
|
162
|
+
--adapter-version) adapter_version=$2; shift 2 ;;
|
|
163
|
+
--runtime-version) runtime_version=$2; shift 2 ;;
|
|
164
|
+
--at) at=$2; shift 2 ;;
|
|
165
|
+
--seq) seq=$2; shift 2 ;;
|
|
166
|
+
--event-id) event_id=$2; shift 2 ;;
|
|
167
|
+
--task-id) task_id=$2; shift 2 ;;
|
|
168
|
+
--status) status=$2; shift 2 ;;
|
|
169
|
+
--reason) reason=$2; shift 2 ;;
|
|
170
|
+
--summary) summary=$2; shift 2 ;;
|
|
171
|
+
--cwd) cwd=$2; shift 2 ;;
|
|
172
|
+
--hq-root) hq_root=$2; shift 2 ;;
|
|
173
|
+
--company-slug) company_slug=$2; shift 2 ;;
|
|
174
|
+
--project) project=$2; shift 2 ;;
|
|
175
|
+
--task) task=$2; shift 2 ;;
|
|
176
|
+
--tool-writes) tool_writes=$2; shift 2 ;;
|
|
177
|
+
--spool) spool=$2; shift 2 ;;
|
|
178
|
+
*) printf 'work_mesh_enqueue: unknown arg %s\n' "$1" >&2; return 2 ;;
|
|
179
|
+
esac
|
|
180
|
+
done
|
|
181
|
+
|
|
182
|
+
if [ -z "$session_id" ]; then
|
|
183
|
+
printf 'work_mesh_enqueue: sessionId required\n' >&2
|
|
184
|
+
return 1
|
|
185
|
+
fi
|
|
186
|
+
if [ -z "$kind" ] || [ -z "$harness" ] || [ -z "$adapter_version" ] || [ -z "$seq" ]; then
|
|
187
|
+
printf 'work_mesh_enqueue: kind, harness, adapter-version, seq required\n' >&2
|
|
188
|
+
return 1
|
|
189
|
+
fi
|
|
190
|
+
|
|
191
|
+
if [ -z "$event_id" ]; then
|
|
192
|
+
work_mesh_ulid >/dev/null
|
|
193
|
+
event_id=$REPLY
|
|
194
|
+
fi
|
|
195
|
+
if [ -z "$at" ]; then
|
|
196
|
+
# Builtin UTC clock (bash 4.2+ printf %( )T) — no date(1) fork.
|
|
197
|
+
if TZ=UTC printf -v at '%(%Y-%m-%dT%H:%M:%S)T.000Z' -1 2>/dev/null && [ -n "$at" ]; then
|
|
198
|
+
:
|
|
199
|
+
else
|
|
200
|
+
at=$(date -u +"%Y-%m-%dT%H:%M:%S.000Z" 2>/dev/null || date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
201
|
+
fi
|
|
202
|
+
fi
|
|
203
|
+
|
|
204
|
+
# Build compact JSON without command-substitution forks (keeps hook p95 low).
|
|
205
|
+
local line q
|
|
206
|
+
work_mesh_json_quote "$event_id"; q=$REPLY
|
|
207
|
+
line='{"v":1,"eventId":'"$q"
|
|
208
|
+
work_mesh_json_quote "$kind"; q=$REPLY
|
|
209
|
+
line+=',"kind":'"$q"
|
|
210
|
+
work_mesh_json_quote "$session_id"; q=$REPLY
|
|
211
|
+
line+=',"sessionId":'"$q"
|
|
212
|
+
work_mesh_json_quote "$harness"; q=$REPLY
|
|
213
|
+
line+=',"harness":'"$q"
|
|
214
|
+
work_mesh_json_quote "$adapter_version"; q=$REPLY
|
|
215
|
+
line+=',"adapterVersion":'"$q"
|
|
216
|
+
if [ -n "$runtime_version" ]; then
|
|
217
|
+
work_mesh_json_quote "$runtime_version"; q=$REPLY
|
|
218
|
+
line+=',"runtimeVersion":'"$q"
|
|
219
|
+
fi
|
|
220
|
+
work_mesh_json_quote "$at"; q=$REPLY
|
|
221
|
+
line+=',"at":'"$q"',"seq":'"$seq"
|
|
222
|
+
if [ -n "$task_id" ]; then
|
|
223
|
+
work_mesh_json_quote "$task_id"; q=$REPLY
|
|
224
|
+
line+=',"taskId":'"$q"
|
|
225
|
+
fi
|
|
226
|
+
if [ -n "$status" ]; then
|
|
227
|
+
work_mesh_json_quote "$status"; q=$REPLY
|
|
228
|
+
line+=',"status":'"$q"
|
|
229
|
+
fi
|
|
230
|
+
if [ -n "$reason" ]; then
|
|
231
|
+
work_mesh_json_quote "$reason"; q=$REPLY
|
|
232
|
+
line+=',"reason":'"$q"
|
|
233
|
+
fi
|
|
234
|
+
if [ -n "$summary" ]; then
|
|
235
|
+
work_mesh_json_quote "$summary"; q=$REPLY
|
|
236
|
+
line+=',"summary":'"$q"
|
|
237
|
+
fi
|
|
238
|
+
if [ -n "$cwd" ]; then
|
|
239
|
+
work_mesh_json_quote "$cwd"; q=$REPLY
|
|
240
|
+
line+=',"cwd":'"$q"
|
|
241
|
+
fi
|
|
242
|
+
if [ -n "$hq_root" ]; then
|
|
243
|
+
work_mesh_json_quote "$hq_root"; q=$REPLY
|
|
244
|
+
line+=',"hqRoot":'"$q"
|
|
245
|
+
fi
|
|
246
|
+
if [ -n "$company_slug" ]; then
|
|
247
|
+
work_mesh_json_quote "$company_slug"; q=$REPLY
|
|
248
|
+
line+=',"companySlug":'"$q"
|
|
249
|
+
fi
|
|
250
|
+
if [ -n "$project" ]; then
|
|
251
|
+
work_mesh_json_quote "$project"; q=$REPLY
|
|
252
|
+
line+=',"project":'"$q"
|
|
253
|
+
fi
|
|
254
|
+
if [ -n "$task" ]; then
|
|
255
|
+
work_mesh_json_quote "$task"; q=$REPLY
|
|
256
|
+
line+=',"task":'"$q"
|
|
257
|
+
fi
|
|
258
|
+
if [ -n "$tool_writes" ]; then
|
|
259
|
+
line+=',"toolWrites":'"$tool_writes"
|
|
260
|
+
fi
|
|
261
|
+
line+='}'
|
|
262
|
+
|
|
263
|
+
work_mesh_ensure_spool "$spool" || return 1
|
|
264
|
+
# Single write under O_APPEND (printf → one write syscall for the line).
|
|
265
|
+
printf '%s\n' "$line" >>"$spool" || return 1
|
|
266
|
+
return 0
|
|
267
|
+
}
|
package/dist/commands/core.js
CHANGED
|
@@ -191,13 +191,14 @@ export const SCAFFOLD_COMMANDS = [
|
|
|
191
191
|
* direct `hq core` command.
|
|
192
192
|
*/
|
|
193
193
|
export const SCAFFOLD_ONLY_ASSETS = [
|
|
194
|
-
// Shared shell libs sourced by the two relocated scripts. Never
|
|
195
|
-
// their own (so no `hq core` command), but they MUST ship in the
|
|
196
|
-
// the bundled hq-session / checkpoint-stop-gate die at `source`
|
|
197
|
-
// user's box. `root` is inert here — they are sourced, not run.
|
|
194
|
+
// Shared shell libs sourced by the two relocated scripts (and hooks). Never
|
|
195
|
+
// dispatched on their own (so no `hq core` command), but they MUST ship in the
|
|
196
|
+
// tarball, or the bundled hq-session / checkpoint-stop-gate die at `source`
|
|
197
|
+
// time on a user's box. `root` is inert here — they are sourced, not run.
|
|
198
198
|
{ asset: "core/scripts/hook-lib.sh", root: "live", summary: "Shared shell primitives sourced by the checkpoint gate" },
|
|
199
199
|
{ asset: "core/scripts/lib/session-scope-capability.sh", root: "live", summary: "Session scope-capability lib sourced by hq-session" },
|
|
200
200
|
{ asset: "core/scripts/lib/session-id.sh", root: "live", summary: "Session-id resolver lib sourced by hq-session" },
|
|
201
|
+
{ asset: "core/scripts/lib/work-mesh-enqueue.sh", root: "live", summary: "Work Mesh Live spool enqueue lib sourced by hooks" },
|
|
201
202
|
{ asset: "core/scripts/archive-old-threads.sh", root: "live", summary: "Scaffold-only native utility companion" },
|
|
202
203
|
{ asset: "core/scripts/codex-skill-bridge.sh", root: "live", summary: "Scaffold-only native utility companion" },
|
|
203
204
|
{ asset: "core/scripts/qmd-reindex-after-sync.sh", root: "cwd", summary: "Scaffold-only native utility companion" },
|
package/dist/commands/mesh.d.ts
CHANGED
|
@@ -8,5 +8,20 @@
|
|
|
8
8
|
import { Command } from "commander";
|
|
9
9
|
import { type MeshCompany, type MeshThread } from "../lib/mesh/api.js";
|
|
10
10
|
export declare function formatCheckLines(threads: MeshThread[], company: MeshCompany, projectId?: string): string[];
|
|
11
|
+
/** Injectable confirmation seam for `hq mesh context correct` (stubbed in tests). */
|
|
12
|
+
export type MeshConfirmFn = (message: string) => Promise<boolean>;
|
|
13
|
+
/** Exported for confirmation-gate unit tests (US-017B). */
|
|
14
|
+
export declare function runContextCorrect(opts: {
|
|
15
|
+
session?: string;
|
|
16
|
+
toCompany?: string;
|
|
17
|
+
project?: string;
|
|
18
|
+
task?: string;
|
|
19
|
+
yes?: boolean;
|
|
20
|
+
json?: boolean;
|
|
21
|
+
machine?: boolean;
|
|
22
|
+
expectedVersion?: string;
|
|
23
|
+
}, deps?: {
|
|
24
|
+
confirm?: MeshConfirmFn;
|
|
25
|
+
}): Promise<void>;
|
|
11
26
|
export declare function registerMeshCommand(program: Command): void;
|
|
12
27
|
//# sourceMappingURL=mesh.d.ts.map
|