feat(cli)!: v0.10 reliability, agent-UX, and command-surface hardening

A hardening + finalization pass over the agent-first CLI: correctness fixes,
richer machine-readable signals, flag/naming consistency, and a symmetric
config surface. Pre-1.0, so it includes breaking renames.

Correctness:
- agent update: resolve/validate --model/--rerank-model (was storing a bogus
  name verbatim, corrupting config.model_id).
- doctor: honor WEKNORA_HOST / WEKNORA_API_KEY (headless path no longer reports
  "no host configured").
- session ask / MCP session_ask: text answer was empty on non-TTY — the agent
  stream sets Done=true on an intermediate agent_query frame before the answer,
  and AgentAccumulator treated the first Done as terminal. Terminate on the
  `complete` event (new sdk.AgentResponseTypeComplete), not a per-frame Done.
- batch exit codes: any per-item failure collapses to operation.failed (exit 1),
  including `doc upload --recursive` partial failures — a permanent per-file
  failure (e.g. a duplicate) no longer surfaces as a retryable exit 7 an agent
  would loop on; per-item typed errors stay in the envelope.

Agent-first signals & discovery:
- error.exit_code in the envelope (type + exit_code disambiguate the
  input.invalid_argument exit-2-vs-5 split in one JSON read).
- meta.hint on empty content search and on draft doc create; doc wait fails fast
  on a never-parsing draft instead of hanging to --timeout.
- retrieval-readiness is visible in the natural flow: kb status / kb check emit
  retrieval_ready, and kb create hints the fix when no embedding model is bound
  — an unconfigured KB no longer looks silently healthy.
- schema contract completeness: every leaf declares output + >=1 example
  (drift-guarded); output strings match the meta actually emitted; chunk list
  and search docs now emit meta.total_count (both previously dropped it).
- schema tolerates a quoted multi-word command label; zero-state auth — and
  `link` with no profile — point at profile setup / the headless WEKNORA_KB_ID
  path instead of looping on `auth login`.
- id-addressed reads tolerate a redundant --kb (doc view/wait, chunk list/view
  accept and ignore it, declared in schema) so a carried-over --kb doesn't
  exit 2; streaming commands warn that --jq does not apply to an NDJSON stream.
- keep JSON-always as the default; --jq hints spell out the .data path.

Consistency & gating:
- doc create: drop the deprecated --name alias (--title only; pre-1.0 break).
- chunk list --limit aligned to 1..10000; model list --limit/-L with
  has_more/total_count; api write-gates -X PUT/PATCH (exit 10); skills install
  expands a leading ~.
- docs corrected: search docs / doc list --keyword help is case-insensitive
  (server does LOWER LIKE); AGENTS.md risk-action list (no phantom kb.init; add
  model.update / kb.config.set) and batch example (failed item carries `error`);
  session resume --message id comes from `message list`, not the stream.
- auth/profile ergonomics: env credentials are now first-class — `auth token`
  prints the active WEKNORA_API_KEY / WEKNORA_TOKEN, and auth login/logout/refresh
  give an env-aware message instead of looping on "run auth login". `auth logout`
  clears credentials but keeps the profile registered (host preserved for
  re-login); deleting a profile is `profile remove`'s job (clean logout/remove
  separation, matching gh / lark).

Config surface (symmetric read/write, in-place model edits):
- kb config now returns a secret-free KBModelConfigView (was {}); `kb config`
  reads, new `kb config set` writes; `kb init` removed (misnomer).
- kb create --chat-model: retrieval-ready in one step.
- model update: edit a model in place (id preserved, references survive) —
  rotate --api-key-stdin, change base-url / display-name / etc.
- session continue-stream renamed to session resume.

Docs: AGENTS.md is the single wire-contract source; CHANGELOG slimmed; stale
kb-init / continue-stream references removed; skill wire-vocab guard extended.
AGENTS.md / weknora-shared SKILL document retrieval_ready (a KB needs an
embedding model to be searchable), that --jq does not apply to NDJSON
streams, and the env-credential-first auth path; the KB quickstart example
now creates a retrieval-ready KB.
This commit is contained in:
nullkey
2026-07-02 15:53:51 +08:00
committed by lyingbug
parent 040a734766
commit d3c03b2f34
95 changed files with 1711 additions and 436 deletions
+1 -1
View File
@@ -61,7 +61,7 @@ the caller to thread follow-ups.
AI agents: this is the primary entrypoint for invoking custom agents.
The 'weknora agent' subtree handles CRUD only (list / view / create /
edit / delete / status / check).
update / delete / status / check).
Modes:
--format json (default): one JSON envelope with answer events
+3
View File
@@ -47,6 +47,9 @@ func (s *scriptedAskSvc) AgentQAStreamWithRequest(_ context.Context, sessionID s
func answerEvent(content string) *sdk.AgentStreamResponse {
return &sdk.AgentStreamResponse{ResponseType: sdk.AgentResponseTypeAnswer, Content: content}
}
// doneEvent is the stream's terminal frame. The real server ends an agent
// stream with a `complete` event (it also sets Done=true on intermediate
// frames), so the terminal is modeled as complete, not a bare answer+done.
func doneEvent() *sdk.AgentStreamResponse {
return &sdk.AgentStreamResponse{ResponseType: sdk.AgentResponseTypeComplete, Done: true}
}
+1 -1
View File
@@ -78,7 +78,7 @@ func NewCmdList(f *cmdutil.Factory) *cobra.Command {
cmdutil.SetAgentHelp(cmd, cmdutil.AgentHelp{
UsedFor: "List chat sessions for the active profile. Results come with meta.count; use --limit to cap, --all-pages to walk every server page, --since to filter by recency (e.g. 7d).",
Examples: []string{"weknora session list --format json", "weknora session list --all-pages --since 7d --format json"},
Output: "envelope.data is an array of Session objects with id, title, updated_at; meta.count is the total returned; meta.total_count is the server-side total before --since filtering",
Output: "envelope.data is an array of Session objects with id, title, updated_at; meta.count is the returned count; meta.total_count is the server-side total before --since filtering; meta.has_more=true when --limit truncated",
})
return cmd
}
@@ -1,4 +1,4 @@
// continue_stream.go implements `weknora session continue-stream` —
// resume.go implements `weknora session resume` —
// re-attach to an SSE event buffer for an in-progress or already-completed
// assistant message under a known session_id.
//
@@ -23,44 +23,42 @@ package sessioncmd
import (
"context"
"errors"
"github.com/spf13/cobra"
"github.com/Tencent/WeKnora/cli/internal/cmdutil"
"github.com/Tencent/WeKnora/cli/internal/iostreams"
"github.com/Tencent/WeKnora/cli/internal/output"
"github.com/Tencent/WeKnora/cli/internal/sse"
sdk "github.com/Tencent/WeKnora/client"
)
// continueStreamFields enumerates the NDJSON init-event + raw SDK event
// resumeFields enumerates the NDJSON init-event + raw SDK event
// vocabulary surfaced for `--format json` / `--format ndjson` discovery.
var continueStreamFields = []string{
var resumeFields = []string{
"session_id", "message_id",
// SDK StreamResponse fields (pass-through): id, response_type, content,
// done, knowledge_references, assistant_message_id, session_id,
// tool_calls, data
}
// ContinueStreamOptions captures `session continue-stream` flag/arg state.
type ContinueStreamOptions struct {
// ResumeOptions captures `session resume` flag/arg state.
type ResumeOptions struct {
SessionID string
MessageID string
}
// ContinueStreamService is the narrow SDK surface this command depends on.
// ResumeService is the narrow SDK surface this command depends on.
// *sdk.Client satisfies it; tests substitute a fake. Compile-time check
// at the bottom of this file.
type ContinueStreamService interface {
type ResumeService interface {
ContinueStream(ctx context.Context, sessionID, messageID string, cb func(*sdk.StreamResponse) error) error
}
// NewCmdContinueStream builds `weknora session continue-stream <session-id> --message <id>`.
func NewCmdContinueStream(f *cmdutil.Factory) *cobra.Command {
opts := &ContinueStreamOptions{}
// NewCmdResume builds `weknora session resume <session-id> --message <id>`.
func NewCmdResume(f *cmdutil.Factory) *cobra.Command {
opts := &ResumeOptions{}
cmd := &cobra.Command{
Use: "continue-stream <session-id>",
Use: "resume <session-id>",
Short: "Resume an SSE event stream for an in-progress or completed session message",
Long: `Re-attach to the SSE event buffer for an assistant message under a known session.
@@ -92,8 +90,8 @@ regardless of --format value. The operator use case (incident response,
debugging) always wants the raw event log; there is no human-text rendering.
--format json and --format ndjson behave identically here; --format text is
silently treated as NDJSON.`,
Example: ` weknora session continue-stream sess_xyz --message msg_abc
weknora session continue-stream sess_xyz -m msg_abc --format ndjson`,
Example: ` weknora session resume sess_xyz --message msg_abc
weknora session resume sess_xyz -m msg_abc --format ndjson`,
Args: cobra.ExactArgs(1),
RunE: func(c *cobra.Command, args []string) error {
opts.SessionID = args[0]
@@ -106,38 +104,39 @@ silently treated as NDJSON.`,
if err != nil {
return err
}
return runContinueStream(c.Context(), opts, fopts, cli)
return runResume(c.Context(), opts, fopts, cli)
},
}
cmd.Flags().StringVarP(&opts.MessageID, "message", "m", "",
"Assistant message ID to resume (from the init or agent_query event of the original stream)")
_ = cmd.MarkFlagRequired("message")
cmdutil.AddFormatFlag(cmd, continueStreamFields...)
cmdutil.AddFormatFlag(cmd, resumeFields...)
cmdutil.SetAgentHelp(cmd, cmdutil.AgentHelp{
UsedFor: "Resume an SSE event stream for an in-progress or completed assistant message. Produces an NDJSON event stream: init line (session_id, message_id) then raw SDK StreamResponse events.",
RequiredFlags: []string{"<session-id> (positional)", "--message (message_id from prior init / agent_query event)"},
RequiredFlags: []string{"<session-id> (positional)", "--message (persisted assistant message id — get it from `weknora message list --session <id>`; a live stream's assistant_message_id is not resumable once the message persists)"},
Examples: []string{
"weknora session continue-stream sess_xyz --message msg_abc --format json",
"# Network-blip recovery: replay with same session_id + message_id from the original 'session ask' init event",
"weknora session resume sess_xyz --message msg_abc --format json",
"# Get the message id from: weknora message list --session <session-id> (the persisted assistant message)",
},
Output: "NDJSON stream: {type:init, session_id, message_id, profile} then SDK StreamResponse events (response_type, content, done, knowledge_references, assistant_message_id, ...)",
Warnings: []string{
"Server replays from event 0 (NOT cursor-from-disconnect). Agents that already consumed events on the original stream MUST dedupe by message_id + event hash to avoid double-processing.",
"Buffer TTL: redis mode 1h hardcoded; memory mode = process lifetime. After expiry the CLI returns local.sse_stream_aborted.",
"Output is always NDJSON (an event stream, not an envelope): --jq does not apply and --format text/json/ndjson behave identically here — parse the event lines yourself.",
},
})
return cmd
}
// runContinueStream is the testable core: validate, dispatch the resume, and
// runResume is the testable core: validate, dispatch the resume, and
// route the NDJSON stream. Returns a typed error.
//
// Always emits NDJSON: a buffered envelope makes no sense for a streaming
// command, and continue-stream has no human-text use case (operators reach
// command, and resume has no human-text use case (operators reach
// for it during incident response / debugging, which always wants the raw
// event log). --format text is therefore treated identically to --format
// json/ndjson here.
func runContinueStream(ctx context.Context, opts *ContinueStreamOptions, _ *cmdutil.FormatOptions, svc ContinueStreamService) error {
func runResume(ctx context.Context, opts *ResumeOptions, _ *cmdutil.FormatOptions, svc ResumeService) error {
if opts.SessionID == "" {
return cmdutil.NewError(cmdutil.CodeInputInvalidArgument, "session-id argument cannot be empty")
}
@@ -145,7 +144,7 @@ func runContinueStream(ctx context.Context, opts *ContinueStreamOptions, _ *cmdu
return cmdutil.NewError(cmdutil.CodeInputInvalidArgument, "--message cannot be empty")
}
if svc == nil {
return cmdutil.NewError(cmdutil.CodeServerError, "session continue-stream: no SDK client available")
return cmdutil.NewError(cmdutil.CodeServerError, "session resume: no SDK client available")
}
w := iostreams.IO.Out
@@ -165,52 +164,27 @@ func runContinueStream(ctx context.Context, opts *ContinueStreamOptions, _ *cmdu
// 2. Open the SDK replay stream and pass each event through as a bare
// NDJSON line. The SDK's StreamResponse is the source of truth for
// the event vocabulary; the CLI does not reshape it.
var streamErrMsg string
// The SDK invokes the callback for each event (including a terminal
// response_type=error frame) BEFORE returning, so raw passthrough still
// emits every event; on a terminal error frame the SDK then returns an
// *SSEStreamError. No CLI-level early-terminate is needed.
cb := func(r *sdk.StreamResponse) error {
isErr := r != nil && r.ResponseType == sdk.ResponseTypeError
if isErr && streamErrMsg == "" {
if r.Content != "" {
streamErrMsg = r.Content
} else if r.Data != nil {
if e, ok := r.Data["error"].(string); ok {
streamErrMsg = e
}
}
}
if err := output.EmitSDKEvent(w, r); err != nil {
return err
}
// Stop once the error frame is emitted rather than blocking on the SDK
// read until the ~30s transport timeout (the server holds the stream
// open after the error). streamErrMsg carries the real reason.
if isErr {
return sse.ErrTerminate
}
return nil
return output.EmitSDKEvent(w, r)
}
err := svc.ContinueStream(ctx, opts.SessionID, opts.MessageID, cb)
if errors.Is(err, sse.ErrTerminate) {
err = nil
}
// A server-delivered error frame is the authoritative failure reason —
// surface it as operation.failed (exit 1) instead of the transport timeout
// the server's stream-close triggers afterwards. Mirrors chat / session ask.
if streamErrMsg != "" {
return cmdutil.NewError(cmdutil.CodeOperationFailed, "continue stream failed: "+streamErrMsg)
}
if err != nil {
// Ctrl-C / SIGTERM lineage (operator gave up on the resume).
if cmdutil.IsCancelled(ctx, err) {
return cmdutil.Wrapf(cmdutil.CodeOperationCancelled, err, "session continue-stream cancelled")
return cmdutil.Wrapf(cmdutil.CodeOperationCancelled, err, "session resume cancelled")
}
// Pre-stream HTTP / transport failure (e.g. 404 if message_id is
// unknown, or buffer-expired body from the server). Route through
// the canonical classifier so codes survive — 404 still surfaces
// as resource.not_found etc.
return cmdutil.WrapHTTP(err, "continue stream")
// WrapStream routes through ClassifySDKError: a terminal SSE error
// frame classifies as server.error (matching chat / session ask); a
// pre-stream HTTP failure (e.g. 404 for an unknown message_id) still
// surfaces via ClassifyHTTPError as resource.not_found etc.
return cmdutil.WrapStream(err, "resume stream")
}
return nil
}
// compile-time check: production SDK client satisfies ContinueStreamService.
var _ ContinueStreamService = (*sdk.Client)(nil)
// compile-time check: production SDK client satisfies ResumeService.
var _ ResumeService = (*sdk.Client)(nil)
@@ -16,9 +16,9 @@ import (
sdk "github.com/Tencent/WeKnora/client"
)
// scriptedContinueStreamSvc serves a canned stream of StreamResponse events
// to runContinueStream and records the (sessionID, messageID) passed in.
type scriptedContinueStreamSvc struct {
// scriptedResumeSvc serves a canned stream of StreamResponse events
// to runResume and records the (sessionID, messageID) passed in.
type scriptedResumeSvc struct {
events []*sdk.StreamResponse
streamErr error
got struct {
@@ -27,7 +27,7 @@ type scriptedContinueStreamSvc struct {
}
}
func (s *scriptedContinueStreamSvc) ContinueStream(_ context.Context, sessionID, messageID string, cb func(*sdk.StreamResponse) error) error {
func (s *scriptedResumeSvc) ContinueStream(_ context.Context, sessionID, messageID string, cb func(*sdk.StreamResponse) error) error {
s.got.sessionID = sessionID
s.got.messageID = messageID
for _, e := range s.events {
@@ -51,11 +51,11 @@ func contStreamComplete() *sdk.StreamResponse {
// arrives.
func TestContinueStream_NDJSON_FirstLineIsInitWithMessageID(t *testing.T) {
out, _ := iostreams.SetForTest(t)
svc := &scriptedContinueStreamSvc{
svc := &scriptedResumeSvc{
events: []*sdk.StreamResponse{contStreamAnswer("hello"), contStreamComplete()},
}
opts := &ContinueStreamOptions{SessionID: "sess_xyz", MessageID: "msg_abc"}
require.NoError(t, runContinueStream(context.Background(), opts, ndjsonOpts(), svc))
opts := &ResumeOptions{SessionID: "sess_xyz", MessageID: "msg_abc"}
require.NoError(t, runResume(context.Background(), opts, ndjsonOpts(), svc))
lines := strings.Split(strings.TrimRight(out.String(), "\n"), "\n")
require.GreaterOrEqual(t, len(lines), 1, "expected at least the init line")
@@ -75,15 +75,15 @@ func TestContinueStream_NDJSON_FirstLineIsInitWithMessageID(t *testing.T) {
// events = N+1 total lines, all valid JSON.
func TestContinueStream_NDJSON_PassthroughEvents(t *testing.T) {
out, _ := iostreams.SetForTest(t)
svc := &scriptedContinueStreamSvc{
svc := &scriptedResumeSvc{
events: []*sdk.StreamResponse{
contStreamAnswer("alpha"),
contStreamAnswer("beta"),
contStreamComplete(),
},
}
opts := &ContinueStreamOptions{SessionID: "sess_x", MessageID: "msg_y"}
require.NoError(t, runContinueStream(context.Background(), opts, ndjsonOpts(), svc))
opts := &ResumeOptions{SessionID: "sess_x", MessageID: "msg_y"}
require.NoError(t, runResume(context.Background(), opts, ndjsonOpts(), svc))
lines := strings.Split(strings.TrimRight(out.String(), "\n"), "\n")
// 1 init + 3 SDK events = 4 lines.
@@ -98,9 +98,9 @@ func TestContinueStream_NDJSON_PassthroughEvents(t *testing.T) {
// flow through to the SDK call.
func TestContinueStream_PassesSessionAndMessageIDToSDK(t *testing.T) {
_, _ = iostreams.SetForTest(t)
svc := &scriptedContinueStreamSvc{events: []*sdk.StreamResponse{contStreamComplete()}}
opts := &ContinueStreamOptions{SessionID: "sess_42", MessageID: "msg_99"}
require.NoError(t, runContinueStream(context.Background(), opts, ndjsonOpts(), svc))
svc := &scriptedResumeSvc{events: []*sdk.StreamResponse{contStreamComplete()}}
opts := &ResumeOptions{SessionID: "sess_42", MessageID: "msg_99"}
require.NoError(t, runResume(context.Background(), opts, ndjsonOpts(), svc))
assert.Equal(t, "sess_42", svc.got.sessionID)
assert.Equal(t, "msg_99", svc.got.messageID)
}
@@ -110,9 +110,9 @@ func TestContinueStream_PassesSessionAndMessageIDToSDK(t *testing.T) {
// core must also refuse empty strings).
func TestContinueStream_EmptySessionID_Rejected(t *testing.T) {
_, _ = iostreams.SetForTest(t)
svc := &scriptedContinueStreamSvc{}
opts := &ContinueStreamOptions{SessionID: "", MessageID: "msg_x"}
err := runContinueStream(context.Background(), opts, ndjsonOpts(), svc)
svc := &scriptedResumeSvc{}
opts := &ResumeOptions{SessionID: "", MessageID: "msg_x"}
err := runResume(context.Background(), opts, ndjsonOpts(), svc)
require.Error(t, err)
var typed *cmdutil.Error
require.ErrorAs(t, err, &typed)
@@ -123,9 +123,9 @@ func TestContinueStream_EmptySessionID_Rejected(t *testing.T) {
// point.
func TestContinueStream_EmptyMessageID_Rejected(t *testing.T) {
_, _ = iostreams.SetForTest(t)
svc := &scriptedContinueStreamSvc{}
opts := &ContinueStreamOptions{SessionID: "sess_x", MessageID: ""}
err := runContinueStream(context.Background(), opts, ndjsonOpts(), svc)
svc := &scriptedResumeSvc{}
opts := &ResumeOptions{SessionID: "sess_x", MessageID: ""}
err := runResume(context.Background(), opts, ndjsonOpts(), svc)
require.Error(t, err)
var typed *cmdutil.Error
require.ErrorAs(t, err, &typed)
@@ -138,9 +138,9 @@ func TestContinueStream_Cancellation_MapsToOperationCancelled(t *testing.T) {
_, _ = iostreams.SetForTest(t)
ctx, cancel := context.WithCancel(context.Background())
cancel()
svc := &scriptedContinueStreamSvc{streamErr: context.Canceled}
opts := &ContinueStreamOptions{SessionID: "sess_x", MessageID: "msg_x"}
err := runContinueStream(ctx, opts, ndjsonOpts(), svc)
svc := &scriptedResumeSvc{streamErr: context.Canceled}
opts := &ResumeOptions{SessionID: "sess_x", MessageID: "msg_x"}
err := runResume(ctx, opts, ndjsonOpts(), svc)
require.Error(t, err)
var typed *cmdutil.Error
require.ErrorAs(t, err, &typed)
@@ -152,20 +152,36 @@ func TestContinueStream_Cancellation_MapsToOperationCancelled(t *testing.T) {
// the canonical HTTP classifier.
func TestContinueStream_NotFound_MapsToResourceNotFound(t *testing.T) {
_, _ = iostreams.SetForTest(t)
svc := &scriptedContinueStreamSvc{streamErr: errors.New("HTTP error 404: not found")}
opts := &ContinueStreamOptions{SessionID: "sess_x", MessageID: "msg_missing"}
err := runContinueStream(context.Background(), opts, ndjsonOpts(), svc)
svc := &scriptedResumeSvc{streamErr: errors.New("HTTP error 404: not found")}
opts := &ResumeOptions{SessionID: "sess_x", MessageID: "msg_missing"}
err := runResume(context.Background(), opts, ndjsonOpts(), svc)
require.Error(t, err)
var typed *cmdutil.Error
require.ErrorAs(t, err, &typed)
assert.Equal(t, cmdutil.CodeResourceNotFound, typed.Code)
}
// TestResume_TerminalStreamError_MapsToServerError pins that a terminal SSE
// error frame (surfaced by the SDK as *SSEStreamError) classifies as
// server.error (exit 7) — the SAME as chat / session ask. Guards against the
// prior inconsistency where resume reported the identical server condition as
// exit 1 while chat/ask reported exit 7.
func TestResume_TerminalStreamError_MapsToServerError(t *testing.T) {
_, _ = iostreams.SetForTest(t)
svc := &scriptedResumeSvc{streamErr: sdk.NewSSEStreamError("no chat model configured")}
opts := &ResumeOptions{SessionID: "sess_x", MessageID: "msg_x"}
err := runResume(context.Background(), opts, ndjsonOpts(), svc)
require.Error(t, err)
var typed *cmdutil.Error
require.ErrorAs(t, err, &typed)
assert.Equal(t, cmdutil.CodeServerError, typed.Code)
}
// TestContinueStream_RequiresMessageFlag verifies cobra refuses to run the
// command without --message (the flag is marked required).
func TestContinueStream_RequiresMessageFlag(t *testing.T) {
f := &cmdutil.Factory{}
cmd := NewCmdContinueStream(f)
cmd := NewCmdResume(f)
var buf bytes.Buffer
cmd.SetOut(&buf)
cmd.SetErr(&buf)
@@ -185,7 +201,7 @@ func TestContinueStream_RequiresMessageFlag(t *testing.T) {
// command without the positional <session-id>.
func TestContinueStream_RequiresSessionIDArg(t *testing.T) {
f := &cmdutil.Factory{}
cmd := NewCmdContinueStream(f)
cmd := NewCmdResume(f)
var buf bytes.Buffer
cmd.SetOut(&buf)
cmd.SetErr(&buf)
+2 -2
View File
@@ -1,5 +1,5 @@
// Package sessioncmd holds `weknora session` command tree (list / view /
// delete / ask / continue-stream / stop) for chat history and agent invocation.
// delete / ask / resume / stop) for chat history and agent invocation.
//
// Package name `sessioncmd` (not `session`) so callers can `import sdk
// "github.com/Tencent/WeKnora/client"` and use `sdk.Session` without
@@ -22,7 +22,7 @@ func NewCmd(f *cmdutil.Factory) *cobra.Command {
cmd.AddCommand(NewCmdView(f))
cmd.AddCommand(NewCmdDelete(f))
cmd.AddCommand(NewCmdAsk(f))
cmd.AddCommand(NewCmdContinueStream(f))
cmd.AddCommand(NewCmdResume(f))
cmd.AddCommand(NewCmdStop(f))
cmd.AddCommand(NewCmdToolApproval(f))
return cmd
+4 -4
View File
@@ -4,9 +4,9 @@
// Unlike Ctrl-C (which only drops the local connection while the server keeps
// generating and billing tokens), this tells the server to stop.
//
// This is the symmetric counterpart to `session continue-stream`: both key on
// This is the symmetric counterpart to `session resume`: both key on
// (session_id, message_id). The message_id comes from the init event of the
// original chat / session ask / continue-stream stream.
// original chat / session ask / resume stream.
package sessioncmd
import (
@@ -52,7 +52,7 @@ func NewCmdStop(f *cmdutil.Factory) *cobra.Command {
session. Unlike Ctrl-C (which only drops the local connection while the server
keeps generating and billing tokens), this tells the server to stop.
Symmetric with 'session continue-stream': both key on (session_id, message_id).`,
Symmetric with 'session resume': both key on (session_id, message_id).`,
Example: ` weknora session stop sess_xyz --message msg_abc`,
Args: cobra.ExactArgs(1),
RunE: func(c *cobra.Command, args []string) error {
@@ -81,7 +81,7 @@ Symmetric with 'session continue-stream': both key on (session_id, message_id).`
cmdutil.AddFormatFlag(cmd, stopFields...)
cmdutil.AddDryRunFlag(cmd, &opts.DryRun)
cmdutil.SetAgentHelp(cmd, cmdutil.AgentHelp{
UsedFor: "Stop server-side generation for an in-flight assistant message (counterpart to continue-stream). The message_id comes from the init event of the chat / session ask / continue-stream stream you're stopping.",
UsedFor: "Stop server-side generation for an in-flight assistant message (counterpart to resume). The message_id comes from the init event of the chat / session ask / resume stream you're stopping.",
RequiredFlags: []string{"<session-id> (positional)", "--message (message_id from the init event of the stream you're stopping)"},
Examples: []string{"weknora session stop sess_xyz --message msg_abc"},
Output: "envelope {session_id, message_id, stopped:true}",
+2 -2
View File
@@ -49,7 +49,7 @@ When a server-side agent run (weknora session ask) needs to call a tool
that requires approval, the stream emits a tool-approval event carrying a
pending id and the run blocks. This command unblocks it: approve (default)
lets the tool call execute, --reject cancels it. After resolving, resume
the answer with weknora session continue-stream.
the answer with weknora session resume.
--modified-args replaces the tool call arguments on approve (JSON object).
It conflicts with --reject (rejected calls never execute).
@@ -122,7 +122,7 @@ func newCmdResolve(f *cmdutil.Factory) *cobra.Command {
cmdutil.AddDryRunFlag(cmd, &opts.DryRun)
cmdutil.SetRisk(cmd, "session.tool_approval.resolve")
cmdutil.SetAgentHelp(cmd, cmdutil.AgentHelp{
UsedFor: "approve or reject a pending tool call from an agent run; then resume with session continue-stream",
UsedFor: "approve or reject a pending tool call from an agent run; then resume with session resume",
RequiredFlags: []string{"<pending-id> (positional)"},
Examples: []string{
"weknora session tool-approval resolve pend_abc -y",