chore: Bump v1.0.18 and add event subcommand and fixes

This commit is contained in:
qingmei
2026-09-11 16:48:38 +08:00
parent 249dbfffd8
commit e631b355da
2 changed files with 46 additions and 1 deletions
+45
View File
@@ -2,6 +2,51 @@
All notable changes to tmeet will be documented in this file, following the [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) convention.
## [v1.0.18] - 2026-09-11
### Added
- **New `event` subcommand group — real-time event subscription via a per-host bus daemon** (`cmd/event/`, `cmd/root.go`, `internal/event/**`): Introduces a full "per-host bus daemon + shared WSS long connection + multi-consumer fan-out" framework so agents can subscribe to Tencent Meeting real-time events (e.g. `meeting.started`, `meeting.end`) and consume them as NDJSON. All `event consume` consumers share a single WSS connection managed by the bus daemon, which centralises handshake / heartbeat / auto-reconnect.
- `event list` (`cmd/event/list.go`) — Lists subscribable EventKeys from the local built-in registry; **does not require login** and issues no remote calls. Optional `--domain` filters by domain (e.g. `meeting`); unknown domain exits with code 1 and prints the set of known domains.
- `event schema <EventKey>` (`cmd/event/schema.go`) — Prints `params_schema` / `resolved_output_schema` / `jq_root_path` for the given EventKey; **does not require login**. Unknown key exits with code 1 pointing at `event list`. This is a mandatory pre-step before writing `--jq` / `--param`: a wrong `jq_root_path` does not error, it silently drops events (e.g. `meeting.started` / `meeting.end` have `jq_root_path=.payload` and `payload` itself is an array — jq must first do `.[0]` before descending into fields).
- `event consume --event-id <EventKey>` (`cmd/event/consume.go`, `cmd/event/consume_runner.go`) — The actual subscription entry point; **requires login** (owner_hash is bound to the bus). Supports both batch mode (`--max-events` / `--timeout`) and long-running mode (neither flag, exit via `Ctrl-C` or `event stop`). Ships with `--jq` client-side projection, `--param key=value` for subscription parameters, `--output-dir` for NDJSON tee-to-disk (relative paths only, `..` segments rejected — hard-validated), and `--quiet` to suppress non-fatal stderr. **Strict stdout / stderr separation**: business events go only to stdout NDJSON; ready markers / exit summaries / diagnostics go only to stderr. Once ready, stderr emits `[event] ready event_key=<key>` (not muted by `--quiet`); on exit, stderr emits `[event] exited — received <N> event(s) in <duration> (reason: limit|timeout|signal|shutdown)`. **Does not read stdin at all**: closing stdin / `< /dev/null` / `nohup` / `setsid` will not trigger exit in either bounded or unbounded mode, making the command naturally compatible with agent / background scenarios. The stream **does not replay historical events** — only events newly produced after subscription are delivered.
- `event status` (`cmd/event/status.go`, `cmd/event/status_stop_test.go`) — Inspects the local bus daemon state; **does not require login** and reads only the local bus directory. Lists `buses[]` with `state`: `running` / `stale_owner` (credential OpenId disagrees with bus owner) / `orphan` (pid dead but artefacts left behind) / `refused` / `errored`. Optional `--fail-on-orphan` exits with code 2 when an orphan is detected, useful for CI / self-healing scripts.
- `event stop` (`cmd/event/stop.go`) — Stops the local bus daemon; **does not require login**. `--force` is treated as a **write operation** (enters the dangerous-operation confirmation table) and is used to reap `orphan` / `stale_owner` remnants; `refused` / `errored` states return exit code 2.
- `event _bus` (`cmd/event/bus.go`) — **Hidden subcommand** forked automatically by `event consume`; **agents must never invoke it directly**. Owns the single WSS connection to the server, multiplexes subscribers, and fans events out to consumers over IPC (Unix socket / Windows named pipe).
- Event framework internals (`internal/event/bus`, `internal/event/source`, `internal/event/protocol`, `internal/event/protocol/wsspb`, `internal/event/busdiscover`, `internal/event/spawner`, `internal/event/transport`, `internal/event/cleanup`, `internal/event/jqfilter`): Hub / subscription registry / connection management / WSS + protobuf codec (`tmeet_wss.proto`) / bus discovery + PID file / cross-platform detach fork (unix / windows) / cross-platform IPC transport / session keep-alive with token refresh / event de-duplication / jq projection engine with unit tests.
- New exit-code contract: `0` (normal) / `1` (fatal: Hello rejected, unknown EventKey, IO error, subscription failure, ...) / `2` (only for `event status --fail-on-orphan` and `event stop` on `refused` / `errored`).
- New SKILL section and reference doc (`skills/tmeet-skill/references/tmeet-event.md`, 483 lines): full coverage of use-case routing, mandatory call sequence, subprocess contract (ready / exited markers, exit codes, stdin EOF behaviour, stderr diagnostics), red lines (schema-before-jq, no `kill -9`, multi-account switching, path validation, privacy in output, `_bus` never called directly), bus-remnant self-heal flow, EventKey field reference, and a common-failure lookup table.
- **Event-related client error codes** (`internal/exception/client_code.go`, `internal/exception/errors.go`): `ClientCodeEventInternal = 4000` / `ClientCodeEventBus = 4001` / `ClientCodeEventBusNotRunning = 4002`, along with matching `EventInternalError` / `EventBusError` / `EventBusNotRunningError` constants.
- **WSS token-expiry server codes** (`internal/exception/server_code.go`): `ServerCodeWssTokenExpired = 200010203` / `ServerCodeWssHeadTokenExpired = 10006`, consumed by the WSS long-connection side to detect expiry and trigger token refresh.
- **`WSS` endpoint constant** (`internal/core/endpoints.go`): `Endpoints.WSS` and `GetWSSEndpoint()`, pointing to `meeting.tencent.com`, used by the event framework to open the WSS long connection.
- **`config.ResourceReleaseHook` teardown-hook mechanism** (`internal/config/user.go`, `internal/config/resource_hooks_test.go`): New `RegisterResourceReleaseHook` / `ResetResourceReleaseHooksForTest` and a `ClearUserConfigFunc` type. Credential teardown (`ClearUserConfig`) now runs every registered hook **first** in registration order (fail-fast: any hook error or panic aborts teardown and leaves keychain + `active_open_id` intact so the user can retry), and only proceeds to keychain / meta cleanup if all hooks succeeded. `cmd/root.go` registers an `event-bus` hook via `registerResourceReleaseHook()` (dispatching to `internal/event/cleanup.OnUserCleared`) so `auth logout` and the refresh-token failure fallback gracefully stop the outgoing account's bus, preventing residue from leaking into the next login session. Deliberately placed in `internal/config` (the lowest package in the dependency graph) to avoid an `internal/event``internal/config` cycle.
- **Process-lifetime exclusive lock `filelock.ProcessLock`** (`internal/core/filelock/processlock.go`, `internal/core/filelock/filelock.go`, `internal/core/filelock/filelock_unix.go`, `internal/core/filelock/filelock_windows.go`): Distinct from the existing short-lived `WithLock` (poll + defer auto-release), `ProcessLock` acquires non-blockingly and holds for the rest of the process; the OS releases it at process exit. Foundation for the bus daemon "am I alive?" probe (the bus takes a `ProcessLock` on `bus.alive.lock` at startup and never releases it; any subsequent `TryLock` succeeding means the previous holder died). The cross-platform lock primitives `lockFDNonBlocking` / `unlockFD` are extracted so both `WithLock` and `ProcessLock` share a single well-tested implementation. Exports `ErrHeld` for `errors.Is` to distinguish contention from real syscall failures.
- **`log.InitNamed` and instance-level `Logger` helpers** (`internal/log/logging.go`): New `InitNamed(logDir, subDir, prefix, level)` lets long-running auxiliary processes (notably the event bus daemon) own their own log namespace while still benefiting from rotation / retention / async writes. The returned `*Logger` is **not** registered as the global `defaultLogger`; callers own it and must call `Close()`. Added instance-level `(*Logger).Debugf/Infof/Warnf/Errorf` and a `(*Logger).Close()` that is idempotent (double-close does not panic).
- **`thttp.DefaultNoProxyHttpClient` direct-connection fallback client and per-request `WithRequestClient` option** (`internal/core/thttp/client.go`, `internal/core/thttp/request.go`): New `*http.Client` with `Proxy: nil` (all other transport tunables match `DefaultHttpClient`) for use as a retry path when a CGI request fails at the network layer; `WithRequestClient(clt)` is a new per-request option that overrides the client-level `*http.Client` for a single request without mutating the shared client.
- **`common.BuildUniqueID(openID, machineID)` unifies `Tmeet-Unique-ID` / `cli_uniq_id` formatting** (`internal/common/system.go`): The `"<openId>*<machineId>"` concatenation is centralised in one helper. The REST proxy's `Tmeet-Unique-ID` request header and the WSS `AuthBindReq.cli_uniq_id` field now share the same implementation, ruling out format drift between the two paths.
- **`build.sh` now runs unit tests before compilation** (`build.sh`): Runs `go test -count=1 ./...` up front and aborts on failure (via the script's `set -e`), baking "test before build" into the release flow.
### Changed
- **`ClearUserConfig` is now a two-phase "release resources, then clear credentials" flow** (`internal/config/user.go`, `internal/auth/auth.go`, `internal/proxy/rest-proxy/proxy.go`): The new `ClearUserConfig` runs (1) read `ActiveOpenId`; (2) dispatch every registered `ResourceReleaseHook` fail-fast — any failure or panic leaves keychain + `active_open_id` intact so the user can retry; (3) remove the keychain entry; (4) clear `active_open_id` from meta. The former implementation is preserved as `ClearUserConfigUnResource`. `auth.RefreshToken` now takes a `ClearUserConfigFunc` argument (wired to `config.ClearUserConfig` at the call site) so it no longer hard-depends on a specific teardown function, and so event / test paths can inject different teardown strategies.
- **Record-state enum descriptions refined** (`internal/utils/enumerate/record_state.go`, `internal/utils/enumerate/record_state_test.go`, `internal/utils/converter_test.go`): `录制中``录制中,不可查看或申请`, `转码中``转码中,不可查看或申请`, `转码完成``转码完成,可根据录制文件权限进行下一步`; unit tests updated in lock-step.
- **`log.Init` / `log.Close` internals refactored around `newLogger` and instance-level `Close`** (`internal/log/logging.go`): Package-level `Init` now delegates to the shared `newLogger` constructor, and package-level `Close` delegates to `defaultLogger.Close()`. `Logger` gains `subDir` / `prefix` fields with `filePrefix()` / `fileSubDir()` fallbacks so path composition, rotation and cleanup all route through instance methods instead of the hard-coded package-level `logPrefix` / `logSubDir`, unlocking the standalone log namespace used by the event bus daemon.
- **`minute` package and CLI command renamed to `minutes`** (`cmd/minute/``cmd/minutes/`, `cmd/root.go`): CLI entry point changes from `tmeet minute search|get` to `tmeet minutes search|get`; `skills/tmeet-skill/references/tmeet-minute.md` renamed to `tmeet-minutes.md`; every `minute` reference in SKILL and README (command tree, routing decision tables, error hints) rewritten to `minutes`.
- **`cmd/root.go` wires the `event` subcommand group and enables the resource-release hook** (`cmd/root.go`): Adds `rootCmd.AddCommand(event.NewBaseCmd(tmeet))`; calls `registerResourceReleaseHook()` at the top of `Execute()` (deliberately not in `init()` — registering in `init()` would activate the hook whenever the `cmd/root` package is imported, e.g. by tests, and risk accidentally tearing down the user's bus); also drops a redundant `fmt.Sprintf` wrapper around `output.PrintErrorf` in favour of the native printf-style signature.
- **README trimmed of the long command-reference section** (`README.md`, `README_EN.md`, `docs/command.md`, `docs/command_en.md`): The ~1000-line "Command reference" section is moved out of README into the new `docs/command.md` / `docs/command_en.md`. README keeps only the command tree plus the overview of pagination / `--compact` / `--format`. The command tree gains `event list|schema|consume|status|stop`, and the pagination table renames `minute search|get` to `minutes search|get`.
- **SKILL bumped to 1.0.18, fully embracing the event group and the minutes rename** (`skills/tmeet-skill/SKILL.md`, `skills/tmeet-skill/references/tmeet-event.md`, `skills/tmeet-skill/references/tmeet-minutes.md`):
- **Top-level description extended**: adds "实时事件订阅(会议/录制/纪要等事件流)" to the capability description.
- **Login-prerequisite exceptions extended**: on top of `auth login` / `auth status`, `event list` / `event schema` / `event status` / `event stop` are also **login-free** so users can inspect bus remnants from an unauthenticated state.
- **Command tree gains an `event` branch**, linked to the new `references/tmeet-event.md`.
- **`--format` section gains an exception clause**: the `event` subcommand family emits **bare JSON****no `{trace_id, message, data}` envelope** (e.g. `event consume` emits NDJSON lines shaped `{event, trace_id, payload}`; `event list` emits `[{...}, ...]` directly) — and **does not go through the compact middleware**, so `--compact` has no effect on event output. Use `event consume --jq` for projection instead.
- **Yuanbao-minutes routing and the "minutes vs recording" decision table**: every `minute get|search` reference rewritten to `minutes get|search`.
- **Common-errors table gains an `event consume` failure entry**: missing ready marker / exit code 1 / `event stop` returning `refused`, etc., all delegate to `tmeet-event.md`'s common-failure lookup table.
### Fixed
- **`Base64DecodeConverter` no longer leaves `RawStdEncoding` / `URLEncoding` fields as raw ciphertext** (`internal/utils/converter.go`): Previously the converter only fell back to `RawURLEncoding` once when `StdEncoding` decoding failed — coverage was incomplete: if the server returned `RawStdEncoding` (standard encoding without padding) or `URLEncoding` (URL-safe encoding with padding), decoding failed and the raw Base64 ciphertext was returned to the caller, surfacing as "an unreadable blob" in the model / user output. The converter now walks `StdEncoding``RawStdEncoding``URLEncoding``RawURLEncoding` in order, validating `utf8.Valid` on each success, and returns the decoded plaintext at the first hit; only if all four encodings fail does the original value pass through untouched.
- **CGI requests would fail wholesale when a stale proxy env var was inherited** (`internal/proxy/cgi-proxy/proxy.go`, `internal/core/thttp/client.go`, `internal/core/thttp/request.go`): Agent-sandbox scenarios often set `HTTP_PROXY` / `HTTPS_PROXY` / `ALL_PROXY` pointing at a sidecar proxy, and a long-lived (or sandbox-spawned) `tmeet` process inherits those env vars. If the sidecar is torn down but the env vars remain, `DefaultHttpClient` dials a dead proxy on every request and fails at the network layer — the user-visible symptom is "every CGI-backed command times out / connection refused". Fix: introduce `DefaultNoProxyHttpClient` (`Proxy: nil` direct-connection client) plus a per-request `WithRequestClient` option. When `RequestProxy` detects a network-layer failure (not a non-200 HTTP response — an actual `c.clt.Do` error, meaning the request never got a response), it retries once over the direct-connection client using a **brand-new Request struct** so the retry does not inherit the authenticators / headers already appended by the first attempt; non-200 statuses are **not** retried (the server did respond, so retransmission would not help). The whole path also gains structured `log.Errorf` / `log.Warnf` diagnostics, with sensitive headers (`Set-Cookie` / `Authorization` / `Cookie`) redacted via `redactHeader` before being logged.
## [v1.0.17] - 2026-09-09
### Added
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@tencentcloud/tmeet",
"version": "v1.0.17",
"version": "v1.0.18",
"description": "腾讯会议 CLI 工具",
"bin": {
"tmeet": "./scripts/tmeet.js"