trustgraph/ts/packages/cli/src/commands/agent.ts
elpresidank 0746d7ffd5 feat(ts): add real quality gates — Biome lint + effect-law ratchet + class inventory
- biome.json (2.4.16, linter-only) wired as "lint" in all six packages
- scripts/check-effect-laws.ts: Effect-native law enforcement encoding the
  adapted beep-effect effect-first/schema-first laws (no native JSON/switch/
  sort/fetch/timers, no process.env, no throw new, no Effect.run* outside
  boundaries, no Schema-suffixed constants, no node:fs/path, AST-based
  pure-data interface detection per law 38/39)
- ratcheting baseline allowlist (95 entries / 290 findings) that must shrink
  to documented exemptions only; stale counts fail the gate
- root lint chains turbo lint + law check + native-class inventory
- fix all 163 initial Biome findings: import-type style, templates, two `any`s,
  ten non-null assertions (librarian getService gate, A.matchRight in atoms,
  ensureNode returning nodes, main.tsx mount guard)

Gates: lint, check:tsgo, build, test (force, 11 tasks) all green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 06:40:01 -05:00

84 lines
2.9 KiB
TypeScript

/**
* Agent CLI commands.
*
* Python reference: trustgraph-cli/trustgraph/cli/invoke_agent.py
*/
import { Effect } from "effect";
import * as Argument from "effect/unstable/cli/Argument";
import * as Command from "effect/unstable/cli/Command";
import type { CliCommandError } from "./util.js";
import { cliCommandError, withGatewayClient, } from "./util.js";
function asRecord(value: unknown): Record<string, unknown> {
return typeof value === "object" && value !== null && !Array.isArray(value)
? value as Record<string, unknown>
: {};
}
function stringProperty(source: unknown, key: string): string | undefined {
const value = asRecord(source)[key];
return typeof value === "string" ? value : undefined;
}
function booleanProperty(source: unknown, key: string): boolean | undefined {
const value = asRecord(source)[key];
return typeof value === "boolean" ? value : undefined;
}
function responseErrorMessage(source: unknown): string | undefined {
const error = asRecord(source).error;
if (typeof error === "string") return error;
return stringProperty(error, "message");
}
export const agentCommand = Command.make("agent", {
question: Argument.string("question").pipe(Argument.withDescription("Question to ask")),
}, ({ question }) =>
withGatewayClient((client, opts) =>
Effect.gen(function* () {
let streamError: CliCommandError | undefined;
yield* client.runDispatchStream(
{
scope: "flow",
flow: opts.flow,
service: "agent",
request: {
question,
user: opts.user,
collection: "default",
streaming: true,
},
},
(chunk) => {
const resp = asRecord(chunk.response);
const chunkType = stringProperty(resp, "chunk_type");
const error = chunkType === "error" ? responseErrorMessage(resp) ?? "Unknown agent error" : responseErrorMessage(resp);
if (error !== undefined) {
streamError = cliCommandError("agent", error);
return true;
}
const content = stringProperty(resp, "content") ?? "";
const messageComplete = booleanProperty(resp, "end_of_message") === true;
const dialogComplete = chunk.complete === true || booleanProperty(resp, "end_of_dialog") === true;
if (chunkType === "thought" || chunkType === "observation") {
if (content.length > 0) process.stderr.write(content);
} else if (chunkType === "answer" || chunkType === "final-answer") {
if (content.length > 0) process.stdout.write(content);
if (messageComplete || dialogComplete) process.stdout.write("\n");
}
return dialogComplete;
},
{ timeoutMs: 120_000, retries: 2 },
);
if (streamError !== undefined) {
return yield* streamError;
}
}),
),
).pipe(Command.withDescription("Ask the TrustGraph agent a question"));