openpencil/apps/desktop/git/error.ts
Kayshen-X a7d73ebb62 feat(ai): pencil-style agentic design tool-loop, multi-chat tabs, #27 panel restyle
Built-in design generation now runs as an agentic MCP tool-loop (reusing the
agent-rs BuiltInProvider), gated behind OPENPENCIL_DESIGN_AGENT_LOOP / the
Settings experimental toggle; the orchestrator stays the default.

- design-agent system prompt + in-process design toolset (parity-locked with
  the MCP surface) + flag-gated Intent::Design routing
- spawn_agents execution as sequential sub-loops + live creation-mode badges
  (per-agent glow + 'N/M designing...' header)
- new MCP tools: get_guidelines, ToolSearch, get_screenshot, get_editor_state,
  export_nodes, spawn_agents; style-guide local audit
- #27 AI panel restyle: rounded tool cards + green check-rings, gray user
  bubbles, model-pill bottom toolbar, header, empty-state pills, the
  PARALLEL AGENTS (agent_team_size) 1x-6x chip dropdown
- multi-chat tabs: ChatSessions model (Deref-to-active) + tab row UI
  (switch / close / + / Cmd+T) with each run bound to its tab

Large checkpoint commit spanning the working tree (Rust shell crates).
2026-07-02 21:21:06 +08:00

75 lines
2 KiB
TypeScript

// apps/desktop/git/error.ts
//
// Unified error type for the desktop git layer. Phase 1b only emits a subset
// of these codes; Phase 2 will throw the rest (auth, network, merge, etc.)
// without modifying this file.
/**
* The complete error code union. New codes should land here, not in callsites,
* so the renderer's error matrix in the spec stays in sync with the reality.
*/
export type GitErrorCode =
// Phase 1b emits these:
| 'init-failed'
| 'open-failed'
| 'not-a-repo'
| 'commit-empty'
| 'branch-exists'
| 'branch-current'
| 'branch-unmerged'
| 'engine-crash'
// Phase 2 will emit these (declared here for forward-compat):
| 'no-file'
| 'clone-failed'
| 'clone-target-exists'
| 'clone-network'
| 'auth-required'
| 'auth-failed'
| 'auth-token-invalid'
| 'network'
| 'timeout'
| 'commit-author-missing'
| 'pull-non-fast-forward'
| 'push-rejected'
| 'push-no-remote'
| 'branch-switch-dirty'
| 'merge-conflict'
| 'merge-conflict-non-op'
| 'merge-still-conflicted'
| 'merge-abort-failed'
| 'restore-dirty'
| 'ssh-not-supported-iso'
| 'ssh-key-missing'
| 'concurrent-busy'
| 'external-modified'
| 'save-required';
export class GitError extends Error {
readonly code: GitErrorCode;
readonly recoverable: boolean;
readonly detail?: unknown;
constructor(
code: GitErrorCode,
message: string,
opts: { recoverable?: boolean; detail?: unknown; cause?: unknown } = {},
) {
super(message, opts.cause !== undefined ? { cause: opts.cause } : undefined);
this.name = 'GitError';
this.code = code;
this.recoverable = opts.recoverable ?? true;
if (opts.detail !== undefined) this.detail = opts.detail;
}
}
/**
* Type guard for catching GitError specifically (since `instanceof` across
* realms can be flaky in tests, this is a defensive backup).
*/
export function isGitError(err: unknown): err is GitError {
return (
err instanceof GitError ||
(typeof err === 'object' && err !== null && (err as { name?: string }).name === 'GitError')
);
}