Compare commits
7
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4ee4a44e16 | ||
|
|
40181f8c0c | ||
|
|
14ff02ebe1 | ||
|
|
55574fe12e | ||
|
|
222ee16d51 | ||
|
|
27ced3ee18 | ||
|
|
cefcf5b409 |
+10
-2
@@ -6,10 +6,18 @@ The format is inspired by [Keep a Changelog](https://keepachangelog.com/) and th
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [1.0.54-beta.1] - 2026-07-21
|
||||
|
||||
This beta validates the restored default transport envelope for personal event output with opt-in flattening, plus Schema CLI path and plugin overlay compatibility fixes, on top of the validated `v1.0.53-beta.7` baseline.
|
||||
|
||||
### Changed
|
||||
|
||||
- **Personal event output compatibility** (#743) — `event consume` once again preserves the transport envelope by default for `ndjson`/`json`/`pretty`, while retaining the existing `compact` processor. New Agent workflows opt into the event-specific top-level DTO with `--flatten`, which is mutually exclusive with `-f raw` and `--debug-raw-events`; `event schema --flatten` describes that DTO, while the default schema describes `type/event_type/data/headers` and points to `.data | fromjson`.
|
||||
|
||||
### Fixed
|
||||
|
||||
- **Schema CLI path compatibility** — user-facing Schema lookups once again accept space-, dot-, and slash-separated CLI paths without weakening strict canonical identity resolution.
|
||||
- **Plugin CLI overlays** — installed plugins register their manifest-authored command trees again for HTTP and stdio servers, and a plugin may now replace a hidden compatibility fallback (for example `conference`) instead of being skipped as a distribution conflict.
|
||||
- **Schema CLI path compatibility** (#738) — user-facing Schema lookups once again accept space-, dot-, and slash-separated CLI paths without weakening strict canonical identity resolution.
|
||||
- **Plugin CLI overlays** (#701) — installed plugins register their manifest-authored command trees again for HTTP and stdio servers, and a plugin may now replace a hidden compatibility fallback (for example `conference`) instead of being skipped as a distribution conflict.
|
||||
|
||||
## [1.0.53] - 2026-07-21
|
||||
|
||||
|
||||
@@ -476,6 +476,8 @@ Env vars: `DWS_SKILL_MODE=mono|multi` (also honored by `install.sh` / `install.p
|
||||
|
||||
`dws event consume` subscribes as the currently logged-in user over a managed Stream WebSocket and emits each event as one NDJSON line on stdout. The public catalog currently covers messages that mention the current user, one-to-one messages with a specified user, and messages in a specified group.
|
||||
|
||||
The default `ndjson`, `json`, and `pretty` output preserves the transport envelope (`type`, `event_type`, string `data`, and `headers`) for existing scripts; `compact` retains its existing processor. Add `--flatten` to emit the stable top-level business fields used by Agent workflows. `--format` controls JSON serialization; `--flatten` controls the data structure and cannot be combined with `-f raw` or `--debug-raw-events`.
|
||||
|
||||
> **Prerequisite**: run `dws auth login`. Personal identity is resolved from the OAuth token and cannot be supplied through command-line identity flags.
|
||||
|
||||
For an event-focused installation, use the official convenience installer:
|
||||
@@ -487,19 +489,19 @@ curl -fsSL https://raw.githubusercontent.com/DingTalk-Real-AI/dingtalk-workspace
|
||||
```bash
|
||||
# Inspect the public personal event catalog and schema
|
||||
dws event list
|
||||
dws event schema user_im_message_receive_o2o
|
||||
dws event schema user_im_message_receive_o2o --flatten
|
||||
|
||||
# Listen for messages that mention the current user
|
||||
dws event consume user_im_message_receive_at -f ndjson
|
||||
dws event consume user_im_message_receive_at --flatten -f ndjson
|
||||
|
||||
# Listen for one-to-one messages with a specified user
|
||||
dws event consume user_im_message_receive_o2o --user <userId> -f ndjson
|
||||
dws event consume user_im_message_receive_o2o --user <userId> --flatten -f ndjson
|
||||
|
||||
# Listen by openDingtalkId (external contact, bot, or cross-organization identity)
|
||||
dws event consume user_im_message_receive_o2o --open-dingtalk-id <openDingtalkId> -f ndjson
|
||||
dws event consume user_im_message_receive_o2o --open-dingtalk-id <openDingtalkId> --flatten -f ndjson
|
||||
|
||||
# Listen for messages in a specified group
|
||||
dws event consume user_im_message_receive_group --group <openConversationId> -f ndjson
|
||||
dws event consume user_im_message_receive_group --group <openConversationId> --flatten -f ndjson
|
||||
|
||||
# Inspect local consumers and cancel a subscription
|
||||
dws event status
|
||||
|
||||
+7
-5
@@ -470,6 +470,8 @@ DWS_SKILL_SOURCE=/path/to/skills dws skill setup --mode multi
|
||||
|
||||
`dws event consume` 使用当前 OAuth 登录用户建立托管的 Stream WebSocket 长连接,并把每条事件以 NDJSON 一行输出到 stdout。当前公开目录包括:当前用户被 @ 的消息、与指定用户的单聊消息、指定群的消息。
|
||||
|
||||
默认 `ndjson`、`json`、`pretty` 输出保留兼容 transport envelope(`type`、`event_type`、字符串 `data`、`headers`),`compact` 继续沿用原 processor。Agent 或新脚本显式加 `--flatten` 后,输出稳定的顶层业务字段。`--format` 控制 JSON 序列化,`--flatten` 控制数据结构,且不能与 `-f raw` 或 `--debug-raw-events` 同时使用。
|
||||
|
||||
> **前置条件**:先运行 `dws auth login`。个人身份从 OAuth token 解析,不允许通过命令行伪造。
|
||||
|
||||
只需要 event 能力时,可以使用官方便捷安装脚本:
|
||||
@@ -481,19 +483,19 @@ curl -fsSL https://raw.githubusercontent.com/DingTalk-Real-AI/dingtalk-workspace
|
||||
```bash
|
||||
# 查看公开个人事件目录和 schema
|
||||
dws event list
|
||||
dws event schema user_im_message_receive_o2o
|
||||
dws event schema user_im_message_receive_o2o --flatten
|
||||
|
||||
# 监听当前用户被 @ 的消息
|
||||
dws event consume user_im_message_receive_at -f ndjson
|
||||
dws event consume user_im_message_receive_at --flatten -f ndjson
|
||||
|
||||
# 监听与指定用户的单聊消息
|
||||
dws event consume user_im_message_receive_o2o --user <userId> -f ndjson
|
||||
dws event consume user_im_message_receive_o2o --user <userId> --flatten -f ndjson
|
||||
|
||||
# 使用 openDingtalkId 监听外部联系人、机器人或跨组织身份
|
||||
dws event consume user_im_message_receive_o2o --open-dingtalk-id <openDingtalkId> -f ndjson
|
||||
dws event consume user_im_message_receive_o2o --open-dingtalk-id <openDingtalkId> --flatten -f ndjson
|
||||
|
||||
# 监听指定群的消息
|
||||
dws event consume user_im_message_receive_group --group <openConversationId> -f ndjson
|
||||
dws event consume user_im_message_receive_group --group <openConversationId> --flatten -f ndjson
|
||||
|
||||
# 查看本地 consume,并取消指定订阅
|
||||
dws event status
|
||||
|
||||
@@ -1469,10 +1469,10 @@ func TestCrossPlatformCoveragePersonalEventPureCoverage(t *testing.T) {
|
||||
if !ok {
|
||||
t.Fatal("mention definition missing")
|
||||
}
|
||||
if err := renderPersonalSchema(io.Discard, def, ""); err != nil {
|
||||
if err := renderPersonalSchema(io.Discard, def, "", false); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := renderPersonalSchema(io.Discard, def, "yaml"); err == nil {
|
||||
if err := renderPersonalSchema(io.Discard, def, "yaml", true); err == nil {
|
||||
t.Fatal("unsupported schema format succeeded")
|
||||
}
|
||||
for _, key := range []string{"", "unknown", personal.EventMention, personal.EventFromUser} {
|
||||
|
||||
@@ -112,6 +112,7 @@ func newEventConsumeCommand() *cobra.Command {
|
||||
dryRun bool
|
||||
foreground bool
|
||||
asIdentity string
|
||||
flatten bool
|
||||
personalOpts personalConsumeOptions
|
||||
streamOpts eventStreamTicketOptions
|
||||
)
|
||||
@@ -127,7 +128,11 @@ func newEventConsumeCommand() *cobra.Command {
|
||||
json 每事件多行美化 JSON(必须配 --max-events 或 --duration)
|
||||
pretty 同 json,未来加颜色
|
||||
raw 仅 SDK 原始 payload,无外层封装
|
||||
compact 扁平化 + 解析嵌套 + 抽取语义字段(Agent 友好)
|
||||
compact 单行紧凑 JSON;不传 --flatten 时沿用原 compact processor
|
||||
|
||||
数据结构:
|
||||
ndjson/json/pretty 默认保持 transport envelope(type/event_type/data/headers)
|
||||
--flatten 结构化格式输出稳定的顶层业务字段,适合 Agent / 脚本直接消费
|
||||
|
||||
默认使用当前 OAuth 登录态自动创建/复用个人订阅并建立个人长连接;非默认组织加
|
||||
--profile。连上后 stderr 打就绪行 [event] ready,等它出现再读 stdout;停机用
|
||||
@@ -144,6 +149,7 @@ SIGTERM、关 stdin,或先用 dws event stop <subscribe_id> --dry-run 预览
|
||||
}
|
||||
if as == "user" {
|
||||
personalOpts.EventKey = firstArg(args)
|
||||
personalOpts.Flatten = flatten
|
||||
personalOpts.Common = commonConsumeOptions{
|
||||
EventTypes: eventTypes,
|
||||
Filter: filter,
|
||||
@@ -167,6 +173,7 @@ SIGTERM、关 stdin,或先用 dws event stop <subscribe_id> --dry-run 预览
|
||||
return fmt.Errorf("event consume: --debug-raw-events is only supported with --as user")
|
||||
}
|
||||
if err := rejectChangedFlags(c, "user",
|
||||
"flatten",
|
||||
"subscribe-id",
|
||||
"rule",
|
||||
"name",
|
||||
@@ -280,6 +287,8 @@ SIGTERM、关 stdin,或先用 dws event stop <subscribe_id> --dry-run 预览
|
||||
"提示 bus 客户端期望 compact 渲染(语义透传,bus 仍按原 payload 投递)")
|
||||
f.StringVarP(&formatRaw, "format", "f", "ndjson",
|
||||
"输出格式 (ndjson/json/pretty/raw/compact);事件流默认 ndjson")
|
||||
f.BoolVar(&flatten, "flatten", false,
|
||||
"将个人事件 transport envelope 投影为稳定的顶层业务字段")
|
||||
f.StringVar(&outputDir, "output-dir", "",
|
||||
"每事件写一个文件到该目录 ({type}_{id}_{ts}.json);与 stdout 互斥")
|
||||
f.StringArrayVar(&routesRaw, "route", nil,
|
||||
|
||||
@@ -61,6 +61,7 @@ type commonConsumeOptions struct {
|
||||
type personalConsumeOptions struct {
|
||||
Common commonConsumeOptions
|
||||
EventKey string
|
||||
Flatten bool
|
||||
DebugRawEvents bool
|
||||
SubscribeID string
|
||||
Rule string
|
||||
@@ -140,6 +141,7 @@ var (
|
||||
func newEventSchemaCommand() *cobra.Command {
|
||||
var asIdentity string
|
||||
var formatRaw string
|
||||
var flatten bool
|
||||
cmd := &cobra.Command{
|
||||
Use: "schema <event_key>",
|
||||
Short: "显示事件 schema",
|
||||
@@ -157,11 +159,12 @@ func newEventSchemaCommand() *cobra.Command {
|
||||
if !def.Public {
|
||||
return personal.PublicAvailabilityError(args[0])
|
||||
}
|
||||
return renderPersonalSchema(c.OutOrStdout(), def, formatRaw)
|
||||
return renderPersonalSchema(c.OutOrStdout(), def, formatRaw, flatten)
|
||||
},
|
||||
}
|
||||
cmd.Flags().StringVar(&asIdentity, "as", "user", "事件身份: user")
|
||||
cmd.Flags().StringVarP(&formatRaw, "format", "f", "json", "输出格式: json")
|
||||
cmd.Flags().BoolVar(&flatten, "flatten", false, "显示 --flatten 消费模式对应的顶层业务字段 schema")
|
||||
hideEventInternalFlags(cmd, "as")
|
||||
cli.AnnotateRuntimePositionals(cmd, cli.RuntimeSchemaPositional{
|
||||
Name: "event_key",
|
||||
@@ -189,7 +192,7 @@ func runPersonalEventList(c *cobra.Command, opts personalListOptions) error {
|
||||
return tw.Flush()
|
||||
}
|
||||
|
||||
func renderPersonalSchema(w io.Writer, def personal.Definition, format string) error {
|
||||
func renderPersonalSchema(w io.Writer, def personal.Definition, format string, flatten bool) error {
|
||||
format = strings.ToLower(strings.TrimSpace(format))
|
||||
if format == "" {
|
||||
format = "json"
|
||||
@@ -199,7 +202,7 @@ func renderPersonalSchema(w io.Writer, def personal.Definition, format string) e
|
||||
}
|
||||
enc := json.NewEncoder(w)
|
||||
enc.SetIndent("", " ")
|
||||
return enc.Encode(personal.BuildSchemaDocument(def))
|
||||
return enc.Encode(personal.BuildSchemaDocumentForMode(def, flatten))
|
||||
}
|
||||
|
||||
func runPersonalEventConsume(c *cobra.Command, opts personalConsumeOptions) error {
|
||||
@@ -207,6 +210,19 @@ func runPersonalEventConsume(c *cobra.Command, opts personalConsumeOptions) erro
|
||||
if err := ensurePublicPersonalEvent(opts.EventKey); err != nil {
|
||||
return err
|
||||
}
|
||||
rawFormat := ""
|
||||
if f := c.Flags().Lookup("format"); f != nil && f.Changed {
|
||||
rawFormat = opts.Common.FormatRaw
|
||||
}
|
||||
normalised, fellback := consume.NormalizeFormat(rawFormat)
|
||||
if fellback && !opts.Common.Quiet {
|
||||
fmt.Fprintf(c.ErrOrStderr(), "WARN: --format %q has no meaning for event stream; using ndjson\n", rawFormat)
|
||||
}
|
||||
if err := validatePersonalEventOutputMode(opts.Flatten, opts.DebugRawEvents, normalised); err != nil {
|
||||
return fmt.Errorf("event consume --as user: %w", err)
|
||||
}
|
||||
projector := personalEventProjector(opts.DebugRawEvents, opts.Flatten)
|
||||
|
||||
configDir := defaultConfigDir()
|
||||
identity, err := personalResolveEventIdentity(ctx, configDir, opts.StreamSourceID)
|
||||
if err != nil {
|
||||
@@ -221,16 +237,6 @@ func runPersonalEventConsume(c *cobra.Command, opts personalConsumeOptions) erro
|
||||
if err != nil {
|
||||
return fmt.Errorf("event consume --as user: %w", err)
|
||||
}
|
||||
rawFormat := ""
|
||||
if f := c.Flags().Lookup("format"); f != nil && f.Changed {
|
||||
rawFormat = opts.Common.FormatRaw
|
||||
}
|
||||
normalised, fellback := consume.NormalizeFormat(rawFormat)
|
||||
if fellback && !opts.Common.Quiet {
|
||||
fmt.Fprintf(c.ErrOrStderr(), "WARN: --format %q has no meaning for event stream; using ndjson\n", rawFormat)
|
||||
}
|
||||
projector := personalEventProjector(opts.DebugRawEvents)
|
||||
|
||||
if opts.Common.DryRun {
|
||||
if strings.TrimSpace(opts.SubscribeID) == "" {
|
||||
if err := validatePersonalSubscriptionOptions(opts); err != nil {
|
||||
@@ -247,6 +253,7 @@ func runPersonalEventConsume(c *cobra.Command, opts personalConsumeOptions) erro
|
||||
Duration: opts.Common.Duration,
|
||||
EventKey: opts.EventKey,
|
||||
Format: normalised,
|
||||
Flatten: opts.Flatten,
|
||||
OutputDir: opts.Common.OutputDir,
|
||||
Routes: routes,
|
||||
Projector: projector,
|
||||
@@ -303,6 +310,7 @@ func runPersonalEventConsume(c *cobra.Command, opts personalConsumeOptions) erro
|
||||
Duration: opts.Common.Duration,
|
||||
EventKey: eventKey,
|
||||
Format: normalised,
|
||||
Flatten: opts.Flatten,
|
||||
OutputDir: opts.Common.OutputDir,
|
||||
Routes: routes,
|
||||
Projector: projector,
|
||||
@@ -366,11 +374,27 @@ func runPersonalEventConsume(c *cobra.Command, opts personalConsumeOptions) erro
|
||||
return err
|
||||
}
|
||||
|
||||
func personalEventProjector(debugRawEvents bool) consume.Projector {
|
||||
func personalEventProjector(debugRawEvents, flatten bool) consume.Projector {
|
||||
if debugRawEvents {
|
||||
return func(ev transport.Event) (any, error) { return ev, nil }
|
||||
}
|
||||
return personal.ProjectOutput
|
||||
if flatten {
|
||||
return personal.ProjectOutput
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validatePersonalEventOutputMode(flatten, debugRawEvents bool, format consume.Format) error {
|
||||
if !flatten {
|
||||
return nil
|
||||
}
|
||||
if debugRawEvents {
|
||||
return fmt.Errorf("--flatten and --debug-raw-events are mutually exclusive")
|
||||
}
|
||||
if format == consume.FormatRaw {
|
||||
return fmt.Errorf("--flatten and --format raw are mutually exclusive")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func applyPersonalConsumeFilters(cfg *consume.Config, opts personalConsumeOptions, subscribeID, eventKey string) {
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/event/consume"
|
||||
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/event/personal"
|
||||
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/event/transport"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func TestApplyPersonalConsumeFiltersDebugRawEvents(t *testing.T) {
|
||||
@@ -52,11 +53,14 @@ func TestApplyPersonalConsumeFiltersDefault(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestPersonalEventProjectorUsesRawEnvelopeForDebug(t *testing.T) {
|
||||
if personalEventProjector(false) == nil {
|
||||
t.Fatal("normal personal consume projector = nil")
|
||||
func TestPersonalEventProjectorSelectsExplicitModes(t *testing.T) {
|
||||
if personalEventProjector(false, false) != nil {
|
||||
t.Fatal("default personal consume should preserve transport envelope")
|
||||
}
|
||||
projector := personalEventProjector(true)
|
||||
if personalEventProjector(false, true) == nil {
|
||||
t.Fatal("flatten personal consume projector = nil")
|
||||
}
|
||||
projector := personalEventProjector(true, false)
|
||||
if projector == nil {
|
||||
t.Fatal("debug raw personal consume projector = nil")
|
||||
}
|
||||
@@ -74,6 +78,69 @@ func TestPersonalEventProjectorUsesRawEnvelopeForDebug(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestEventConsumeFlattenRejectsRawModesBeforeIdentityResolution(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
args []string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "raw format",
|
||||
args: []string{personal.EventMention, "--flatten", "--format", "raw"},
|
||||
want: "--flatten and --format raw are mutually exclusive",
|
||||
},
|
||||
{
|
||||
name: "raw debug",
|
||||
args: []string{personal.EventMention, "--flatten", "--debug-raw-events"},
|
||||
want: "--flatten and --debug-raw-events are mutually exclusive",
|
||||
},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Setenv("DWS_CONFIG_DIR", t.TempDir())
|
||||
cmd := newEventConsumeCommand()
|
||||
cmd.SilenceUsage = true
|
||||
cmd.SilenceErrors = true
|
||||
cmd.SetArgs(tc.args)
|
||||
err := cmd.Execute()
|
||||
if err == nil || !strings.Contains(err.Error(), tc.want) {
|
||||
t.Fatalf("Execute() error = %v, want %q", err, tc.want)
|
||||
}
|
||||
if strings.Contains(err.Error(), "login") || strings.Contains(err.Error(), "token") {
|
||||
t.Fatalf("output-mode validation ran after identity resolution: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidatePersonalEventOutputModeAllowsFlattenStructuredFormats(t *testing.T) {
|
||||
for _, format := range []consume.Format{consume.FormatNDJSON, consume.FormatJSON, consume.FormatPretty, consume.FormatCompact} {
|
||||
if err := validatePersonalEventOutputMode(true, false, format); err != nil {
|
||||
t.Fatalf("validatePersonalEventOutputMode(true, false, %q) error = %v", format, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestEventConsumeFlattenFlagIsForwarded(t *testing.T) {
|
||||
oldRun := eventRunPersonalConsume
|
||||
t.Cleanup(func() { eventRunPersonalConsume = oldRun })
|
||||
|
||||
var got personalConsumeOptions
|
||||
eventRunPersonalConsume = func(_ *cobra.Command, opts personalConsumeOptions) error {
|
||||
got = opts
|
||||
return nil
|
||||
}
|
||||
cmd := newEventConsumeCommand()
|
||||
cmd.SilenceUsage = true
|
||||
cmd.SilenceErrors = true
|
||||
cmd.SetArgs([]string{personal.EventMention, "--flatten", "--format", "compact"})
|
||||
if err := cmd.Execute(); err != nil {
|
||||
t.Fatalf("Execute() error = %v", err)
|
||||
}
|
||||
if !got.Flatten || got.Common.FormatRaw != "compact" {
|
||||
t.Fatalf("forwarded options = %#v", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEventConsumeDebugRawEventsRequiresUserMode(t *testing.T) {
|
||||
cmd := newEventConsumeCommand()
|
||||
cmd.SilenceUsage = true
|
||||
|
||||
@@ -188,7 +188,44 @@ func TestPersonalEventSchemaHidesSchemaIDs(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestPersonalEventSchemaUsesSingleJSONSchema(t *testing.T) {
|
||||
func TestPersonalEventSchemaDefaultsToTransportEnvelope(t *testing.T) {
|
||||
cmd := newEventSchemaCommand()
|
||||
cmd.SilenceUsage = true
|
||||
cmd.SilenceErrors = true
|
||||
var out bytes.Buffer
|
||||
cmd.SetOut(&out)
|
||||
cmd.SetArgs([]string{personal.EventSingleChat})
|
||||
if err := cmd.Execute(); err != nil {
|
||||
t.Fatalf("Execute() error = %v", err)
|
||||
}
|
||||
var doc map[string]any
|
||||
if err := json.Unmarshal(out.Bytes(), &doc); err != nil {
|
||||
t.Fatalf("schema output is not JSON: %v\n%s", err, out.String())
|
||||
}
|
||||
if doc["jq_root_path"] != ".data | fromjson" {
|
||||
t.Fatalf("jq_root_path = %#v, want .data | fromjson", doc["jq_root_path"])
|
||||
}
|
||||
schema, ok := doc["schema"].(map[string]any)
|
||||
if !ok {
|
||||
t.Fatalf("schema = %#v, want object", doc["schema"])
|
||||
}
|
||||
props, ok := schema["properties"].(map[string]any)
|
||||
if !ok {
|
||||
t.Fatalf("schema.properties = %#v, want object", schema["properties"])
|
||||
}
|
||||
for _, field := range []string{"type", "seq", "event_type", "data", "headers", "subscribe_id"} {
|
||||
if _, ok := props[field]; !ok {
|
||||
t.Fatalf("default envelope schema missing %q: %#v", field, props)
|
||||
}
|
||||
}
|
||||
for _, field := range []string{"content", "sender", "conversation_id", "timestamp"} {
|
||||
if _, ok := props[field]; ok {
|
||||
t.Fatalf("default envelope schema unexpectedly contains flat field %q", field)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPersonalEventFlattenedSchemaUsesSingleJSONSchema(t *testing.T) {
|
||||
for _, eventKey := range []string{
|
||||
personal.EventMention,
|
||||
personal.EventSingleChat,
|
||||
@@ -200,7 +237,7 @@ func TestPersonalEventSchemaUsesSingleJSONSchema(t *testing.T) {
|
||||
cmd.SilenceErrors = true
|
||||
var out bytes.Buffer
|
||||
cmd.SetOut(&out)
|
||||
cmd.SetArgs([]string{eventKey})
|
||||
cmd.SetArgs([]string{eventKey, "--flatten"})
|
||||
if err := cmd.Execute(); err != nil {
|
||||
t.Fatalf("Execute() error = %v", err)
|
||||
}
|
||||
@@ -316,7 +353,7 @@ func TestPersonalActionEventSchemaMatchesFlatOutput(t *testing.T) {
|
||||
cmd.SilenceErrors = true
|
||||
var out bytes.Buffer
|
||||
cmd.SetOut(&out)
|
||||
cmd.SetArgs([]string{eventKey})
|
||||
cmd.SetArgs([]string{eventKey, "--flatten"})
|
||||
if err := cmd.Execute(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -367,6 +404,9 @@ func TestEventSchemaDefaultsToUser(t *testing.T) {
|
||||
if doc["event_key"] != personal.EventSingleChat {
|
||||
t.Fatalf("event_key = %#v, want %s", doc["event_key"], personal.EventSingleChat)
|
||||
}
|
||||
if doc["jq_root_path"] != ".data | fromjson" {
|
||||
t.Fatalf("jq_root_path = %#v, want default envelope path", doc["jq_root_path"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestPersonalEventFromUserIsPubliclyAvailable(t *testing.T) {
|
||||
@@ -469,6 +509,9 @@ func TestEventConsumeCobraSchemaIncludesOpenDingTalkID(t *testing.T) {
|
||||
if _, ok := params["odid"]; ok {
|
||||
t.Fatalf("schema parameters unexpectedly include odid alias: %#v", params)
|
||||
}
|
||||
if _, ok := params["flatten"]; !ok {
|
||||
t.Fatalf("schema parameters missing flatten: %#v", params)
|
||||
}
|
||||
for _, name := range []string{"user", "open-dingtalk-id", "group"} {
|
||||
param, ok := params[name].(map[string]any)
|
||||
if !ok {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"product_id": "event",
|
||||
"tools": {
|
||||
"event consume": {
|
||||
"agent_summary": "订阅并持续消费指定个人事件,输出 NDJSON 事件流",
|
||||
"agent_summary": "订阅并持续消费指定个人事件;Agent 使用 --flatten 输出顶层业务 NDJSON",
|
||||
"agent_summary_source": "dws-agent-selection/event",
|
||||
"availability": "available",
|
||||
"avoid_when": [
|
||||
@@ -13,19 +13,19 @@
|
||||
"effect": "write",
|
||||
"effect_source": "agent-hint",
|
||||
"examples": [
|
||||
"dws event consume user_im_message_receive_user --open-dingtalk-id open-example --max-events 1 --format ndjson",
|
||||
"dws event consume user_im_message_reaction_group --group cid-example --max-events 1 --format ndjson"
|
||||
"dws event consume user_im_message_receive_user --open-dingtalk-id open-example --flatten --max-events 1 --format ndjson",
|
||||
"dws event consume user_im_message_reaction_group --group cid-example --flatten --max-events 1 --format ndjson"
|
||||
],
|
||||
"field_provenance": {
|
||||
"agent_summary": {
|
||||
"value": "订阅并持续消费指定个人事件,输出 NDJSON 事件流",
|
||||
"value": "订阅并持续消费指定个人事件;Agent 使用 --flatten 输出顶层业务 NDJSON",
|
||||
"source": "internal/cli/schema_hints/selection/event.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
"resolution": "highest_precedence",
|
||||
"review_reason": "人工依据实时 dws schema(或 Skill/Cobra/pinned MCP 对照)决策化选型文案与门禁;不改写命令身份与参数契约;示例不含 --yes。",
|
||||
"candidates": [
|
||||
{
|
||||
"value": "订阅并持续消费指定个人事件,输出 NDJSON 事件流",
|
||||
"value": "订阅并持续消费指定个人事件;Agent 使用 --flatten 输出顶层业务 NDJSON",
|
||||
"source": "internal/cli/schema_hints/selection/event.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
"selected": true,
|
||||
@@ -105,8 +105,8 @@
|
||||
},
|
||||
"examples": {
|
||||
"value": [
|
||||
"dws event consume user_im_message_receive_user --open-dingtalk-id open-example --max-events 1 --format ndjson",
|
||||
"dws event consume user_im_message_reaction_group --group cid-example --max-events 1 --format ndjson"
|
||||
"dws event consume user_im_message_receive_user --open-dingtalk-id open-example --flatten --max-events 1 --format ndjson",
|
||||
"dws event consume user_im_message_reaction_group --group cid-example --flatten --max-events 1 --format ndjson"
|
||||
],
|
||||
"source": "internal/cli/schema_hints/selection/event.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
@@ -115,8 +115,8 @@
|
||||
"candidates": [
|
||||
{
|
||||
"value": [
|
||||
"dws event consume user_im_message_receive_user --open-dingtalk-id open-example --max-events 1 --format ndjson",
|
||||
"dws event consume user_im_message_reaction_group --group cid-example --max-events 1 --format ndjson"
|
||||
"dws event consume user_im_message_receive_user --open-dingtalk-id open-example --flatten --max-events 1 --format ndjson",
|
||||
"dws event consume user_im_message_reaction_group --group cid-example --flatten --max-events 1 --format ndjson"
|
||||
],
|
||||
"source": "internal/cli/schema_hints/selection/event.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
@@ -538,7 +538,7 @@
|
||||
]
|
||||
},
|
||||
"event schema": {
|
||||
"agent_summary": "查询指定个人事件码的 payload 字段结构",
|
||||
"agent_summary": "查询指定个人事件码的输出字段结构;Agent 应查询 --flatten 模式",
|
||||
"agent_summary_source": "dws-agent-selection/event",
|
||||
"availability": "available",
|
||||
"avoid_when": [
|
||||
@@ -549,18 +549,18 @@
|
||||
"effect": "read",
|
||||
"effect_source": "agent-hint",
|
||||
"examples": [
|
||||
"dws event schema user_im_message_receive_at --format json"
|
||||
"dws event schema user_im_message_receive_at --flatten --format json"
|
||||
],
|
||||
"field_provenance": {
|
||||
"agent_summary": {
|
||||
"value": "查询指定个人事件码的 payload 字段结构",
|
||||
"value": "查询指定个人事件码的输出字段结构;Agent 应查询 --flatten 模式",
|
||||
"source": "internal/cli/schema_hints/selection/event.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
"resolution": "highest_precedence",
|
||||
"review_reason": "人工依据实时 dws schema(或 Skill/Cobra/pinned MCP 对照)决策化选型文案与门禁;不改写命令身份与参数契约;示例不含 --yes。",
|
||||
"candidates": [
|
||||
{
|
||||
"value": "查询指定个人事件码的 payload 字段结构",
|
||||
"value": "查询指定个人事件码的输出字段结构;Agent 应查询 --flatten 模式",
|
||||
"source": "internal/cli/schema_hints/selection/event.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
"selected": true,
|
||||
@@ -640,7 +640,7 @@
|
||||
},
|
||||
"examples": {
|
||||
"value": [
|
||||
"dws event schema user_im_message_receive_at --format json"
|
||||
"dws event schema user_im_message_receive_at --flatten --format json"
|
||||
],
|
||||
"source": "internal/cli/schema_hints/selection/event.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
@@ -649,7 +649,7 @@
|
||||
"candidates": [
|
||||
{
|
||||
"value": [
|
||||
"dws event schema user_im_message_receive_at --format json"
|
||||
"dws event schema user_im_message_receive_at --flatten --format json"
|
||||
],
|
||||
"source": "internal/cli/schema_hints/selection/event.json",
|
||||
"precedence": "reviewed_explicit",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"version": 1,
|
||||
"source_hash": "sha256:de587cba5051dd4c2715353012d8d9f2a7c5208a0c6dee4ea703cfc97b7351f0",
|
||||
"source_hash": "sha256:5df496973c41b3b4f7ae8c856ff0e9e99bcabd4455419b7d41bb23c44df6f1af",
|
||||
"surface_hash": "sha256:7ef588f38052f0104e027c8daff5fefd68698781f2c87715461183d4058288ed",
|
||||
"coverage": {
|
||||
"surface_products": 22,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"version": 1,
|
||||
"source_hash": "sha256:de587cba5051dd4c2715353012d8d9f2a7c5208a0c6dee4ea703cfc97b7351f0",
|
||||
"source_hash": "sha256:5df496973c41b3b4f7ae8c856ff0e9e99bcabd4455419b7d41bb23c44df6f1af",
|
||||
"surface_hash": "sha256:7ef588f38052f0104e027c8daff5fefd68698781f2c87715461183d4058288ed",
|
||||
"source_files": 150,
|
||||
"hint_files": 46,
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"version": 1,
|
||||
"source_hash": "sha256:522d4b43cf13f07430a407319a772cd11e9b1f268f8d98b4d68bdb385e4e585b",
|
||||
"source_hash": "sha256:14398788381c822f208e8cae059d98cd60da6625023869c74015735650d67e6f",
|
||||
"surface_hash": "sha256:7ef588f38052f0104e027c8daff5fefd68698781f2c87715461183d4058288ed",
|
||||
"catalog": {
|
||||
"agent_metadata": {
|
||||
"products_with_metadata": 22,
|
||||
"source": "embedded-skill-metadata",
|
||||
"source_hash": "sha256:de587cba5051dd4c2715353012d8d9f2a7c5208a0c6dee4ea703cfc97b7351f0",
|
||||
"source_hash": "sha256:5df496973c41b3b4f7ae8c856ff0e9e99bcabd4455419b7d41bb23c44df6f1af",
|
||||
"surface_hash": "sha256:7ef588f38052f0104e027c8daff5fefd68698781f2c87715461183d4058288ed",
|
||||
"surface_products": 22,
|
||||
"surface_tools": 572,
|
||||
@@ -12178,7 +12178,7 @@
|
||||
"tools": [
|
||||
{
|
||||
"agent_metadata_source": "embedded-skill-metadata",
|
||||
"agent_summary": "订阅并持续消费指定个人事件,输出 NDJSON 事件流",
|
||||
"agent_summary": "订阅并持续消费指定个人事件;Agent 使用 --flatten 输出顶层业务 NDJSON",
|
||||
"agent_summary_source": "dws-agent-selection/event",
|
||||
"availability": "available",
|
||||
"avoid_when": [
|
||||
@@ -12189,7 +12189,7 @@
|
||||
"cli_name": "consume",
|
||||
"cli_path": "event consume",
|
||||
"confirmation": "not_required",
|
||||
"description": "订阅 DingTalk 个人事件并将每条事件以 NDJSON 输出到 stdout。\n\n输出格式(事件流默认 ndjson;显式 -f json/pretty/raw 可覆盖;-f table/csv 对\n事件流无意义会 fallback 到 ndjson):\n ndjson (默认) 一行一对象,适合 jq / 管道处理\n json 每事件多行美化 JSON(必须配 --max-events 或 --duration)\n pretty 同 json,未来加颜色\n raw 仅 SDK 原始 payload,无外层封装\n compact 扁平化 + 解析嵌套 + 抽取语义字段(Agent 友好)\n\n默认使用当前 OAuth 登录态自动创建/复用个人订阅并建立个人长连接;非默认组织加\n--profile。连上后 stderr 打就绪行 [event] ready,等它出现再读 stdout;停机用\nSIGTERM、关 stdin,或先用 dws event stop \u003csubscribe_id\u003e --dry-run 预览、确认后加\n--yes,绝不要 kill -9。\n--event-types/--filter 只影响本地 bus → consume 这一段投递;普通个人事件消费\n通常不需要设置。",
|
||||
"description": "订阅 DingTalk 个人事件并将每条事件以 NDJSON 输出到 stdout。\n\n输出格式(事件流默认 ndjson;显式 -f json/pretty/raw 可覆盖;-f table/csv 对\n事件流无意义会 fallback 到 ndjson):\n ndjson (默认) 一行一对象,适合 jq / 管道处理\n json 每事件多行美化 JSON(必须配 --max-events 或 --duration)\n pretty 同 json,未来加颜色\n raw 仅 SDK 原始 payload,无外层封装\n compact 单行紧凑 JSON;不传 --flatten 时沿用原 compact processor\n\n数据结构:\n ndjson/json/pretty 默认保持 transport envelope(type/event_type/data/headers)\n --flatten 结构化格式输出稳定的顶层业务字段,适合 Agent / 脚本直接消费\n\n默认使用当前 OAuth 登录态自动创建/复用个人订阅并建立个人长连接;非默认组织加\n--profile。连上后 stderr 打就绪行 [event] ready,等它出现再读 stdout;停机用\nSIGTERM、关 stdin,或先用 dws event stop \u003csubscribe_id\u003e --dry-run 预览、确认后加\n--yes,绝不要 kill -9。\n--event-types/--filter 只影响本地 bus → consume 这一段投递;普通个人事件消费\n通常不需要设置。",
|
||||
"effect": "write",
|
||||
"idempotency": "non_idempotent",
|
||||
"interface_mode": "composite",
|
||||
@@ -12234,7 +12234,7 @@
|
||||
},
|
||||
{
|
||||
"agent_metadata_source": "embedded-skill-metadata",
|
||||
"agent_summary": "查询指定个人事件码的 payload 字段结构",
|
||||
"agent_summary": "查询指定个人事件码的输出字段结构;Agent 应查询 --flatten 模式",
|
||||
"agent_summary_source": "dws-agent-selection/event",
|
||||
"availability": "available",
|
||||
"avoid_when": [
|
||||
@@ -290130,7 +290130,7 @@
|
||||
"skills/mono/SKILL.md",
|
||||
"skills/mono/references/products/event.md"
|
||||
],
|
||||
"agent_summary": "订阅并持续消费指定个人事件,输出 NDJSON 事件流",
|
||||
"agent_summary": "订阅并持续消费指定个人事件;Agent 使用 --flatten 输出顶层业务 NDJSON",
|
||||
"agent_summary_source": "dws-agent-selection/event",
|
||||
"availability": "available",
|
||||
"avoid_when": [
|
||||
@@ -290149,13 +290149,13 @@
|
||||
]
|
||||
]
|
||||
},
|
||||
"description": "订阅 DingTalk 个人事件并将每条事件以 NDJSON 输出到 stdout。\n\n输出格式(事件流默认 ndjson;显式 -f json/pretty/raw 可覆盖;-f table/csv 对\n事件流无意义会 fallback 到 ndjson):\n ndjson (默认) 一行一对象,适合 jq / 管道处理\n json 每事件多行美化 JSON(必须配 --max-events 或 --duration)\n pretty 同 json,未来加颜色\n raw 仅 SDK 原始 payload,无外层封装\n compact 扁平化 + 解析嵌套 + 抽取语义字段(Agent 友好)\n\n默认使用当前 OAuth 登录态自动创建/复用个人订阅并建立个人长连接;非默认组织加\n--profile。连上后 stderr 打就绪行 [event] ready,等它出现再读 stdout;停机用\nSIGTERM、关 stdin,或先用 dws event stop \u003csubscribe_id\u003e --dry-run 预览、确认后加\n--yes,绝不要 kill -9。\n--event-types/--filter 只影响本地 bus → consume 这一段投递;普通个人事件消费\n通常不需要设置。",
|
||||
"description": "订阅 DingTalk 个人事件并将每条事件以 NDJSON 输出到 stdout。\n\n输出格式(事件流默认 ndjson;显式 -f json/pretty/raw 可覆盖;-f table/csv 对\n事件流无意义会 fallback 到 ndjson):\n ndjson (默认) 一行一对象,适合 jq / 管道处理\n json 每事件多行美化 JSON(必须配 --max-events 或 --duration)\n pretty 同 json,未来加颜色\n raw 仅 SDK 原始 payload,无外层封装\n compact 单行紧凑 JSON;不传 --flatten 时沿用原 compact processor\n\n数据结构:\n ndjson/json/pretty 默认保持 transport envelope(type/event_type/data/headers)\n --flatten 结构化格式输出稳定的顶层业务字段,适合 Agent / 脚本直接消费\n\n默认使用当前 OAuth 登录态自动创建/复用个人订阅并建立个人长连接;非默认组织加\n--profile。连上后 stderr 打就绪行 [event] ready,等它出现再读 stdout;停机用\nSIGTERM、关 stdin,或先用 dws event stop \u003csubscribe_id\u003e --dry-run 预览、确认后加\n--yes,绝不要 kill -9。\n--event-types/--filter 只影响本地 bus → consume 这一段投递;普通个人事件消费\n通常不需要设置。",
|
||||
"display": "事件订阅 (DingTalk Stream 长连接)",
|
||||
"effect": "write",
|
||||
"effect_source": "agent-hint",
|
||||
"examples": [
|
||||
"dws event consume user_im_message_receive_user --open-dingtalk-id open-example --max-events 1 --format ndjson",
|
||||
"dws event consume user_im_message_reaction_group --group cid-example --max-events 1 --format ndjson"
|
||||
"dws event consume user_im_message_receive_user --open-dingtalk-id open-example --flatten --max-events 1 --format ndjson",
|
||||
"dws event consume user_im_message_reaction_group --group cid-example --flatten --max-events 1 --format ndjson"
|
||||
],
|
||||
"field_provenance": {
|
||||
"agent_summary": {
|
||||
@@ -290165,14 +290165,14 @@
|
||||
"review_reason": "人工依据实时 dws schema(或 Skill/Cobra/pinned MCP 对照)决策化选型文案与门禁;不改写命令身份与参数契约;示例不含 --yes。",
|
||||
"selected": true,
|
||||
"source": "internal/cli/schema_hints/selection/event.json",
|
||||
"value": "订阅并持续消费指定个人事件,输出 NDJSON 事件流"
|
||||
"value": "订阅并持续消费指定个人事件;Agent 使用 --flatten 输出顶层业务 NDJSON"
|
||||
}
|
||||
],
|
||||
"precedence": "reviewed_explicit",
|
||||
"resolution": "highest_precedence",
|
||||
"review_reason": "人工依据实时 dws schema(或 Skill/Cobra/pinned MCP 对照)决策化选型文案与门禁;不改写命令身份与参数契约;示例不含 --yes。",
|
||||
"source": "internal/cli/schema_hints/selection/event.json",
|
||||
"value": "订阅并持续消费指定个人事件,输出 NDJSON 事件流"
|
||||
"value": "订阅并持续消费指定个人事件;Agent 使用 --flatten 输出顶层业务 NDJSON"
|
||||
},
|
||||
"availability": {
|
||||
"candidates": [
|
||||
@@ -290250,13 +290250,13 @@
|
||||
"precedence": "cobra_help",
|
||||
"selected": true,
|
||||
"source": "cobra_help",
|
||||
"value": "订阅 DingTalk 个人事件并将每条事件以 NDJSON 输出到 stdout。\n\n输出格式(事件流默认 ndjson;显式 -f json/pretty/raw 可覆盖;-f table/csv 对\n事件流无意义会 fallback 到 ndjson):\n ndjson (默认) 一行一对象,适合 jq / 管道处理\n json 每事件多行美化 JSON(必须配 --max-events 或 --duration)\n pretty 同 json,未来加颜色\n raw 仅 SDK 原始 payload,无外层封装\n compact 扁平化 + 解析嵌套 + 抽取语义字段(Agent 友好)\n\n默认使用当前 OAuth 登录态自动创建/复用个人订阅并建立个人长连接;非默认组织加\n--profile。连上后 stderr 打就绪行 [event] ready,等它出现再读 stdout;停机用\nSIGTERM、关 stdin,或先用 dws event stop \u003csubscribe_id\u003e --dry-run 预览、确认后加\n--yes,绝不要 kill -9。\n--event-types/--filter 只影响本地 bus → consume 这一段投递;普通个人事件消费\n通常不需要设置。"
|
||||
"value": "订阅 DingTalk 个人事件并将每条事件以 NDJSON 输出到 stdout。\n\n输出格式(事件流默认 ndjson;显式 -f json/pretty/raw 可覆盖;-f table/csv 对\n事件流无意义会 fallback 到 ndjson):\n ndjson (默认) 一行一对象,适合 jq / 管道处理\n json 每事件多行美化 JSON(必须配 --max-events 或 --duration)\n pretty 同 json,未来加颜色\n raw 仅 SDK 原始 payload,无外层封装\n compact 单行紧凑 JSON;不传 --flatten 时沿用原 compact processor\n\n数据结构:\n ndjson/json/pretty 默认保持 transport envelope(type/event_type/data/headers)\n --flatten 结构化格式输出稳定的顶层业务字段,适合 Agent / 脚本直接消费\n\n默认使用当前 OAuth 登录态自动创建/复用个人订阅并建立个人长连接;非默认组织加\n--profile。连上后 stderr 打就绪行 [event] ready,等它出现再读 stdout;停机用\nSIGTERM、关 stdin,或先用 dws event stop \u003csubscribe_id\u003e --dry-run 预览、确认后加\n--yes,绝不要 kill -9。\n--event-types/--filter 只影响本地 bus → consume 这一段投递;普通个人事件消费\n通常不需要设置。"
|
||||
}
|
||||
],
|
||||
"precedence": "cobra_help",
|
||||
"resolution": "highest_precedence",
|
||||
"source": "cobra_help",
|
||||
"value": "订阅 DingTalk 个人事件并将每条事件以 NDJSON 输出到 stdout。\n\n输出格式(事件流默认 ndjson;显式 -f json/pretty/raw 可覆盖;-f table/csv 对\n事件流无意义会 fallback 到 ndjson):\n ndjson (默认) 一行一对象,适合 jq / 管道处理\n json 每事件多行美化 JSON(必须配 --max-events 或 --duration)\n pretty 同 json,未来加颜色\n raw 仅 SDK 原始 payload,无外层封装\n compact 扁平化 + 解析嵌套 + 抽取语义字段(Agent 友好)\n\n默认使用当前 OAuth 登录态自动创建/复用个人订阅并建立个人长连接;非默认组织加\n--profile。连上后 stderr 打就绪行 [event] ready,等它出现再读 stdout;停机用\nSIGTERM、关 stdin,或先用 dws event stop \u003csubscribe_id\u003e --dry-run 预览、确认后加\n--yes,绝不要 kill -9。\n--event-types/--filter 只影响本地 bus → consume 这一段投递;普通个人事件消费\n通常不需要设置。"
|
||||
"value": "订阅 DingTalk 个人事件并将每条事件以 NDJSON 输出到 stdout。\n\n输出格式(事件流默认 ndjson;显式 -f json/pretty/raw 可覆盖;-f table/csv 对\n事件流无意义会 fallback 到 ndjson):\n ndjson (默认) 一行一对象,适合 jq / 管道处理\n json 每事件多行美化 JSON(必须配 --max-events 或 --duration)\n pretty 同 json,未来加颜色\n raw 仅 SDK 原始 payload,无外层封装\n compact 单行紧凑 JSON;不传 --flatten 时沿用原 compact processor\n\n数据结构:\n ndjson/json/pretty 默认保持 transport envelope(type/event_type/data/headers)\n --flatten 结构化格式输出稳定的顶层业务字段,适合 Agent / 脚本直接消费\n\n默认使用当前 OAuth 登录态自动创建/复用个人订阅并建立个人长连接;非默认组织加\n--profile。连上后 stderr 打就绪行 [event] ready,等它出现再读 stdout;停机用\nSIGTERM、关 stdin,或先用 dws event stop \u003csubscribe_id\u003e --dry-run 预览、确认后加\n--yes,绝不要 kill -9。\n--event-types/--filter 只影响本地 bus → consume 这一段投递;普通个人事件消费\n通常不需要设置。"
|
||||
},
|
||||
"effect": {
|
||||
"candidates": [
|
||||
@@ -290282,8 +290282,8 @@
|
||||
"selected": true,
|
||||
"source": "internal/cli/schema_hints/selection/event.json",
|
||||
"value": [
|
||||
"dws event consume user_im_message_receive_user --open-dingtalk-id open-example --max-events 1 --format ndjson",
|
||||
"dws event consume user_im_message_reaction_group --group cid-example --max-events 1 --format ndjson"
|
||||
"dws event consume user_im_message_receive_user --open-dingtalk-id open-example --flatten --max-events 1 --format ndjson",
|
||||
"dws event consume user_im_message_reaction_group --group cid-example --flatten --max-events 1 --format ndjson"
|
||||
]
|
||||
}
|
||||
],
|
||||
@@ -290292,8 +290292,8 @@
|
||||
"review_reason": "人工依据实时 dws schema(或 Skill/Cobra/pinned MCP 对照)决策化选型文案与门禁;不改写命令身份与参数契约;示例不含 --yes。",
|
||||
"source": "internal/cli/schema_hints/selection/event.json",
|
||||
"value": [
|
||||
"dws event consume user_im_message_receive_user --open-dingtalk-id open-example --max-events 1 --format ndjson",
|
||||
"dws event consume user_im_message_reaction_group --group cid-example --max-events 1 --format ndjson"
|
||||
"dws event consume user_im_message_receive_user --open-dingtalk-id open-example --flatten --max-events 1 --format ndjson",
|
||||
"dws event consume user_im_message_reaction_group --group cid-example --flatten --max-events 1 --format ndjson"
|
||||
]
|
||||
},
|
||||
"idempotency": {
|
||||
@@ -290444,7 +290444,7 @@
|
||||
"interface_reason": "Reviewed composite workflow: the command creates or reuses a remote personal-event subscription and coordinates the local event bus and Stream consumer; no single pinned RPC represents the workflow.",
|
||||
"is_alias": false,
|
||||
"name": "consume",
|
||||
"parameter_count": 27,
|
||||
"parameter_count": 28,
|
||||
"parameters": {
|
||||
"compact": {
|
||||
"description": "提示 bus 客户端期望 compact 渲染(语义透传,bus 仍按原 payload 投递)",
|
||||
@@ -291124,6 +291124,90 @@
|
||||
"required": false,
|
||||
"type": "string"
|
||||
},
|
||||
"flatten": {
|
||||
"description": "将个人事件 transport envelope 投影为稳定的顶层业务字段",
|
||||
"field_provenance": {
|
||||
"description": {
|
||||
"candidates": [
|
||||
{
|
||||
"precedence": "cobra_contract",
|
||||
"selected": true,
|
||||
"source": "cobra_usage",
|
||||
"value": "将个人事件 transport envelope 投影为稳定的顶层业务字段"
|
||||
},
|
||||
{
|
||||
"precedence": "default",
|
||||
"selected": false,
|
||||
"source": "default",
|
||||
"value": ""
|
||||
}
|
||||
],
|
||||
"precedence": "cobra_contract",
|
||||
"resolution": "highest_precedence",
|
||||
"source": "cobra_usage",
|
||||
"value": "将个人事件 transport envelope 投影为稳定的顶层业务字段"
|
||||
},
|
||||
"property": {
|
||||
"candidates": [
|
||||
{
|
||||
"precedence": "inference",
|
||||
"selected": true,
|
||||
"source": "flag_name_inference",
|
||||
"value": "flatten"
|
||||
}
|
||||
],
|
||||
"precedence": "inference",
|
||||
"resolution": "highest_precedence",
|
||||
"source": "flag_name_inference",
|
||||
"value": "flatten"
|
||||
},
|
||||
"required": {
|
||||
"candidates": [
|
||||
{
|
||||
"precedence": "default",
|
||||
"selected": true,
|
||||
"source": "default",
|
||||
"value": false
|
||||
}
|
||||
],
|
||||
"precedence": "default",
|
||||
"resolution": "fallback",
|
||||
"source": "default",
|
||||
"value": false
|
||||
},
|
||||
"required_when": {
|
||||
"candidates": [
|
||||
{
|
||||
"precedence": "default",
|
||||
"selected": true,
|
||||
"source": "default",
|
||||
"value": ""
|
||||
}
|
||||
],
|
||||
"precedence": "default",
|
||||
"resolution": "highest_precedence",
|
||||
"source": "default",
|
||||
"value": ""
|
||||
},
|
||||
"type": {
|
||||
"candidates": [
|
||||
{
|
||||
"precedence": "cobra_contract",
|
||||
"selected": true,
|
||||
"source": "cobra_flag_type",
|
||||
"value": "boolean"
|
||||
}
|
||||
],
|
||||
"precedence": "cobra_contract",
|
||||
"resolution": "highest_precedence",
|
||||
"source": "cobra_flag_type",
|
||||
"value": "boolean"
|
||||
}
|
||||
},
|
||||
"property": "flatten",
|
||||
"required": false,
|
||||
"type": "boolean"
|
||||
},
|
||||
"force": {
|
||||
"description": "仅 --foreground 模式生效:跳过单实例锁 (慎用:会让云事件被随机切分)",
|
||||
"field_provenance": {
|
||||
@@ -293464,7 +293548,7 @@
|
||||
"skills/mono/SKILL.md",
|
||||
"skills/mono/references/products/event.md"
|
||||
],
|
||||
"agent_summary": "查询指定个人事件码的 payload 字段结构",
|
||||
"agent_summary": "查询指定个人事件码的输出字段结构;Agent 应查询 --flatten 模式",
|
||||
"agent_summary_source": "dws-agent-selection/event",
|
||||
"availability": "available",
|
||||
"avoid_when": [
|
||||
@@ -293480,7 +293564,7 @@
|
||||
"effect": "read",
|
||||
"effect_source": "agent-hint",
|
||||
"examples": [
|
||||
"dws event schema user_im_message_receive_at --format json"
|
||||
"dws event schema user_im_message_receive_at --flatten --format json"
|
||||
],
|
||||
"field_provenance": {
|
||||
"agent_summary": {
|
||||
@@ -293490,14 +293574,14 @@
|
||||
"review_reason": "人工依据实时 dws schema(或 Skill/Cobra/pinned MCP 对照)决策化选型文案与门禁;不改写命令身份与参数契约;示例不含 --yes。",
|
||||
"selected": true,
|
||||
"source": "internal/cli/schema_hints/selection/event.json",
|
||||
"value": "查询指定个人事件码的 payload 字段结构"
|
||||
"value": "查询指定个人事件码的输出字段结构;Agent 应查询 --flatten 模式"
|
||||
}
|
||||
],
|
||||
"precedence": "reviewed_explicit",
|
||||
"resolution": "highest_precedence",
|
||||
"review_reason": "人工依据实时 dws schema(或 Skill/Cobra/pinned MCP 对照)决策化选型文案与门禁;不改写命令身份与参数契约;示例不含 --yes。",
|
||||
"source": "internal/cli/schema_hints/selection/event.json",
|
||||
"value": "查询指定个人事件码的 payload 字段结构"
|
||||
"value": "查询指定个人事件码的输出字段结构;Agent 应查询 --flatten 模式"
|
||||
},
|
||||
"availability": {
|
||||
"candidates": [
|
||||
@@ -293607,7 +293691,7 @@
|
||||
"selected": true,
|
||||
"source": "internal/cli/schema_hints/selection/event.json",
|
||||
"value": [
|
||||
"dws event schema user_im_message_receive_at --format json"
|
||||
"dws event schema user_im_message_receive_at --flatten --format json"
|
||||
]
|
||||
}
|
||||
],
|
||||
@@ -293616,7 +293700,7 @@
|
||||
"review_reason": "人工依据实时 dws schema(或 Skill/Cobra/pinned MCP 对照)决策化选型文案与门禁;不改写命令身份与参数契约;示例不含 --yes。",
|
||||
"source": "internal/cli/schema_hints/selection/event.json",
|
||||
"value": [
|
||||
"dws event schema user_im_message_receive_at --format json"
|
||||
"dws event schema user_im_message_receive_at --flatten --format json"
|
||||
]
|
||||
},
|
||||
"idempotency": {
|
||||
@@ -293763,8 +293847,92 @@
|
||||
"interface_reason": "命令读取 CLI 内置的个人事件 payload 定义,不绑定 pinned MCP RPC",
|
||||
"is_alias": false,
|
||||
"name": "schema",
|
||||
"parameter_count": 1,
|
||||
"parameter_count": 2,
|
||||
"parameters": {
|
||||
"flatten": {
|
||||
"description": "显示 --flatten 消费模式对应的顶层业务字段 schema",
|
||||
"field_provenance": {
|
||||
"description": {
|
||||
"candidates": [
|
||||
{
|
||||
"precedence": "cobra_contract",
|
||||
"selected": true,
|
||||
"source": "cobra_usage",
|
||||
"value": "显示 --flatten 消费模式对应的顶层业务字段 schema"
|
||||
},
|
||||
{
|
||||
"precedence": "default",
|
||||
"selected": false,
|
||||
"source": "default",
|
||||
"value": ""
|
||||
}
|
||||
],
|
||||
"precedence": "cobra_contract",
|
||||
"resolution": "highest_precedence",
|
||||
"source": "cobra_usage",
|
||||
"value": "显示 --flatten 消费模式对应的顶层业务字段 schema"
|
||||
},
|
||||
"property": {
|
||||
"candidates": [
|
||||
{
|
||||
"precedence": "inference",
|
||||
"selected": true,
|
||||
"source": "flag_name_inference",
|
||||
"value": "flatten"
|
||||
}
|
||||
],
|
||||
"precedence": "inference",
|
||||
"resolution": "highest_precedence",
|
||||
"source": "flag_name_inference",
|
||||
"value": "flatten"
|
||||
},
|
||||
"required": {
|
||||
"candidates": [
|
||||
{
|
||||
"precedence": "default",
|
||||
"selected": true,
|
||||
"source": "default",
|
||||
"value": false
|
||||
}
|
||||
],
|
||||
"precedence": "default",
|
||||
"resolution": "fallback",
|
||||
"source": "default",
|
||||
"value": false
|
||||
},
|
||||
"required_when": {
|
||||
"candidates": [
|
||||
{
|
||||
"precedence": "default",
|
||||
"selected": true,
|
||||
"source": "default",
|
||||
"value": ""
|
||||
}
|
||||
],
|
||||
"precedence": "default",
|
||||
"resolution": "highest_precedence",
|
||||
"source": "default",
|
||||
"value": ""
|
||||
},
|
||||
"type": {
|
||||
"candidates": [
|
||||
{
|
||||
"precedence": "cobra_contract",
|
||||
"selected": true,
|
||||
"source": "cobra_flag_type",
|
||||
"value": "boolean"
|
||||
}
|
||||
],
|
||||
"precedence": "cobra_contract",
|
||||
"resolution": "highest_precedence",
|
||||
"source": "cobra_flag_type",
|
||||
"value": "boolean"
|
||||
}
|
||||
},
|
||||
"property": "flatten",
|
||||
"required": false,
|
||||
"type": "boolean"
|
||||
},
|
||||
"format": {
|
||||
"default": "json",
|
||||
"description": "输出格式: json",
|
||||
|
||||
@@ -634,6 +634,11 @@
|
||||
"target": "event consume",
|
||||
"reason": "已审查的带 event_key 和输出参数的 Skill 引用,固定映射到当前公开 event consume leaf"
|
||||
},
|
||||
"event consume user_im_message_receive_at": {
|
||||
"status": "alias",
|
||||
"target": "event consume",
|
||||
"reason": "已审查的带 event_key 参数的 Skill 引用,固定映射到当前公开 event consume leaf"
|
||||
},
|
||||
"event consume user_im_message_receive_group": {
|
||||
"status": "alias",
|
||||
"target": "event consume",
|
||||
|
||||
@@ -585,6 +585,11 @@
|
||||
"target": "event consume",
|
||||
"reason": "已审查的带 event_key 和输出参数的 Skill 引用,固定映射到当前公开 event consume leaf"
|
||||
},
|
||||
"event consume user_im_message_receive_at": {
|
||||
"status": "alias",
|
||||
"target": "event consume",
|
||||
"reason": "已审查的带 event_key 参数的 Skill 引用,固定映射到当前公开 event consume leaf"
|
||||
},
|
||||
"event consume user_im_message_receive_group": {
|
||||
"status": "alias",
|
||||
"target": "event consume",
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
},
|
||||
"tools": {
|
||||
"event.consume": {
|
||||
"agent_summary": "订阅并持续消费指定个人事件,输出 NDJSON 事件流",
|
||||
"agent_summary": "订阅并持续消费指定个人事件;Agent 使用 --flatten 输出顶层业务 NDJSON",
|
||||
"use_when": [
|
||||
"需要实时监听 @我、指定单聊、指定群或指定发送人的后续消息事件",
|
||||
"需要监听指定单聊或群聊中的消息已读、撤回或表情回应事件",
|
||||
@@ -20,8 +20,8 @@
|
||||
"只看事件目录/字段时用 event list / event schema"
|
||||
],
|
||||
"examples": [
|
||||
"dws event consume user_im_message_receive_user --open-dingtalk-id open-example --max-events 1 --format ndjson",
|
||||
"dws event consume user_im_message_reaction_group --group cid-example --max-events 1 --format ndjson"
|
||||
"dws event consume user_im_message_receive_user --open-dingtalk-id open-example --flatten --max-events 1 --format ndjson",
|
||||
"dws event consume user_im_message_reaction_group --group cid-example --flatten --max-events 1 --format ndjson"
|
||||
],
|
||||
"reviewed": true,
|
||||
"review_reason": "人工依据实时 dws schema(或 Skill/Cobra/pinned MCP 对照)决策化选型文案与门禁;不改写命令身份与参数契约;示例不含 --yes。",
|
||||
@@ -57,7 +57,7 @@
|
||||
]
|
||||
},
|
||||
"event.schema": {
|
||||
"agent_summary": "查询指定个人事件码的 payload 字段结构",
|
||||
"agent_summary": "查询指定个人事件码的输出字段结构;Agent 应查询 --flatten 模式",
|
||||
"use_when": [
|
||||
"已知任一公开个人消息 event_key,消费前需要理解扁平输出字段"
|
||||
],
|
||||
@@ -66,7 +66,7 @@
|
||||
"要实际收事件时用 event consume"
|
||||
],
|
||||
"examples": [
|
||||
"dws event schema user_im_message_receive_at --format json"
|
||||
"dws event schema user_im_message_receive_at --flatten --format json"
|
||||
],
|
||||
"reviewed": true,
|
||||
"review_reason": "人工依据实时 dws schema(或 Skill/Cobra/pinned MCP 对照)决策化选型文案与门禁;不改写命令身份与参数契约;示例不含 --yes。",
|
||||
|
||||
@@ -90,6 +90,10 @@ type Config struct {
|
||||
// NormalizeFormat; an empty Format here defaults to NDJSON inside
|
||||
// BuildPipeline.
|
||||
Format Format
|
||||
// Flatten records whether the caller explicitly selected a structured
|
||||
// business projection. Projector remains the executable behavior; this
|
||||
// field is surfaced in dry-run output so users can verify the final mode.
|
||||
Flatten bool
|
||||
// OutputDir, if non-empty, switches the fallback sink from stdout to
|
||||
// "file per event" under this directory.
|
||||
OutputDir string
|
||||
|
||||
@@ -132,6 +132,7 @@ func PrintDryRun(w io.Writer, cfg Config) {
|
||||
fmt.Fprintf(w, " filter : %s\n", cfg.Filter)
|
||||
}
|
||||
fmt.Fprintf(w, " format : %s\n", cfg.Format)
|
||||
fmt.Fprintf(w, " flatten : %v\n", cfg.Flatten)
|
||||
if cfg.OutputDir != "" {
|
||||
fmt.Fprintf(w, " output_dir : %s\n", cfg.OutputDir)
|
||||
}
|
||||
|
||||
@@ -137,6 +137,7 @@ func TestPrintDryRun_RendersAllSetFields(t *testing.T) {
|
||||
c.EventTypes = []string{"im.*", "approval.*"}
|
||||
c.Filter = "^im\\."
|
||||
c.Format = FormatCompact
|
||||
c.Flatten = true
|
||||
c.OutputDir = "/tmp/events"
|
||||
c.Routes, _ = ParseRoutes([]string{`^im\.=dir:/tmp/im/`})
|
||||
c.MaxEvents = 5
|
||||
@@ -151,7 +152,7 @@ func TestPrintDryRun_RendersAllSetFields(t *testing.T) {
|
||||
wants := []string{
|
||||
"client_id", "workdir", "ipc_endpoint", "im.*,approval.*",
|
||||
"^im\\.", "compact", "/tmp/events", "route[0]", "max_events : 5",
|
||||
"duration", "true",
|
||||
"duration", "flatten : true", "true",
|
||||
}
|
||||
for _, w := range wants {
|
||||
if !strings.Contains(out, w) {
|
||||
|
||||
@@ -394,6 +394,39 @@ func outputSchema(eventKey string) map[string]any {
|
||||
}
|
||||
}
|
||||
|
||||
func transportEnvelopeSchema(eventKey string) map[string]any {
|
||||
eventType := reflect.TypeOf(transport.Event{})
|
||||
properties := make(map[string]any, eventType.NumField())
|
||||
for i := 0; i < eventType.NumField(); i++ {
|
||||
field := eventType.Field(i)
|
||||
name := strings.Split(field.Tag.Get("json"), ",")[0]
|
||||
property := map[string]any{"type": schemaType(field.Type)}
|
||||
switch name {
|
||||
case "type":
|
||||
property["description"] = "transport frame 类型"
|
||||
property["enum"] = []string{string(transport.FrameTypeEvent)}
|
||||
case "event_type":
|
||||
property["description"] = "事件类型"
|
||||
property["enum"] = []string{eventKey}
|
||||
case "data":
|
||||
property["description"] = "服务端业务 payload JSON 字符串"
|
||||
property["content_media_type"] = "application/json"
|
||||
case "headers":
|
||||
property["description"] = "Stream transport headers"
|
||||
property["additionalProperties"] = map[string]any{"type": "string"}
|
||||
case "event_id":
|
||||
property["description"] = "transport 事件 ID"
|
||||
case "subscribe_id":
|
||||
property["description"] = "个人事件订阅 ID"
|
||||
}
|
||||
properties[name] = property
|
||||
}
|
||||
return map[string]any{
|
||||
"type": "object",
|
||||
"properties": properties,
|
||||
}
|
||||
}
|
||||
|
||||
func outputTypeForEvent(eventKey string) reflect.Type {
|
||||
switch {
|
||||
case isMessageReceiveEvent(eventKey):
|
||||
|
||||
@@ -258,8 +258,18 @@ func Catalog(category string, enabledOnly, includePending bool) []Definition {
|
||||
}
|
||||
|
||||
func BuildSchemaDocument(def Definition) SchemaDocument {
|
||||
return BuildSchemaDocumentForMode(def, false)
|
||||
}
|
||||
|
||||
func BuildSchemaDocumentForMode(def Definition, flatten bool) SchemaDocument {
|
||||
requiredParams := make([]string, 0, len(def.RequiredParams))
|
||||
requiredParams = append(requiredParams, def.RequiredParams...)
|
||||
jqRootPath := ".data | fromjson"
|
||||
schema := transportEnvelopeSchema(def.EventKey)
|
||||
if flatten {
|
||||
jqRootPath = "."
|
||||
schema = outputSchema(def.EventKey)
|
||||
}
|
||||
return SchemaDocument{
|
||||
EventKey: def.EventKey,
|
||||
DisplayName: def.DisplayName,
|
||||
@@ -268,8 +278,8 @@ func BuildSchemaDocument(def Definition) SchemaDocument {
|
||||
RuleType: def.RuleType,
|
||||
RequiredParams: requiredParams,
|
||||
Constraints: cloneParameterConstraints(def.Constraints),
|
||||
JQRootPath: ".",
|
||||
Schema: outputSchema(def.EventKey),
|
||||
JQRootPath: jqRootPath,
|
||||
Schema: schema,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -95,7 +95,7 @@ func TestDefinitionJSONHidesInternalSchemaIDs(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestSchemaDocumentsUseSingleJSONSchema(t *testing.T) {
|
||||
func TestSchemaDocumentsDefaultToTransportEnvelope(t *testing.T) {
|
||||
for _, eventKey := range []string{EventMention, EventSingleChat, EventInChat, EventFromUser} {
|
||||
t.Run(eventKey, func(t *testing.T) {
|
||||
def, ok := Lookup(eventKey)
|
||||
@@ -117,16 +117,16 @@ func TestSchemaDocumentsUseSingleJSONSchema(t *testing.T) {
|
||||
"required_params",
|
||||
"jq_root_path",
|
||||
"schema",
|
||||
"type",
|
||||
"seq",
|
||||
"event_id",
|
||||
"timestamp",
|
||||
"event_born_time",
|
||||
"event_type",
|
||||
"subscribe_id",
|
||||
"content",
|
||||
"sender",
|
||||
"sender_open_dingtalk_id",
|
||||
"conversation_id",
|
||||
"message_id",
|
||||
"create_time",
|
||||
"event_time",
|
||||
"source_id",
|
||||
"data",
|
||||
"headers",
|
||||
"received_at_unix_ms",
|
||||
} {
|
||||
if !strings.Contains(out, want) {
|
||||
t.Fatalf("schema for %s missing %q: %s", eventKey, want, out)
|
||||
@@ -144,7 +144,6 @@ func TestSchemaDocumentsUseSingleJSONSchema(t *testing.T) {
|
||||
"payload_schema",
|
||||
"output_schema",
|
||||
"data_json_path",
|
||||
"headers",
|
||||
"audit",
|
||||
"tenant",
|
||||
"subject",
|
||||
@@ -152,13 +151,18 @@ func TestSchemaDocumentsUseSingleJSONSchema(t *testing.T) {
|
||||
"msgIdMetaq",
|
||||
"at_users",
|
||||
"sender_user_id",
|
||||
"sender_open_dingtalk_id",
|
||||
"conversation_id",
|
||||
"message_id",
|
||||
"create_time",
|
||||
"event_time",
|
||||
} {
|
||||
if strings.Contains(out, leaked) {
|
||||
t.Fatalf("schema for %s leaked %q: %s", eventKey, leaked, out)
|
||||
}
|
||||
}
|
||||
if doc.JQRootPath != "." {
|
||||
t.Fatalf("jq_root_path = %q, want .", doc.JQRootPath)
|
||||
if doc.JQRootPath != ".data | fromjson" {
|
||||
t.Fatalf("jq_root_path = %q, want .data | fromjson", doc.JQRootPath)
|
||||
}
|
||||
if doc.RequiredParams == nil {
|
||||
t.Fatalf("required_params = nil, want empty slice")
|
||||
@@ -167,6 +171,38 @@ func TestSchemaDocumentsUseSingleJSONSchema(t *testing.T) {
|
||||
if !ok {
|
||||
t.Fatalf("schema.properties = %#v, want object", doc.Schema["properties"])
|
||||
}
|
||||
wantProperties := []string{
|
||||
"type", "seq", "event_id", "event_born_time", "event_corp_id",
|
||||
"event_type", "event_unified_app_id", "event_scope", "subscribe_id",
|
||||
"source_id", "rule_type", "data", "headers", "received_at_unix_ms",
|
||||
}
|
||||
if len(props) != len(wantProperties) {
|
||||
t.Fatalf("schema.properties = %#v, want exactly %d transport fields", props, len(wantProperties))
|
||||
}
|
||||
for _, name := range wantProperties {
|
||||
if _, ok := props[name].(map[string]any); !ok {
|
||||
t.Fatalf("schema.properties.%s = %#v, want object", name, props[name])
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFlattenedSchemaDocumentsUseMessageDTO(t *testing.T) {
|
||||
for _, eventKey := range []string{EventMention, EventSingleChat, EventInChat, EventFromUser} {
|
||||
t.Run(eventKey, func(t *testing.T) {
|
||||
def, ok := Lookup(eventKey)
|
||||
if !ok {
|
||||
t.Fatalf("Lookup(%q) failed", eventKey)
|
||||
}
|
||||
doc := BuildSchemaDocumentForMode(def, true)
|
||||
if doc.JQRootPath != "." {
|
||||
t.Fatalf("jq_root_path = %q, want .", doc.JQRootPath)
|
||||
}
|
||||
props, ok := doc.Schema["properties"].(map[string]any)
|
||||
if !ok {
|
||||
t.Fatalf("schema.properties = %#v, want object", doc.Schema["properties"])
|
||||
}
|
||||
wantProperties := []string{
|
||||
"type", "event_id", "timestamp", "subscribe_id", "message_id",
|
||||
"conversation_id", "sender", "sender_open_dingtalk_id", "content",
|
||||
@@ -180,6 +216,11 @@ func TestSchemaDocumentsUseSingleJSONSchema(t *testing.T) {
|
||||
t.Fatalf("schema.properties.%s = %#v, want object", name, props[name])
|
||||
}
|
||||
}
|
||||
for _, transportField := range []string{"data", "headers", "seq", "event_type"} {
|
||||
if _, ok := props[transportField]; ok {
|
||||
t.Fatalf("flattened schema exposed transport field %q", transportField)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -286,7 +327,7 @@ func TestActionSchemaDocumentsMatchOutputDTOs(t *testing.T) {
|
||||
if !ok {
|
||||
t.Fatalf("Lookup(%q) failed", eventKey)
|
||||
}
|
||||
doc := BuildSchemaDocument(def)
|
||||
doc := BuildSchemaDocumentForMode(def, true)
|
||||
if doc.JQRootPath != "." {
|
||||
t.Fatalf("jq_root_path = %q, want .", doc.JQRootPath)
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ cli_version: ">=1.0.15"
|
||||
- 单次批量操作不超过 30 条记录
|
||||
- 所有命令必须**严格遵循**对应产品参考文档里面规定的参数格式(如:如果有参数值,则参数和参数值之间至少用一个空格隔开)
|
||||
- **脚本优先**:[scripts/](./scripts/) 下的 `python scripts/<name>.py` 已封装翻页/轮询/批量逻辑,遇到对应场景(如 AI 表格批量导入导出、AI 应用创建轮询、文档创建后写内容、钉盘目录树等)**优先调用脚本**而非手写多步命令。脚本均支持 `--dry-run` 预览、`--format json` 输出,失败时回退到手动步骤
|
||||
- **实时个人消息事件例外**:用户要监听消息、订阅事件、自动回复消息或事件驱动 Agent 时,必须走 `dws event consume` 长连接,不要写脚本轮询消息历史
|
||||
- **实时个人消息事件例外**:用户要监听消息、订阅事件、自动回复消息或事件驱动 Agent 时,必须走 `dws event consume ... --flatten` 长连接,不要写脚本轮询消息历史
|
||||
|
||||
## Shortcut 与原子命令的使用原则
|
||||
|
||||
@@ -259,7 +259,7 @@ Schema 与 Help 冲突是**契约漂移**,不得静默猜测或把两边字段
|
||||
|
||||
`dev.*` 包含 helper-only 执行面,其中远端 helper 未进入 pinned metadata 时标记为 `composite`,不能伪装成 `local`。`event list` / `event schema` 读取内置目录和 payload 定义,属于 `local`;`event consume` / `event status` / `event stop` 同时编排远端个人订阅控制面与本地 bus/consume,属于 `composite`。实现来源不同,不改变统一查询边界:进入全局 `dws schema` 的命令必须先进入 reviewed CommandRegistry,并由同一 `ToolSpec` 投影到 leaf、产品/分组、`--all` 与 Catalog。不得在查询时重新调用 MCP `tools/list`,也不得把 Cobra 临时合成结果作为第二条 Schema 数据路径。
|
||||
|
||||
事件需要区分两种 Schema:`dws event schema <event_key>` 查询事件 payload 字段;`dws schema "event consume"` 查询 CLI 命令参数。前者是真实业务命令,后者只读取最终内嵌 SchemaRegistry;不能相互替代。
|
||||
事件需要区分两种 Schema:`dws event schema <event_key> --flatten` 查询 Agent 要消费的顶层业务字段;`dws schema "event consume"` 查询 CLI 命令参数。前者是真实业务命令,后者只读取最终内嵌 SchemaRegistry;不能相互替代。
|
||||
|
||||
`source` 表示最终命令 identity 的来源,不表示运行时 backing;helper/local/MCP 实现机制读取 `interface_mode`、`availability` 和 provenance,不要假定 `dev.*` 必然是 `source=mcp:<server>`,也不要假定本地命令必然是 `source=cobra`。
|
||||
|
||||
|
||||
@@ -13,8 +13,8 @@
|
||||
|
||||
| Command | Purpose |
|
||||
|---|---|
|
||||
| `dws event schema <event_key>` | 查看事件参数和输出字段 schema |
|
||||
| `dws event consume <event_key> [flags]` | 阻塞消费,事件写到 stdout,用 `-f ndjson` |
|
||||
| `dws event schema <event_key> --flatten` | 查看 Agent 使用的顶层业务字段 schema |
|
||||
| `dws event consume <event_key> --flatten [flags]` | 阻塞消费,事件写到 stdout,用 `-f ndjson` |
|
||||
| `dws event status --event <event_key>` | 查看个人订阅、bus、本地 consume |
|
||||
| `dws event stop <subscribe_id> --dry-run` / `--yes` | 先预览,再确认取消订阅并停止对应本地消费 |
|
||||
| `dws event stop --all --dry-run` / `--yes` | 先预览,再确认清理当前身份下全部个人订阅 |
|
||||
@@ -42,20 +42,20 @@
|
||||
|
||||
| 用户说 | 下一步 |
|
||||
|---|---|
|
||||
| "监听有人 @ 我的消息" | `event consume`,事件码 `user_im_message_receive_at`,参数 `-f ndjson` |
|
||||
| "监听我和 userId test-user-001 的单聊消息" | `event consume`,事件码 `user_im_message_receive_o2o`,参数 `--user test-user-001 -f ndjson` |
|
||||
| "监听我和 openDingtalkId abc 的单聊消息" | `event consume`,事件码 `user_im_message_receive_o2o`,参数 `--open-dingtalk-id abc -f ndjson` |
|
||||
| "监听有人 @ 我的消息" | `event consume`,事件码 `user_im_message_receive_at`,参数 `--flatten -f ndjson` |
|
||||
| "监听我和 userId test-user-001 的单聊消息" | `event consume`,事件码 `user_im_message_receive_o2o`,参数 `--user test-user-001 --flatten -f ndjson` |
|
||||
| "监听我和 openDingtalkId abc 的单聊消息" | `event consume`,事件码 `user_im_message_receive_o2o`,参数 `--open-dingtalk-id abc --flatten -f ndjson` |
|
||||
| "监听 XX 群消息" | 先 `dws chat search --query "XX" --format json`,确认后 consume group |
|
||||
| "监听 userId test-user-001 发给我的消息" | `event consume`,事件码 `user_im_message_receive_user`,参数 `--user test-user-001 -f ndjson` |
|
||||
| "监听 openDingtalkId abc 发给我的消息" | `event consume`,事件码 `user_im_message_receive_user`,参数 `--open-dingtalk-id abc -f ndjson` |
|
||||
| "监听我发给 userId test-user-001 的消息是否已读" | `event consume`,事件码 `user_im_message_read_o2o`,参数 `--user test-user-001 -f ndjson` |
|
||||
| "监听 userId test-user-001 发给我的消息" | `event consume`,事件码 `user_im_message_receive_user`,参数 `--user test-user-001 --flatten -f ndjson` |
|
||||
| "监听 openDingtalkId abc 发给我的消息" | `event consume`,事件码 `user_im_message_receive_user`,参数 `--open-dingtalk-id abc --flatten -f ndjson` |
|
||||
| "监听我发给 userId test-user-001 的消息是否已读" | `event consume`,事件码 `user_im_message_read_o2o`,参数 `--user test-user-001 --flatten -f ndjson` |
|
||||
| "监听 XX 群消息已读" | 先解析群 ID,再 consume `user_im_message_read_group --group <id>` |
|
||||
| "监听我和 userId test-user-001 的消息撤回" | `event consume`,事件码 `user_im_message_recall_o2o`,参数 `--user test-user-001 -f ndjson` |
|
||||
| "监听我和 userId test-user-001 的消息撤回" | `event consume`,事件码 `user_im_message_recall_o2o`,参数 `--user test-user-001 --flatten -f ndjson` |
|
||||
| "监听 XX 群消息撤回" | 先解析群 ID,再 consume `user_im_message_recall_group --group <id>` |
|
||||
| "监听我和 userId test-user-001 的消息贴表情" | `event consume`,事件码 `user_im_message_reaction_o2o`,参数 `--user test-user-001 -f ndjson` |
|
||||
| "监听我和 userId test-user-001 的消息贴表情" | `event consume`,事件码 `user_im_message_reaction_o2o`,参数 `--user test-user-001 --flatten -f ndjson` |
|
||||
| "监听 XX 群消息表情回应" | 先解析群 ID,再 consume `user_im_message_reaction_group --group <id>` |
|
||||
| "监听并自动回复某人的单聊消息" | 先解析对端 userId,再启动 o2o consume;不要写轮询脚本 |
|
||||
| "查看个人消息事件 schema" | `dws event schema <event_key>` |
|
||||
| "查看个人消息事件 schema" | `dws event schema <event_key> --flatten` |
|
||||
| "看个人事件订阅状态" | `dws event status --event <event_key>` |
|
||||
| "停止这个个人事件订阅" | `dws event stop <subscribe_id> --dry-run`,确认后改用 `--yes` |
|
||||
|
||||
@@ -66,8 +66,8 @@
|
||||
## Call flow
|
||||
|
||||
1. 从用户意图选择事件码;人名或群名先解析成必填 ID。
|
||||
2. 需要了解字段时运行 `dws event schema <event_key>`,读取 `schema.properties`;`jq_root_path` 当前固定为 `.`。
|
||||
3. 启动 `dws event consume <event_key> ... -f ndjson`,等待 stderr 出现 `[event] ready event_key=<key> bus_pid=<pid> subscribe_id=<id>` 后开始处理 stdout,不要用 `sleep` 猜测。
|
||||
2. 需要了解字段时运行 `dws event schema <event_key> --flatten`,读取 `schema.properties`;此模式的 `jq_root_path` 为 `.`。
|
||||
3. 启动 `dws event consume <event_key> ... --flatten -f ndjson`,等待 stderr 出现 `[event] ready event_key=<key> bus_pid=<pid> subscribe_id=<id>` 后开始处理 stdout,不要用 `sleep` 猜测。
|
||||
4. stdout 每行是一个扁平事件 JSON;直接按该事件的 `schema.properties` 读取顶层字段。
|
||||
5. 需要确认监听状态时运行 `dws event status --event <event_key>`,查看 `Subscriptions` 和 `Consumers`。
|
||||
6. 任务完成后优雅结束 consume;本次新建的订阅会自动取消。复用已有订阅或需要从外部主动取消时,先运行 `dws event stop <subscribe_id> --dry-run`,向用户确认后再以 `--yes` 执行;自测可在 consume 加 `--max-events` 或 `--duration` 自动退出。
|
||||
@@ -75,31 +75,31 @@
|
||||
## Commands
|
||||
|
||||
```bash
|
||||
dws event schema user_im_message_receive_at
|
||||
dws event schema user_im_message_receive_o2o
|
||||
dws event schema user_im_message_receive_group
|
||||
dws event schema user_im_message_receive_user
|
||||
dws event schema user_im_message_read_o2o
|
||||
dws event schema user_im_message_read_group
|
||||
dws event schema user_im_message_recall_o2o
|
||||
dws event schema user_im_message_recall_group
|
||||
dws event schema user_im_message_reaction_o2o
|
||||
dws event schema user_im_message_reaction_group
|
||||
dws event schema user_im_message_receive_at --flatten
|
||||
dws event schema user_im_message_receive_o2o --flatten
|
||||
dws event schema user_im_message_receive_group --flatten
|
||||
dws event schema user_im_message_receive_user --flatten
|
||||
dws event schema user_im_message_read_o2o --flatten
|
||||
dws event schema user_im_message_read_group --flatten
|
||||
dws event schema user_im_message_recall_o2o --flatten
|
||||
dws event schema user_im_message_recall_group --flatten
|
||||
dws event schema user_im_message_reaction_o2o --flatten
|
||||
dws event schema user_im_message_reaction_group --flatten
|
||||
```
|
||||
|
||||
```bash
|
||||
dws event consume user_im_message_receive_at -f ndjson
|
||||
dws event consume user_im_message_receive_o2o --user test-user-001 -f ndjson
|
||||
dws event consume user_im_message_receive_o2o --open-dingtalk-id abc -f ndjson
|
||||
dws event consume user_im_message_receive_group --group <openConversationId> -f ndjson
|
||||
dws event consume user_im_message_receive_user --user test-user-001 -f ndjson
|
||||
dws event consume user_im_message_receive_user --open-dingtalk-id abc -f ndjson
|
||||
dws event consume user_im_message_read_o2o --user test-user-001 -f ndjson
|
||||
dws event consume user_im_message_read_group --group <openConversationId> -f ndjson
|
||||
dws event consume user_im_message_recall_o2o --user test-user-001 -f ndjson
|
||||
dws event consume user_im_message_recall_group --group <openConversationId> -f ndjson
|
||||
dws event consume user_im_message_reaction_o2o --user test-user-001 -f ndjson
|
||||
dws event consume user_im_message_reaction_group --group <openConversationId> -f ndjson
|
||||
dws event consume user_im_message_receive_at --flatten -f ndjson
|
||||
dws event consume user_im_message_receive_o2o --user test-user-001 --flatten -f ndjson
|
||||
dws event consume user_im_message_receive_o2o --open-dingtalk-id abc --flatten -f ndjson
|
||||
dws event consume user_im_message_receive_group --group <openConversationId> --flatten -f ndjson
|
||||
dws event consume user_im_message_receive_user --user test-user-001 --flatten -f ndjson
|
||||
dws event consume user_im_message_receive_user --open-dingtalk-id abc --flatten -f ndjson
|
||||
dws event consume user_im_message_read_o2o --user test-user-001 --flatten -f ndjson
|
||||
dws event consume user_im_message_read_group --group <openConversationId> --flatten -f ndjson
|
||||
dws event consume user_im_message_recall_o2o --user test-user-001 --flatten -f ndjson
|
||||
dws event consume user_im_message_recall_group --group <openConversationId> --flatten -f ndjson
|
||||
dws event consume user_im_message_reaction_o2o --user test-user-001 --flatten -f ndjson
|
||||
dws event consume user_im_message_reaction_group --group <openConversationId> --flatten -f ndjson
|
||||
```
|
||||
|
||||
上述所有 `*_o2o` 命令和 `user_im_message_receive_user` 都可将 `--user <userId>` 替换为 `--open-dingtalk-id <openDingtalkId>`,但两个参数不能同时使用。
|
||||
@@ -125,10 +125,10 @@ dws event stop --all --yes
|
||||
|
||||
## Output parsing
|
||||
|
||||
- 推荐 `-f ndjson`:一行一个事件 JSON,适合 Agent 管道读取。
|
||||
- 人工取样可用 `-f json --max-events 1`。
|
||||
- `jq_root_path` 当前为 `.`;消息正文、发送人和会话 ID 分别直接读取顶层 `content`、`sender`、`conversation_id`。
|
||||
- 不要生成 `fromjson` 或内部 payload 路径。正常处理直接持续读取 stdout,不要改写为 `--output-dir` watcher。
|
||||
- 推荐 `--flatten -f ndjson`:顶层业务字段,一行一个事件 JSON,适合 Agent 管道读取。
|
||||
- 人工取样可用 `--flatten -f json --max-events 1`。`--format` 只控制序列化,`--flatten` 控制数据结构。
|
||||
- `--flatten` 的 `jq_root_path` 为 `.`;消息正文、发送人和会话 ID 分别直接读取顶层 `content`、`sender`、`conversation_id`。
|
||||
- Agent 已显式使用 `--flatten`,不要再生成 `fromjson` 或内部 payload 路径。不传时默认保持兼容 envelope,业务 payload 在 `.data | fromjson`。正常处理直接持续读取 stdout,不要改写为 `--output-dir` watcher。
|
||||
- 群自动回复使用顶层 `conversation_id`;单聊自动回复使用顶层 `sender_open_dingtalk_id`。
|
||||
- 已读事件直接读取 `reader/reader_open_dingtalk_id/read_time`;撤回事件读取 `recaller/recaller_open_dingtalk_id/recall_time`。
|
||||
- 表情回应事件直接读取 `operator/operator_open_dingtalk_id/reaction_name/reaction_text/operation_type/operation_time`。
|
||||
@@ -136,7 +136,7 @@ dws event stop --all --yes
|
||||
- 正常动作事件输出不含内部 `payload/uid/corpid/clientId/filterSubId/bizid`;原始排查才使用 `-f raw` 或 `--debug-raw-events`。
|
||||
- 自己发的消息不作为事件回来(`isSelfLoop` 过滤);自发验证会看到 0 事件,测试投递使用别人或机器人发消息。
|
||||
- `--jq <表达式>` 可进一步过滤或投影扁平输出。
|
||||
- `--debug-raw-events` 仅用于服务端联调,正常消费不要使用。
|
||||
- `--debug-raw-events` 仅用于服务端联调,正常消费不要使用;它和 `--flatten` 互斥,`-f raw` 也不能与 `--flatten` 同时使用。
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
|
||||
@@ -19,8 +19,8 @@ description: 钉钉个人 IM 事件长连接监听、订阅与消费,覆盖消
|
||||
| Command | Purpose |
|
||||
|---|---|
|
||||
| `dws event list` | 查看当前个人事件目录;不要把它当能力菜单主动展示 |
|
||||
| `dws event schema <event_key>` | 查看事件参数和输出字段 schema,默认 JSON |
|
||||
| `dws event consume <event_key> [flags]` | 阻塞消费;事件写到 stdout,推荐 `-f ndjson` |
|
||||
| `dws event schema <event_key> --flatten` | 查看 Agent 使用的顶层业务字段 schema,默认 JSON |
|
||||
| `dws event consume <event_key> --flatten [flags]` | 阻塞消费;事件写到 stdout,推荐 `-f ndjson` |
|
||||
| `dws event status --event <event_key>` | 查看个人订阅、personal bus 和本地 consume |
|
||||
| `dws event stop <subscribe_id> --dry-run` / `--yes` | 先预览,再确认取消个人订阅并停止对应本地消费 |
|
||||
| `dws event stop --all --dry-run` / `--yes` | 先预览,再确认清理当前身份下本地记录的全部个人订阅 |
|
||||
@@ -57,17 +57,17 @@ description: 钉钉个人 IM 事件长连接监听、订阅与消费,覆盖消
|
||||
- 用户只给群名时,先运行 `dws chat search --query "<group>" --format json` 解析 openConversationId;多候选必须让用户确认。
|
||||
- 用户要求执行“撤回消息”时使用 `dws chat`;只有“监听/订阅消息撤回”才使用 `dws event consume user_im_message_recall_*`。
|
||||
- 用户说“贴标签”且语义是给消息贴表情时,按消息表情回应事件处理,event key 使用 `reaction`。
|
||||
- 正常 Agent 消费使用 `-f ndjson`。抓一条样本可用 `--max-events 1 -f json`。
|
||||
- 正常 Agent 消费统一显式使用 `--flatten -f ndjson`。抓一条样本可用 `--flatten --max-events 1 -f json`。`--format` 只控制 JSON 序列化,`--flatten` 才控制数据结构。
|
||||
- 监听非默认组织时带 `--profile <corpId 或 profile 名>`;漏传会退回默认 profile 而失败。
|
||||
- 自己发的消息不作为事件回来(`isSelfLoop` 过滤):边监听边 `dws chat message send` 回复不成环;测试投递用别人 / 机器人发(自发会看到 0 事件)。
|
||||
- `--debug-raw-events` 只用于联调确认服务端推送是否到达本地连接;正常任务不要使用。
|
||||
- `--debug-raw-events` 只用于联调确认服务端推送是否到达本地连接;正常任务不要使用。它和 `--flatten` 互斥,`-f raw` 也不能与 `--flatten` 同时使用。
|
||||
- 排查:consume 报 bus 启动失败 → 报错已带真实原因,先查 `dws --profile <x> auth status`(非默认组织带对 `--profile`);本地日志见 `~/.dws/events/<edition>/personal_stream/<hash>/bus.log`(`hash` 见 `dws event status` 的 Workdir);有残留先用 `dws event stop --all --dry-run` 预览,确认后加 `--yes` 清理。看着"挂住"无输出多是误加了 `--foreground`(那是跑 bus、不打印事件),去掉即可。
|
||||
|
||||
## Call flow
|
||||
|
||||
1. 从用户意图选择事件码;人名或群名先解析成必填 ID。
|
||||
2. 需要了解字段时运行 `dws event schema <event_key>`,读取 `schema.properties`;`jq_root_path` 当前固定为 `.`。
|
||||
3. 启动 `dws event consume <event_key> ... -f ndjson`,等待 stderr 出现 `[event] ready event_key=<key> bus_pid=<pid> subscribe_id=<id>` 后开始处理 stdout,不要用 `sleep` 猜测。
|
||||
2. 需要了解字段时运行 `dws event schema <event_key> --flatten`,读取 `schema.properties`;此模式的 `jq_root_path` 为 `.`。
|
||||
3. 启动 `dws event consume <event_key> ... --flatten -f ndjson`,等待 stderr 出现 `[event] ready event_key=<key> bus_pid=<pid> subscribe_id=<id>` 后开始处理 stdout,不要用 `sleep` 猜测。
|
||||
4. stdout 每行是一个扁平事件 JSON;直接按该事件的 `schema.properties` 读取顶层字段。
|
||||
5. 需要确认监听状态时运行 `dws event status --event <event_key>`,查看 `Subscriptions` 和 `Consumers`。
|
||||
6. 任务完成后优雅结束 consume;本次新建的订阅会自动取消。复用已有订阅或需要从外部主动取消时,先用 `dws event stop <subscribe_id> --dry-run` 预览,向用户确认后再加 `--yes`;临时测试可用 `--max-events` 或 `--duration` 自动退出。
|
||||
@@ -88,57 +88,67 @@ description: 钉钉个人 IM 事件长连接监听、订阅与消费,覆盖消
|
||||
|
||||
```bash
|
||||
# 当前用户被 @ 的消息
|
||||
dws event consume user_im_message_receive_at -f ndjson
|
||||
dws event consume user_im_message_receive_at --flatten -f ndjson
|
||||
|
||||
# 当前用户与指定用户的单聊消息
|
||||
dws event consume user_im_message_receive_o2o \
|
||||
--user test-user-001 \
|
||||
--flatten \
|
||||
-f ndjson
|
||||
|
||||
# 使用 openDingtalkId 监听外部联系人、机器人或跨组织身份的单聊消息
|
||||
dws event consume user_im_message_receive_o2o \
|
||||
--open-dingtalk-id open-user-1 \
|
||||
--flatten \
|
||||
-f ndjson
|
||||
|
||||
# 指定群聊/会话消息
|
||||
dws event consume user_im_message_receive_group \
|
||||
--group cidxxxxxxxx \
|
||||
--flatten \
|
||||
-f ndjson
|
||||
|
||||
# 指定发送人的消息(单聊和群聊)
|
||||
dws event consume user_im_message_receive_user \
|
||||
--user test-user-001 \
|
||||
--flatten \
|
||||
-f ndjson
|
||||
|
||||
# 使用 openDingtalkId 监听指定发送人的消息
|
||||
dws event consume user_im_message_receive_user \
|
||||
--open-dingtalk-id open-user-1 \
|
||||
--flatten \
|
||||
-f ndjson
|
||||
|
||||
# 指定单聊消息已读
|
||||
dws event consume user_im_message_read_o2o \
|
||||
--user test-user-001 \
|
||||
--flatten \
|
||||
-f ndjson
|
||||
|
||||
# 指定群聊消息撤回
|
||||
dws event consume user_im_message_recall_group \
|
||||
--group cidxxxxxxxx \
|
||||
--flatten \
|
||||
-f ndjson
|
||||
|
||||
# 指定单聊消息收到表情回应
|
||||
dws event consume user_im_message_reaction_o2o \
|
||||
--user test-user-001 \
|
||||
--flatten \
|
||||
-f ndjson
|
||||
|
||||
# 有界自测
|
||||
dws event consume user_im_message_receive_at \
|
||||
--duration 10m \
|
||||
--flatten \
|
||||
-f ndjson
|
||||
|
||||
# 抓一条样本
|
||||
dws event consume user_im_message_receive_o2o \
|
||||
--user test-user-001 \
|
||||
--max-events 1 \
|
||||
--flatten \
|
||||
-f json
|
||||
```
|
||||
|
||||
@@ -146,10 +156,10 @@ dws event consume user_im_message_receive_o2o \
|
||||
|
||||
## 输出处理
|
||||
|
||||
- `dws event schema <event_key>` 是写解析逻辑的依据。
|
||||
- 顶层 `jq_root_path` 说明业务字段起点;当前值是 `.`。
|
||||
- `dws event schema <event_key> --flatten` 是 Agent 写解析逻辑的依据。
|
||||
- `--flatten` 模式的顶层 `jq_root_path` 为 `.`;不传时为兼容存量脚本的 transport envelope,业务 payload 在 `.data | fromjson`。
|
||||
- `schema.properties` 是业务字段列表,例如 `content`、`sender`、`conversation_id`、`message_id`、`event_time`。
|
||||
- 所有公开事件都是扁平业务对象,直接读取顶层字段;不要生成 `fromjson` 或内部 payload 路径。
|
||||
- Agent 命令已显式传 `--flatten`,直接读取顶层字段;不要对该模式再生成 `fromjson` 或内部 payload 路径。
|
||||
- 群自动回复使用事件顶层 `conversation_id`;单聊自动回复使用顶层 `sender_open_dingtalk_id`。
|
||||
- 已读事件读取顶层 `reader`、`reader_open_dingtalk_id`、`read_time`;撤回事件读取 `recaller`、`recaller_open_dingtalk_id`、`recall_time`。
|
||||
- 表情回应事件读取顶层 `operator`、`operator_open_dingtalk_id`、`reaction_name`、`reaction_text`、`operation_type`、`operation_time`。
|
||||
|
||||
@@ -15,19 +15,19 @@ dws auth login
|
||||
查看事件 schema:
|
||||
|
||||
```bash
|
||||
dws event schema user_im_message_receive_at
|
||||
dws event schema user_im_message_receive_o2o
|
||||
dws event schema user_im_message_receive_group
|
||||
dws event schema user_im_message_receive_user
|
||||
dws event schema user_im_message_read_o2o
|
||||
dws event schema user_im_message_read_group
|
||||
dws event schema user_im_message_recall_o2o
|
||||
dws event schema user_im_message_recall_group
|
||||
dws event schema user_im_message_reaction_o2o
|
||||
dws event schema user_im_message_reaction_group
|
||||
dws event schema user_im_message_receive_at --flatten
|
||||
dws event schema user_im_message_receive_o2o --flatten
|
||||
dws event schema user_im_message_receive_group --flatten
|
||||
dws event schema user_im_message_receive_user --flatten
|
||||
dws event schema user_im_message_read_o2o --flatten
|
||||
dws event schema user_im_message_read_group --flatten
|
||||
dws event schema user_im_message_recall_o2o --flatten
|
||||
dws event schema user_im_message_recall_group --flatten
|
||||
dws event schema user_im_message_reaction_o2o --flatten
|
||||
dws event schema user_im_message_reaction_group --flatten
|
||||
```
|
||||
|
||||
schema 默认 JSON。业务字段说明在 `schema.properties`,`jq_root_path` 当前固定为 `.`。
|
||||
schema 默认 JSON。Agent 使用 `--flatten` schema,业务字段在 `schema.properties`,`jq_root_path` 为 `.`。不传 `--flatten` 时查看兼容 transport envelope,其 `jq_root_path` 为 `.data | fromjson`。
|
||||
|
||||
## Event catalog
|
||||
|
||||
@@ -62,61 +62,72 @@ schema 默认 JSON。业务字段说明在 `schema.properties`,`jq_root_path`
|
||||
|
||||
```bash
|
||||
# 被 @ 消息
|
||||
dws event consume user_im_message_receive_at -f ndjson
|
||||
dws event consume user_im_message_receive_at --flatten -f ndjson
|
||||
|
||||
# 指定单聊消息
|
||||
dws event consume user_im_message_receive_o2o \
|
||||
--user test-user-001 \
|
||||
--flatten \
|
||||
-f ndjson
|
||||
|
||||
# 通过 openDingtalkId 指定单聊对端
|
||||
dws event consume user_im_message_receive_o2o \
|
||||
--open-dingtalk-id open-user-1 \
|
||||
--flatten \
|
||||
-f ndjson
|
||||
|
||||
# 指定群消息
|
||||
dws event consume user_im_message_receive_group \
|
||||
--group cidxxxxxxxx \
|
||||
--flatten \
|
||||
-f ndjson
|
||||
|
||||
# 指定发送人的消息(单聊和群聊)
|
||||
dws event consume user_im_message_receive_user \
|
||||
--user test-user-001 \
|
||||
--flatten \
|
||||
-f ndjson
|
||||
|
||||
# 通过 openDingtalkId 指定发送人
|
||||
dws event consume user_im_message_receive_user \
|
||||
--open-dingtalk-id open-user-1 \
|
||||
--flatten \
|
||||
-f ndjson
|
||||
|
||||
# 指定单聊已读事件
|
||||
dws event consume user_im_message_read_o2o \
|
||||
--user test-user-001 \
|
||||
--flatten \
|
||||
-f ndjson
|
||||
|
||||
# 指定群聊已读事件
|
||||
dws event consume user_im_message_read_group \
|
||||
--group cidxxxxxxxx \
|
||||
--flatten \
|
||||
-f ndjson
|
||||
|
||||
# 指定单聊撤回事件
|
||||
dws event consume user_im_message_recall_o2o \
|
||||
--user test-user-001 \
|
||||
--flatten \
|
||||
-f ndjson
|
||||
|
||||
# 指定群聊撤回事件
|
||||
dws event consume user_im_message_recall_group \
|
||||
--group cidxxxxxxxx \
|
||||
--flatten \
|
||||
-f ndjson
|
||||
|
||||
# 指定单聊表情回应事件
|
||||
dws event consume user_im_message_reaction_o2o \
|
||||
--user test-user-001 \
|
||||
--flatten \
|
||||
-f ndjson
|
||||
|
||||
# 指定群聊表情回应事件
|
||||
dws event consume user_im_message_reaction_group \
|
||||
--group cidxxxxxxxx \
|
||||
--flatten \
|
||||
-f ndjson
|
||||
```
|
||||
|
||||
@@ -124,16 +135,16 @@ dws event consume user_im_message_reaction_group \
|
||||
|
||||
| 事件码 | 自测参数 | 触发方式 |
|
||||
|---|---|---|
|
||||
| `user_im_message_receive_at` | `--duration 10m -f ndjson` | 让任意可触达用户在群里 @ 当前登录用户 |
|
||||
| `user_im_message_receive_o2o` | `--user <userId>` 或 `--open-dingtalk-id <id>`,加 `--duration 10m -f ndjson` | 让对端用户给当前登录用户发送单聊消息 |
|
||||
| `user_im_message_receive_group` | `--group <openConversationId> --duration 10m -f ndjson` | 让任意用户在该群发送消息 |
|
||||
| `user_im_message_receive_user` | `--user <userId>` 或 `--open-dingtalk-id <id>`,加 `--duration 10m -f ndjson` | 让指定用户分别在单聊或共同群聊中发送消息 |
|
||||
| `user_im_message_read_o2o` | `--user <userId>` 或 `--open-dingtalk-id <id>`,加 `--duration 10m -f ndjson` | 当前用户给对端发送单聊消息,再让对端打开并阅读 |
|
||||
| `user_im_message_read_group` | `--group <openConversationId> --duration 10m -f ndjson` | 当前用户在群内发送消息,再让群成员打开并阅读 |
|
||||
| `user_im_message_recall_o2o` | `--user <userId>` 或 `--open-dingtalk-id <id>`,加 `--duration 10m -f ndjson` | 在指定单聊中发送并撤回一条消息 |
|
||||
| `user_im_message_recall_group` | `--group <openConversationId> --duration 10m -f ndjson` | 在指定群聊中发送并撤回一条消息 |
|
||||
| `user_im_message_reaction_o2o` | `--user <userId>` 或 `--open-dingtalk-id <id>`,加 `--duration 10m -f ndjson` | 在指定单聊中给消息添加表情回应 |
|
||||
| `user_im_message_reaction_group` | `--group <openConversationId> --duration 10m -f ndjson` | 在指定群聊中给消息添加表情回应 |
|
||||
| `user_im_message_receive_at` | `--flatten --duration 10m -f ndjson` | 让任意可触达用户在群里 @ 当前登录用户 |
|
||||
| `user_im_message_receive_o2o` | `--user <userId>` 或 `--open-dingtalk-id <id>`,加 `--flatten --duration 10m -f ndjson` | 让对端用户给当前登录用户发送单聊消息 |
|
||||
| `user_im_message_receive_group` | `--group <openConversationId> --flatten --duration 10m -f ndjson` | 让任意用户在该群发送消息 |
|
||||
| `user_im_message_receive_user` | `--user <userId>` 或 `--open-dingtalk-id <id>`,加 `--flatten --duration 10m -f ndjson` | 让指定用户分别在单聊或共同群聊中发送消息 |
|
||||
| `user_im_message_read_o2o` | `--user <userId>` 或 `--open-dingtalk-id <id>`,加 `--flatten --duration 10m -f ndjson` | 当前用户给对端发送单聊消息,再让对端打开并阅读 |
|
||||
| `user_im_message_read_group` | `--group <openConversationId> --flatten --duration 10m -f ndjson` | 当前用户在群内发送消息,再让群成员打开并阅读 |
|
||||
| `user_im_message_recall_o2o` | `--user <userId>` 或 `--open-dingtalk-id <id>`,加 `--flatten --duration 10m -f ndjson` | 在指定单聊中发送并撤回一条消息 |
|
||||
| `user_im_message_recall_group` | `--group <openConversationId> --flatten --duration 10m -f ndjson` | 在指定群聊中发送并撤回一条消息 |
|
||||
| `user_im_message_reaction_o2o` | `--user <userId>` 或 `--open-dingtalk-id <id>`,加 `--flatten --duration 10m -f ndjson` | 在指定单聊中给消息添加表情回应 |
|
||||
| `user_im_message_reaction_group` | `--group <openConversationId> --flatten --duration 10m -f ndjson` | 在指定群聊中给消息添加表情回应 |
|
||||
|
||||
stderr 出现固定就绪行 `[event] ready event_key=<key> bus_pid=<pid> subscribe_id=<id>` 表示本地 consume 已连接到事件 bus;父进程等这行再读 stdout。stdout 每行是一个扁平事件 JSON。
|
||||
|
||||
@@ -141,7 +152,8 @@ stderr 出现固定就绪行 `[event] ready event_key=<key> bus_pid=<pid> subscr
|
||||
|
||||
| 参数 | 用途 |
|
||||
|---|---|
|
||||
| `-f ndjson` | 推荐输出,一行一个事件 JSON |
|
||||
| `--flatten` | 将 `ndjson/json/pretty` 的默认 transport envelope(或原 compact processor)投影为 Agent 可直接读取的顶层业务字段;不能与 `-f raw` 或 `--debug-raw-events` 同时使用 |
|
||||
| `-f ndjson` | 控制序列化为一行一个 JSON;不改变数据结构 |
|
||||
| `-f json` | 人工查看单条或少量样本;必须配合 `--max-events` 或 `--duration` |
|
||||
| `--max-events <n>` | 收到 N 条后退出 |
|
||||
| `--duration <duration>` | 到时退出,例如 `30s`、`10m` |
|
||||
@@ -153,11 +165,11 @@ stderr 出现固定就绪行 `[event] ready event_key=<key> bus_pid=<pid> subscr
|
||||
| `--filter-json <json>` | 使用个人事件 Filter DSL 过滤 |
|
||||
| `--debug-raw-events` | 联调用:绕过本地过滤,输出当前 personal stream 实际收到的可解析事件 |
|
||||
|
||||
正常 Agent 消费不要使用 `--debug-raw-events`。它会输出当前连接收到的所有可解析事件,只用于判断服务端是否推到了本机连接。
|
||||
正常 Agent 消费不要使用 `--debug-raw-events`。它会输出当前连接收到的所有可解析事件,只用于判断服务端是否推到了本机连接,并且不能与 `--flatten` 同时使用。
|
||||
|
||||
## Output parsing
|
||||
|
||||
`-f ndjson` 的 stdout 每行就是一个扁平业务事件对象。消息接收事件常见顶层字段:
|
||||
Agent 使用 `--flatten -f ndjson`,stdout 每行是一个扁平业务事件对象。消息接收事件常见顶层字段:
|
||||
|
||||
| 字段 | 说明 |
|
||||
|---|---|
|
||||
@@ -173,7 +185,7 @@ stderr 出现固定就绪行 `[event] ready event_key=<key> bus_pid=<pid> subscr
|
||||
| `create_time` | 消息创建时间 |
|
||||
| `event_time` | 消息事件时间戳 |
|
||||
|
||||
直接按顶层字段解析,不要使用 `fromjson`,也不要依赖内部 transport payload 路径。图片、文件等媒体消息的 `content` 可能是可读描述;需要实际媒体文件时调用 `dws chat message download-media`。
|
||||
在 `--flatten` 模式下直接按顶层字段解析,不要再使用 `fromjson` 或内部 payload 路径。不传 `--flatten` 时保持兼容 transport envelope,字段为 `type/event_type/data/headers`,业务 payload 需从 `.data | fromjson` 读取。图片、文件等媒体消息的 `content` 可能是可读描述;需要实际媒体文件时调用 `dws chat message download-media`。
|
||||
|
||||
所有动作事件都包含顶层 `type`、`event_id`、`timestamp`、`subscribe_id`、`message_id`、`conversation_id`、`sender`、`sender_open_dingtalk_id` 和 `event_time`。各类动作的专有字段如下:
|
||||
|
||||
@@ -204,6 +216,7 @@ stderr 出现固定就绪行 `[event] ready event_key=<key> bus_pid=<pid> subscr
|
||||
dws event consume user_im_message_receive_group \
|
||||
--group cidxxxxxxxx \
|
||||
--query "报警,故障" \
|
||||
--flatten \
|
||||
-f ndjson
|
||||
```
|
||||
|
||||
|
||||
+15
-15
@@ -1000,27 +1000,27 @@ Agent 安装 dws skill 后,仅依据 skill 提供的参考文档,将自然
|
||||
|
||||
**event_event_consume_at_001**
|
||||
- Prompt: 监听有人 @ 我的消息
|
||||
- Expected: `dws event consume user_im_message_receive_at -f ndjson`
|
||||
- Expected: `dws event consume user_im_message_receive_at --flatten -f ndjson`
|
||||
|
||||
#### `dws event consume user_im_message_receive_o2o`
|
||||
|
||||
**event_event_consume_o2o_001**
|
||||
- Prompt: 监听我和 userId test-user-001 的单聊消息
|
||||
- Expected: `dws event consume user_im_message_receive_o2o --user test-user-001 -f ndjson`
|
||||
- Expected: `dws event consume user_im_message_receive_o2o --user test-user-001 --flatten -f ndjson`
|
||||
- Flags: `--user` = `test-user-001`
|
||||
|
||||
**event_event_consume_o2o_open_id_001**
|
||||
- Prompt: 监听我和 openDingtalkId abc 的单聊消息
|
||||
- Expected: `dws event consume user_im_message_receive_o2o --open-dingtalk-id abc -f ndjson`
|
||||
- Expected: `dws event consume user_im_message_receive_o2o --open-dingtalk-id abc --flatten -f ndjson`
|
||||
- Flags: `--open-dingtalk-id` = `abc`
|
||||
|
||||
**event_event_consume_o2o_003** `[ASK_USER]`
|
||||
- Prompt: 监听我的个人单聊消息
|
||||
- Expected: `dws event consume user_im_message_receive_o2o -f ndjson`
|
||||
- Expected: `dws event consume user_im_message_receive_o2o --flatten -f ndjson`
|
||||
|
||||
**event_event_consume_o2o_auto_reply_001**
|
||||
- Prompt: 监听我和 userId test-user-001 的单聊消息,并对方发什么自动回复什么
|
||||
- Expected: `dws event consume user_im_message_receive_o2o --user test-user-001 -f ndjson`
|
||||
- Expected: `dws event consume user_im_message_receive_o2o --user test-user-001 --flatten -f ndjson`
|
||||
- Flags: `--user` = `test-user-001`
|
||||
- Contract: 等待 stderr 的 `[event] ready`;从每行 NDJSON 顶层读取 `content` 和 `sender_open_dingtalk_id`,持续读取 stdout,不使用轮询或 output-dir watcher
|
||||
|
||||
@@ -1028,7 +1028,7 @@ Agent 安装 dws skill 后,仅依据 skill 提供的参考文档,将自然
|
||||
|
||||
**event_event_consume_group_001**
|
||||
- Prompt: 监听 openConversationId cid123 的群消息
|
||||
- Expected: `dws event consume user_im_message_receive_group --group cid123 -f ndjson`
|
||||
- Expected: `dws event consume user_im_message_receive_group --group cid123 --flatten -f ndjson`
|
||||
- Flags: `--group` = `cid123`
|
||||
- Contract: 群自动回复时直接读取事件顶层 `conversation_id`
|
||||
|
||||
@@ -1036,19 +1036,19 @@ Agent 安装 dws skill 后,仅依据 skill 提供的参考文档,将自然
|
||||
|
||||
**event_event_consume_user_001**
|
||||
- Prompt: 监听 userId test-user-001 发给我的消息,包括单聊和群聊
|
||||
- Expected: `dws event consume user_im_message_receive_user --user test-user-001 -f ndjson`
|
||||
- Expected: `dws event consume user_im_message_receive_user --user test-user-001 --flatten -f ndjson`
|
||||
- Flags: `--user` = `test-user-001`
|
||||
|
||||
**event_event_consume_user_open_id_001**
|
||||
- Prompt: 监听 openDingtalkId abc 发给我的消息,包括单聊和群聊
|
||||
- Expected: `dws event consume user_im_message_receive_user --open-dingtalk-id abc -f ndjson`
|
||||
- Expected: `dws event consume user_im_message_receive_user --open-dingtalk-id abc --flatten -f ndjson`
|
||||
- Flags: `--open-dingtalk-id` = `abc`
|
||||
|
||||
#### `dws event consume user_im_message_read_o2o`
|
||||
|
||||
**event_event_consume_read_o2o_001**
|
||||
- Prompt: 监听我发给 userId test-user-001 的单聊消息是否已读
|
||||
- Expected: `dws event consume user_im_message_read_o2o --user test-user-001 -f ndjson`
|
||||
- Expected: `dws event consume user_im_message_read_o2o --user test-user-001 --flatten -f ndjson`
|
||||
- Flags: `--user` = `test-user-001`
|
||||
- Contract: 从每行 NDJSON 顶层读取 `message_id`、`reader`、`reader_open_dingtalk_id`、`read_time`
|
||||
|
||||
@@ -1056,7 +1056,7 @@ Agent 安装 dws skill 后,仅依据 skill 提供的参考文档,将自然
|
||||
|
||||
**event_event_consume_read_group_001**
|
||||
- Prompt: 监听 openConversationId cid123 群里我发的消息是否已读
|
||||
- Expected: `dws event consume user_im_message_read_group --group cid123 -f ndjson`
|
||||
- Expected: `dws event consume user_im_message_read_group --group cid123 --flatten -f ndjson`
|
||||
- Flags: `--group` = `cid123`
|
||||
- Contract: 从每行 NDJSON 顶层读取 `conversation_id`、`reader`、`reader_open_dingtalk_id`、`read_time`
|
||||
|
||||
@@ -1064,7 +1064,7 @@ Agent 安装 dws skill 后,仅依据 skill 提供的参考文档,将自然
|
||||
|
||||
**event_event_consume_recall_o2o_001**
|
||||
- Prompt: 监听我和 userId test-user-001 的单聊消息撤回事件
|
||||
- Expected: `dws event consume user_im_message_recall_o2o --user test-user-001 -f ndjson`
|
||||
- Expected: `dws event consume user_im_message_recall_o2o --user test-user-001 --flatten -f ndjson`
|
||||
- Flags: `--user` = `test-user-001`
|
||||
- Contract: 从每行 NDJSON 顶层读取 `message_id`、`recaller`、`recaller_open_dingtalk_id`、`recall_time`
|
||||
|
||||
@@ -1072,7 +1072,7 @@ Agent 安装 dws skill 后,仅依据 skill 提供的参考文档,将自然
|
||||
|
||||
**event_event_consume_recall_group_001**
|
||||
- Prompt: 监听 openConversationId cid123 的群消息撤回事件
|
||||
- Expected: `dws event consume user_im_message_recall_group --group cid123 -f ndjson`
|
||||
- Expected: `dws event consume user_im_message_recall_group --group cid123 --flatten -f ndjson`
|
||||
- Flags: `--group` = `cid123`
|
||||
- Contract: 从每行 NDJSON 顶层读取 `conversation_id`、`recaller`、`recaller_open_dingtalk_id`、`recall_time`
|
||||
|
||||
@@ -1080,13 +1080,13 @@ Agent 安装 dws skill 后,仅依据 skill 提供的参考文档,将自然
|
||||
|
||||
**event_event_consume_reaction_o2o_001**
|
||||
- Prompt: 监听我和 userId test-user-001 的单聊消息贴表情事件
|
||||
- Expected: `dws event consume user_im_message_reaction_o2o --user test-user-001 -f ndjson`
|
||||
- Expected: `dws event consume user_im_message_reaction_o2o --user test-user-001 --flatten -f ndjson`
|
||||
- Flags: `--user` = `test-user-001`
|
||||
- Contract: 从每行 NDJSON 顶层读取 `operator`、`operator_open_dingtalk_id`、`reaction_name`、`operation_type`
|
||||
|
||||
**event_event_consume_reaction_o2o_open_id_001**
|
||||
- Prompt: 监听我和 openDingtalkId abc 的单聊消息贴表情事件
|
||||
- Expected: `dws event consume user_im_message_reaction_o2o --open-dingtalk-id abc -f ndjson`
|
||||
- Expected: `dws event consume user_im_message_reaction_o2o --open-dingtalk-id abc --flatten -f ndjson`
|
||||
- Flags: `--open-dingtalk-id` = `abc`
|
||||
- Contract: 从每行 NDJSON 顶层读取 `operator`、`operator_open_dingtalk_id`、`reaction_name`、`operation_type`
|
||||
|
||||
@@ -1094,7 +1094,7 @@ Agent 安装 dws skill 后,仅依据 skill 提供的参考文档,将自然
|
||||
|
||||
**event_event_consume_reaction_group_001**
|
||||
- Prompt: 监听 openConversationId cid123 的群消息表情回应事件
|
||||
- Expected: `dws event consume user_im_message_reaction_group --group cid123 -f ndjson`
|
||||
- Expected: `dws event consume user_im_message_reaction_group --group cid123 --flatten -f ndjson`
|
||||
- Flags: `--group` = `cid123`
|
||||
- Contract: 从每行 NDJSON 顶层读取 `conversation_id`、`operator`、`reaction_name`、`reaction_text`、`operation_type`
|
||||
|
||||
|
||||
@@ -95,6 +95,7 @@ func TestEventSkillUsesFlatOutputContract(t *testing.T) {
|
||||
text := string(content)
|
||||
for _, required := range []string{
|
||||
"[event] ready",
|
||||
"--flatten",
|
||||
"conversation_id",
|
||||
"sender_open_dingtalk_id",
|
||||
"reader_open_dingtalk_id",
|
||||
@@ -109,7 +110,6 @@ func TestEventSkillUsesFlatOutputContract(t *testing.T) {
|
||||
}
|
||||
}
|
||||
for _, retired := range []string{
|
||||
".data | fromjson",
|
||||
"payload.body.",
|
||||
"尚无稳定业务样本",
|
||||
"暂无稳定 payload schema",
|
||||
|
||||
Reference in New Issue
Block a user