Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
79 commits
Select commit Hold shift + click to select a range
13d0d26
Self-heal NyxID binding rejections + streaming-failed reply token loop
eanzhao May 3, 2026
7f0f592
Address PR #561 review (codex P2 + eanzhao): honest self-heal failure…
eanzhao May 4, 2026
00fa7c7
Merge pull request #561 from aevatarAI/fix/2026-05-03_binding-and-rep…
eanzhao May 4, 2026
b8128cb
Propagate reasoning_content through LLM pipeline (fixes #563)
eanzhao May 4, 2026
6765f70
Gracefully handle missing scope in UserMemoryStore (fixes #564)
eanzhao May 4, 2026
2690a61
Fix reasoning_content round-trip: outbound serialization, fallback pa…
eanzhao May 4, 2026
f1ddc4b
Make RemoveEntryAsync throw on missing scope, add no-scope read tests…
eanzhao May 4, 2026
6505a06
Merge pull request #566 from aevatarAI/fix/2026-05-04_user-memory-scope
eanzhao May 4, 2026
99e56e3
Add tests for reasoning_content round-trip coverage
eanzhao May 4, 2026
e757a3b
Add DSML and hook-blocking tests for reasoning_content coverage
eanzhao May 4, 2026
a016c19
Merge pull request #565 from aevatarAI/fix/2026-05-04_deepseek-reason…
eanzhao May 4, 2026
58ad94f
Fix DeepSeek reasoning serialization
eanzhao May 5, 2026
dc436a1
Fix stale binding projection repair
eanzhao May 5, 2026
41a0a70
Activate binding projection during self heal
eanzhao May 5, 2026
419516c
Delete stale binding readmodel on revoke
eanzhao May 5, 2026
f65a80d
List proxy LLM route candidates
eanzhao May 5, 2026
8001adf
Restore owner LLM fallback for Lark users
eanzhao May 5, 2026
c4c1273
fix(skill-runner): treat zero-tool-call runs as failure for fetch-and…
eanzhao May 5, 2026
0423798
Add operator rebuild endpoint to unwedge OAuth client at NyxID
eanzhao May 5, 2026
e3300df
Address Lark bot review comments
eanzhao May 5, 2026
90c13ba
Wrap up issue #513 follow-ups: /whoami no-auth, override matrix, call…
eanzhao May 5, 2026
1fa364d
Address PR #569 review (codex P1 + eanzhao): legacy default + suppres…
eanzhao May 5, 2026
ebc0a69
Address PR #570 review: P1 preserve fields + drop operator-overrides …
eanzhao May 5, 2026
aad6edf
Merge pull request #569 from aevatarAI/fix/2026-05-05_skill-runner-er…
eanzhao May 6, 2026
c72788a
Merge pull request #570 from aevatarAI/fix/2026-05-05_oauth-client-bo…
eanzhao May 6, 2026
2cfac14
Merge pull request #571 from aevatarAI/feat/2026-05-05_issue-513-foll…
eanzhao May 6, 2026
bc48f21
Preserve reasoning content across tool calls
eanzhao May 6, 2026
3c21ee1
Harden Lark bot LLM routing
eanzhao May 6, 2026
a8d0522
Avoid silent Lark reply truncation
eanzhao May 6, 2026
877426c
Add Lark Ornn skill autoload
eanzhao May 6, 2026
27939dd
Fix Ornn skills CLI search invocation
eanzhao May 7, 2026
93dad70
Fix Lark Ornn skill autoload behavior
eanzhao May 7, 2026
b826bf7
Cover Ornn skill provider behavior
eanzhao May 7, 2026
6ab0a7a
Prioritize named Ornn skill autoload
eanzhao May 7, 2026
c039eb2
Always advertise ornn_search_skills tool to LLM
eanzhao May 7, 2026
9a073ee
Drop per-turn Ornn skill autoload pipeline
eanzhao May 7, 2026
fdfb6eb
Sharpen Ornn skill discovery prompts
eanzhao May 7, 2026
bd3502c
Route Ornn skill API calls through NyxID proxy
eanzhao May 7, 2026
a8a1ba1
Default Ornn NyxID slug to canonical 'ornn'
eanzhao May 7, 2026
3197263
Cap and throttle streaming Lark edits to avoid 230072
eanzhao May 7, 2026
4833c71
Move NyxID manual to Ornn skill and add 5-min TTL on remote skill cache
eanzhao May 7, 2026
ed02c91
Refactor Nyx relay streaming runtime state into phase machine
eanzhao May 7, 2026
5d09f91
Fix Lark bot tool calls: align Ornn paths, sandbox path, slug
eanzhao May 7, 2026
1851f18
Add LarkCardKitClient for streaming-card endpoints
eanzhao May 7, 2026
04b904c
Add ssh_exec tool and timeout-budgeted LLM reply fallback
eanzhao May 7, 2026
c854b6a
Add LarkCardStreamingPhase machine for upcoming card sink
eanzhao May 7, 2026
7387b4f
Add IConversationCardTurnRunner contract for CardKit streaming
eanzhao May 7, 2026
1efcf43
Allowlist HangingReplyGenerator polling wait in channel runtime tests
eanzhao May 7, 2026
03726dc
Add ChannelCardConversationTurnRunner for CardKit outbound
eanzhao May 7, 2026
712b9cb
Resolve catalog_service_id for ssh_exec; default Ornn slug to ornn-api
eanzhao May 7, 2026
8c94e36
Wire CardKit card-mode streaming end-to-end
eanzhao May 7, 2026
1041ad7
Add card-mode streaming tests covering create, stream, finalize, fall…
eanzhao May 7, 2026
86178b6
Move CardKit streaming shell builder into Lark platform project
eanzhao May 7, 2026
650b932
Address PR #590 review: P2 hardening + boundary fixes
eanzhao May 7, 2026
af91cc2
Improve NyxID SSH exec coverage
eanzhao May 8, 2026
4c537ad
Cover LarkCardKitClient with HTTP-level unit tests (#590 review)
eanzhao May 8, 2026
c64f49f
PR #562 review: phase A quick fixes
eanzhao May 8, 2026
673809a
PR #562 review: phases C-E (DI, TOCTOU, catalog client)
eanzhao May 8, 2026
d998ec2
PR #562 review: phase F (OAuth endpoints + projector docs) + G
eanzhao May 8, 2026
a5b68c6
PR #562 review: phase H + test fix for #22
eanzhao May 8, 2026
a43b4f9
Cover residual LarkCardKitClient branches (#590 codecov 96.97% -> 100%)
eanzhao May 8, 2026
b61450d
Promote card-mode chunks to a structurally separate proto type
eanzhao May 8, 2026
d5308b4
Merge pull request #590 from aevatarAI/feat/2026-05-07_lark-streaming…
eanzhao May 8, 2026
b78ef5a
PR #562 fix-check follow-up: address 6 still-flagged threads
eanzhao May 8, 2026
1ff822b
Merge branch 'dev' into feature/lark-bot
eanzhao May 8, 2026
297ace3
Revert AddOrnnSkills NyxId safety net (production regression)
eanzhao May 8, 2026
36b8b5c
Default StreamingCardKitEnabled to true so card-mode lights up by def…
eanzhao May 8, 2026
836f91c
Pin streaming-enabled inbox test to legacy chunk type, add card-defau…
eanzhao May 8, 2026
87c2c05
Fix LarkCardKitClient body shape: data/settings as JSON-encoded strings
eanzhao May 8, 2026
c026d6c
ssh_exec: hard wall-clock cap so NyxID hangs don't blow LLM run budget
eanzhao May 8, 2026
e108a6d
Merge branch 'dev' into feature/lark-bot
eanzhao May 8, 2026
fcd3e1d
Fix NyxID SSH exec test stability
eanzhao May 8, 2026
4ac6b45
Bump LLM reply turn budget 120s → 300s
eanzhao May 8, 2026
59fd77a
Rename daily_report → daily everywhere
eanzhao May 8, 2026
f078068
Refactor LLM reply continuation into agent runs
eanzhao May 8, 2026
4d31255
Remove /daily, /social-media, and the agent_builder template path
eanzhao May 8, 2026
c108ef4
Address agent run review comments
eanzhao May 8, 2026
2e4a1d1
Harden agent run output retry handling
eanzhao May 8, 2026
c2f1969
Merge pull request #597 from aevatarAI/refactor/2026-05-08_agent-run-…
eanzhao May 8, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions aevatar.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@
<Project Path="test/Aevatar.Studio.Tests/Aevatar.Studio.Tests.csproj" />
<Project Path="test\Aevatar.AI.Core.Tests\Aevatar.AI.Core.Tests.csproj" />
<Project Path="test\Aevatar.AI.Tests\Aevatar.AI.Tests.csproj" />
<Project Path="test\Aevatar.AI.ToolProviders.Ornn.Tests\Aevatar.AI.ToolProviders.Ornn.Tests.csproj" />
<Project Path="test\Aevatar.AI.ToolProviders.ServiceInvoke.Tests\Aevatar.AI.ToolProviders.ServiceInvoke.Tests.csproj" />
<Project Path="test\Aevatar.AI.ToolProviders.Lark.Tests\Aevatar.AI.ToolProviders.Lark.Tests.csproj" />
<Project Path="test\Aevatar.AI.ToolProviders.Telegram.Tests\Aevatar.AI.ToolProviders.Telegram.Tests.csproj" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,6 @@ namespace Aevatar.GAgents.Authoring.Lark;
/// </remarks>
internal static class AgentBuilderActionIds
{
public const string DailyReport = "create_daily_report";
public const string SocialMedia = "create_social_media";
public const string OpenDailyReportForm = "open_daily_report_form";
public const string OpenSocialMediaForm = "open_social_media_form";
public const string ListTemplates = "list_templates";
public const string ListAgents = "list_agents";
public const string AgentStatus = "agent_status";
public const string RunAgent = "run_agent";
Expand Down
257 changes: 2 additions & 255 deletions agents/Aevatar.GAgents.Authoring.Lark/AgentBuilderCardContent.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
using System.Text;
using System.Text.Json;
using Aevatar.GAgents.Channel.Abstractions;
using Aevatar.GAgents.Scheduled;

namespace Aevatar.GAgents.Authoring.Lark;

Expand All @@ -12,183 +11,7 @@ namespace Aevatar.GAgents.Authoring.Lark;
/// </summary>
public static class AgentBuilderCardContent
{
private const string DailyReportAction = AgentBuilderActionIds.DailyReport;
private const string SocialMediaAction = AgentBuilderActionIds.SocialMedia;
private const string OpenDailyReportFormAction = AgentBuilderActionIds.OpenDailyReportForm;
private const string OpenSocialMediaFormAction = AgentBuilderActionIds.OpenSocialMediaForm;
private const string ListTemplatesAction = AgentBuilderActionIds.ListTemplates;
private const string ListAgentsAction = AgentBuilderActionIds.ListAgents;
private const string DefaultScheduleTime = "09:00";

public static MessageContent BuildDailyReportForm(string? preferredGithubUsername) =>
BuildDailyReportForm(preferredGithubUsername, introCard: null);

/// <summary>
/// Builds the Daily Report creation form card. When <paramref name="introCard"/> is null the
/// default Day One description card is rendered; callers that need a different header (for
/// example, the credentials-required re-prompt) pass their own <see cref="CardBlock"/> and this
/// method uses it verbatim instead.
/// </summary>
public static MessageContent BuildDailyReportForm(
string? preferredGithubUsername,
CardBlock? introCard)
{
var normalizedSaved = string.IsNullOrWhiteSpace(preferredGithubUsername)
? null
: preferredGithubUsername!.Trim();

var content = new MessageContent();
content.Cards.Add(introCard ?? BuildDefaultDailyReportIntroCard(normalizedSaved));

// Pre-fill the saved GitHub username into the input's default_value so users see it inline
// and can keep it with one submit click. Placeholder stays as a generic hint so the field
// does not disappear when the user clicks to edit.
var githubInput = BuildTextInput(
"github_username",
"GitHub Username",
placeholder: "octocat");
if (normalizedSaved is not null)
githubInput.Value = normalizedSaved;
content.Actions.Add(githubInput);

content.Actions.Add(BuildTextInput(
"repositories",
"Repositories (Optional)",
"owner/repo, owner/repo"));
content.Actions.Add(BuildTextInput(
"schedule_time",
"Daily Time (HH:mm)",
DefaultScheduleTime));
content.Actions.Add(BuildTextInput(
"schedule_timezone",
"Time Zone",
SkillRunnerDefaults.DefaultTimezone));

var submit = BuildFormSubmit(
"submit_daily_report",
"Create Agent",
isPrimary: true);
submit.Arguments["agent_builder_action"] = DailyReportAction;
submit.Arguments["run_immediately"] = "true";
content.Actions.Add(submit);

return content;
}

private static CardBlock BuildDefaultDailyReportIntroCard(string? savedGithubUsername)
{
var savedNote = savedGithubUsername is null
? string.Empty
: $"\n\nSaved GitHub username: `{savedGithubUsername}` — it is already filled in, just press **Create Agent** to reuse it.";

return new CardBlock
{
Kind = CardBlockKind.Section,
BlockId = "daily_report_intro",
Title = "Create Daily Report Agent",
Text =
"**Day One template:** Daily GitHub report\n" +
"Fill in the fields below. The agent will run once now and then repeat every day at your chosen local time." +
savedNote,
};
}

public static MessageContent BuildSocialMediaForm()
{
var content = new MessageContent();
content.Cards.Add(new CardBlock
{
Kind = CardBlockKind.Section,
BlockId = "social_media_intro",
Title = "Create Social Media Agent",
Text =
"**Workflow-backed template:** Social media draft + approval\n" +
"Fill in the fields below. Each scheduled run will generate one draft and send approval instructions into this Feishu private chat.",
});

content.Actions.Add(BuildTextInput(
"topic",
"Topic",
"Launch update for the new workflow feature"));
content.Actions.Add(BuildTextInput(
"audience",
"Audience (Optional)",
"Developers and technical founders"));
content.Actions.Add(BuildTextInput(
"style",
"Style (Optional)",
"Confident, concise, product-focused"));
content.Actions.Add(BuildTextInput(
"schedule_time",
"Daily Time (HH:mm)",
DefaultScheduleTime));
content.Actions.Add(BuildTextInput(
"schedule_timezone",
"Time Zone",
SkillRunnerDefaults.DefaultTimezone));

var submit = BuildFormSubmit(
"submit_social_media",
"Create Agent",
isPrimary: true);
submit.Arguments["agent_builder_action"] = SocialMediaAction;
submit.Arguments["run_immediately"] = "true";
content.Actions.Add(submit);

return content;
}

/// <summary>
/// Builds the post-tool acknowledgment for the Day One daily report creation flow.
/// The tool response returns GitHub username, preference-save status, and run_immediately trigger
/// status, which this method folds into a short text reply that leads with "running now" when
/// the schedule fired the first report, so the user knows a report is on the way.
/// </summary>
public static MessageContent FormatDailyReportToolReply(JsonElement root)
{
if (TryReadError(root, out var error))
return TextContent($"Create daily report agent failed: {error}");

var status = TryReadString(root, "status") ?? "accepted";
if (string.Equals(status, "credentials_required", StringComparison.OrdinalIgnoreCase) ||
string.Equals(status, "oauth_required", StringComparison.OrdinalIgnoreCase))
{
return BuildDailyReportCredentialsCard(root, status);
}

var agentId = TryReadString(root, "agent_id") ?? "unknown-agent";
var githubUsername = TryReadString(root, "github_username");
var savedPreference = TryReadBool(root, "github_username_preference_saved");
// The tool reports whether it asked the skill-runner actor to run now, not whether the
// runner actually finished — hence "requested", not "triggered". The ack text still says
// "Running first report now" because we sent the command; if it fails downstream, the
// ground-truth status surfaces through /agent-status, not through this immediate reply.
var runImmediatelyRequested = TryReadBool(root, "run_immediately_requested");
var nextRun = TryReadString(root, "next_scheduled_run") ?? "pending";

var headline = runImmediatelyRequested
? (string.IsNullOrWhiteSpace(githubUsername)
? "Daily report scheduled. Running first report now — I'll reply with the results shortly."
: $"Daily report scheduled for `{githubUsername}`. Running first report now — I'll reply with the results shortly.")
: (string.IsNullOrWhiteSpace(githubUsername)
? "Daily report scheduled."
: $"Daily report scheduled for `{githubUsername}`.");

var lines = new List<string> { headline };
if (savedPreference && !string.IsNullOrWhiteSpace(githubUsername))
lines.Add($"Saved `{githubUsername}` as your default GitHub username.");

lines.Add($"Next scheduled run: {nextRun}");
lines.Add($"Agent ID: {agentId}");

var note = TryReadOptional(root, "note");
if (note is not null)
lines.Add(note);

lines.Add($"Next commands: /agents, /agent-status {agentId}, /run-agent {agentId}");

return TextContent(string.Join('\n', lines));
}

/// <summary>
/// Renders <c>/agents</c> as a single consolidated card. The earlier design produced one
Expand Down Expand Up @@ -222,10 +45,7 @@ public static MessageContent FormatListAgentsResult(JsonElement root, string? no
emptyBody.Append(notice);
emptyBody.Append("\n\n");
}
emptyBody.Append("No agents yet. Create one to get started:\n");
emptyBody.Append("- `/daily` — daily GitHub report\n");
emptyBody.Append("- `/social-media` — social-media drafter\n\n");
emptyBody.Append("Run `/templates` to browse all available templates.");
emptyBody.Append("No agents yet.");

content.Cards.Add(new CardBlock
{
Expand All @@ -234,9 +54,7 @@ public static MessageContent FormatListAgentsResult(JsonElement root, string? no
Title = "Your Agents",
Text = emptyBody.ToString(),
});
content.Actions.Add(BuildAction("Create Daily Report", OpenDailyReportFormAction, isPrimary: true));
content.Actions.Add(BuildAction("Create Social Media", OpenSocialMediaFormAction, isPrimary: false));
content.Actions.Add(BuildAction("Templates", ListTemplatesAction, isPrimary: false));
content.Actions.Add(BuildAction("Refresh", ListAgentsAction, isPrimary: false));
return content;
}

Expand Down Expand Up @@ -285,14 +103,7 @@ public static MessageContent FormatListAgentsResult(JsonElement root, string? no
Text = bodyBuilder.ToString(),
});

// Footer is intentionally limited to discovery / creation shortcuts. Per-agent actions
// (status, run, disable, enable, delete) deliberately stay off this card to avoid the
// visual "list + status panel" duplication called out in issue #476; the inline command
// hints in the body cover the same ground without the layout noise.
content.Actions.Add(BuildAction("Refresh", ListAgentsAction, isPrimary: false));
content.Actions.Add(BuildAction("Templates", ListTemplatesAction, isPrimary: false));
content.Actions.Add(BuildAction("Create Daily Report", OpenDailyReportFormAction, isPrimary: false));
content.Actions.Add(BuildAction("Create Social Media", OpenSocialMediaFormAction, isPrimary: false));
return content;
}

Expand All @@ -315,67 +126,6 @@ private static ActionElement BuildAction(string label, string agentBuilderAction
return button;
}

private static MessageContent BuildDailyReportCredentialsCard(JsonElement root, string status)
{
var providerId = TryReadString(root, "provider_id") ?? "unknown-provider";
var url = TryReadString(root, "authorization_url")
?? TryReadString(root, "auth_url")
?? TryReadString(root, "url")
?? TryReadString(root, "documentation_url");
var note = TryReadString(root, "note")
?? "Enter your GitHub username below — I'll save it as your default and run the report immediately.";
var heading = string.Equals(status, "oauth_required", StringComparison.OrdinalIgnoreCase)
? "GitHub authorization required."
: "GitHub credentials required.";

var descriptionLines = new List<string>
{
$"**{heading}**",
note,
$"Provider ID: `{providerId}`",
};
if (!string.IsNullOrWhiteSpace(url))
descriptionLines.Add($"Open: {url}");
descriptionLines.Add("Or just reply with `/daily <github_username>` — I'll save it and run the report now.");

var introCard = new CardBlock
{
Kind = CardBlockKind.Section,
BlockId = "daily_report_credentials",
Title = "Create Daily Report Agent",
Text = string.Join('\n', descriptionLines),
};

// Echo the username the user already submitted (e.g. `/daily eanzhao`) so it pre-fills
// the form on the auth-required re-prompt — otherwise users had to retype it after the
// OAuth round-trip. The card body alone carries the auth instructions; setting
// content.Text in addition would double-render in Lark form mode (LarkMessageComposer's
// BuildLeadingMarkdown concatenates Text and the first card body), which is the original
// duplicate "GitHub authorization required" block users were seeing.
var submittedGithubUsername = TryReadString(root, "github_username");
return BuildDailyReportForm(
preferredGithubUsername: submittedGithubUsername,
introCard: introCard);
}

private static ActionElement BuildTextInput(string actionId, string label, string placeholder) =>
new()
{
Kind = ActionElementKind.TextInput,
ActionId = actionId,
Label = label,
Placeholder = placeholder,
};

private static ActionElement BuildFormSubmit(string actionId, string label, bool isPrimary) =>
new()
{
Kind = ActionElementKind.FormSubmit,
ActionId = actionId,
Label = label,
IsPrimary = isPrimary,
};

private static MessageContent TextContent(string text) => AgentBuilderJson.TextContent(text);

private static bool TryReadError(JsonElement root, out string error) =>
Expand All @@ -384,9 +134,6 @@ private static bool TryReadError(JsonElement root, out string error) =>
private static string? TryReadString(JsonElement element, string propertyName) =>
AgentBuilderJson.TryReadString(element, propertyName);

private static bool TryReadBool(JsonElement element, string propertyName) =>
AgentBuilderJson.TryReadBool(element, propertyName);

private static string? TryReadOptional(JsonElement element, string propertyName) =>
AgentBuilderJson.TryReadOptional(element, propertyName);
}
Loading
Loading