/** * Backend-specific constants * Re-exported from the main config constants for backend use */ export declare const PROCESS_EXIT_CODES: { readonly SUCCESS: 0; readonly ERROR: 1; readonly RESTART_REQUESTED: 120; }; /** Project tickets (specs/2026-09-28-project-tickets.md) — see config/constants.ts. */ export declare const PROJECT_TICKET_CONSTANTS: { readonly DIR_NAME: "tickets"; readonly REPO_RELATIVE_DIR: ".crewly/tickets"; readonly COUNTER_FILENAME: ".counter.json"; readonly LOCK_FILENAME: ".lock"; readonly FOLDER_GITIGNORE_CONTENT: "# Crewly: transient files, never commit\n.lock\n*.tmp.*\n"; readonly LOCK_STALE_MS: number; readonly LOCK_RETRY_MS: 25; readonly LOCK_TIMEOUT_MS: number; readonly STATUSES: readonly ["backlog", "ready", "in_progress", "review", "done", "cancelled"]; readonly PRIORITIES: readonly ["P0", "P1", "P2", "P3"]; readonly DEFAULT_PRIORITY: "P2"; readonly DEFAULT_STATUS: "backlog"; readonly FALLBACK_PREFIX: "T"; readonly MAX_PREFIX_LENGTH: 4; readonly MAX_SLUG_LENGTH: 48; readonly SECTIONS: { readonly DESCRIPTION: "Description"; readonly ACCEPTANCE: "Acceptance criteria"; readonly LOG: "Log"; }; readonly MIGRATION_SOURCE: "v1-migration"; readonly MIGRATION_STATUS_FOLDERS: readonly ["open", "in_progress"]; readonly MIGRATION_V1_DIR: "tasks"; readonly GITIGNORE_BLOCK: readonly ["# Crewly project tickets are tracked (specs/2026-09-28-project-tickets.md)", "!.crewly/", ".crewly/*", "!.crewly/tickets/", "!.crewly/tickets/**"]; readonly WORK_ITEM_METADATA_KEY: "projectTicket"; readonly SYNC_SWEEP_INTERVAL_MS: number; readonly MAX_SUCCESSOR_HOPS: 8; readonly DELEGATION_TICKET_BODY_KEY: "projectTicketId"; readonly DELEGATION_CALLER_METADATA_KEY: "delegatedBy"; readonly DELEGATION_WORK_ITEM_TYPES: readonly ["delegate"]; readonly DELEGATION_TITLE_MAX_CHARS: 120; readonly DELEGATION_REFUSED_CODE: "project_ticket_refused"; }; /** * Ticket autopilot (specs/2026-09-30-ticket-autopilot.md): a per-project switch * that wakes the project's driver (its team lead) to triage the backlog, with * brakes (one live triage, re-trigger cadence, daily budget) and phone-first * owner notifications (batched questions, one evening digest). */ export declare const TICKET_AUTOPILOT_CONSTANTS: { /** WorkItem type of the triage item the driver receives */ readonly TRIAGE_WORK_ITEM_TYPE: "ticket_triage"; /** `metadata.kind` of the triage item (also how a live one is found) */ readonly TRIAGE_METADATA_KIND: "ticket_triage"; /** Label a ticket carries while it waits on an answer from the owner */ readonly NEEDS_OWNER_LABEL: "needs-owner"; /** Log-line prefix holding the one-line question for the owner */ readonly OWNER_QUESTION_LOG_PREFIX: "owner question: "; /** Max characters of an owner question */ readonly OWNER_QUESTION_MAX_CHARS: 280; /** How often the autopilot re-evaluates every enabled project (ms) */ readonly TICK_INTERVAL_MS: number; /** Re-trigger a triage for a project at most this often on the periodic tick (ms) */ readonly TRIAGE_MIN_INTERVAL_MS: number; /** * A member going idle with nothing ready triggers a triage "immediately", * but never sooner than this after the previous one (ms) — idle events * repeat every few seconds and must not become a wake loop. */ readonly IDLE_TRIGGER_MIN_INTERVAL_MS: number; /** A ticket already listed in a triage (and unchanged since) is listed again only after this long (ms) */ readonly TRIAGE_RELIST_AFTER_MS: number; /** A `ready` ticket untouched this long while someone is idle counts as "nobody takes it" (ms) */ readonly READY_STALE_MS: number; /** A triage item still queued (never picked up) after this long is cancelled and may be replaced (ms) */ readonly TRIAGE_STALE_QUEUED_MS: number; /** Max tickets listed in one triage brief (highest priority, then oldest first) */ readonly TRIAGE_MAX_TICKETS: 20; /** Max characters of a ticket description quoted in the triage brief */ readonly TRIAGE_DESCRIPTION_EXCERPT_CHARS: 300; /** Max characters of a member's role responsibility line in the triage brief */ readonly ROLE_RESPONSIBILITY_MAX_CHARS: 160; /** * One-line responsibility of roles that ship without a `role.json` * description (the brief falls back to these; a member's own * `jobDescription` and a role's description win). */ readonly ROLE_RESPONSIBILITY_FALLBACKS: Readonly>; /** Local hour (0-23) at or after which the daily digest is sent */ readonly DIGEST_HOUR_LOCAL: 21; /** Max tickets named per digest section (the rest are counted) */ readonly DIGEST_MAX_ITEMS_PER_SECTION: 8; /** Default daily budget (tokens) of the project's team agents when the owner sets none */ readonly DEFAULT_DAILY_BUDGET_TOKENS: 20000000; /** Default and bounds of in-progress tickets per member */ readonly DEFAULT_MAX_IN_FLIGHT_PER_MEMBER: 1; readonly MAX_IN_FLIGHT_PER_MEMBER_LIMIT: 5; /** State file under CREWLY_HOME (debounce / notification bookkeeping, survives restarts) */ readonly STATE_FILENAME: "ticket-autopilot-state.json"; /** Env kill switch: `0` keeps the autopilot service from starting */ readonly ENV_SWITCH: "CREWLY_TICKET_AUTOPILOT"; /** Default and largest range of `GET …/stats` and `…/runs` (days, today included) */ readonly STATS_DEFAULT_DAYS: 14; readonly STATS_MAX_DAYS: 90; /** WorkItem type / `metadata.kind` of the daily retro the driver receives */ readonly RETRO_WORK_ITEM_TYPE: "autopilot_retro"; readonly RETRO_METADATA_KIND: "autopilot_retro"; /** Local hour (0-23) at or after which yesterday's retro is scheduled */ readonly RETRO_HOUR_LOCAL: 9; /** Wiki folder (inside the project vault) the retros are written to */ readonly RETRO_WIKI_DIR: "llm-curated/autopilot-retros"; /** Project (by name) harness-gap tickets are filed on */ readonly RETRO_HARNESS_PROJECT: "Crewly"; /** * Hold on a harness-gap ticket until the owner approves it: triage skips * it; only the retro card's answer removes it (not ask-owner --clear, * not other decisions), and the digest does not list it as waiting. */ readonly RETRO_PENDING_LABEL: "retro-pending"; /** * Labels that keep a ticket out of triage by default (the owner or the * driver parked it). Per project: `ticketAutopilot.skipLabels`. */ readonly DEFAULT_SKIP_LABELS: readonly string[]; /** Max skip labels a project may configure */ readonly SKIP_LABELS_LIMIT: 20; /** Retro scheduling / check-in reads that fail are retried after this, doubling up to the max */ readonly RETRY_BACKOFF_MIN_MS: number; readonly RETRY_BACKOFF_MAX_MS: number; /** Labels of a harness-gap ticket filed by a retro */ readonly RETRO_GAP_LABELS: readonly string[]; /** Most harness-gap tickets filed per day, across projects */ readonly RETRO_MAX_GAPS_PER_DAY: 3; /** Word overlap (Jaccard) at or above which a gap duplicates an open ticket / earlier gap */ readonly RETRO_DEDUPE_SIMILARITY: 0.6; /** How long filed gap titles are remembered for the dedupe (days) */ readonly RETRO_GAP_MEMORY_DAYS: 60; /** Longest retro summary / problem detail accepted (chars) */ readonly RETRO_SUMMARY_MAX_CHARS: 6000; readonly RETRO_DETAIL_MAX_CHARS: 1000; /** Problem title bounds (chars) and most problems per retro */ readonly RETRO_TITLE_MIN_CHARS: 4; readonly RETRO_TITLE_MAX_CHARS: 140; readonly RETRO_MAX_PROBLEMS: 20; /** Problem classes */ readonly RETRO_CLASSES: readonly string[]; /** Decision kind of the one card that approves a retro's harness-gap tickets */ readonly RETRO_DECISION_KIND: "retro_harness_gaps"; /** How long the owner has to answer it (the default, Skip, applies after) */ readonly RETRO_DECISION_DEADLINE_MS: number; /** Option labels of that card */ readonly RETRO_APPROVE_LABEL: "Approve"; readonly RETRO_SKIP_LABEL: "Skip"; /** WorkItem type / `metadata.kind` of the goal replan the driver receives when nothing is left to triage */ readonly REPLAN_WORK_ITEM_TYPE: "goal_replan"; readonly REPLAN_METADATA_KIND: "goal_replan"; /** * Largest explicit `replansPerDay` (0 = off). Absent, the speed mode's * daily cap applies ({@link TICKET_AUTOPILOT_CONSTANTS.SPEED_MODES}). */ readonly REPLANS_PER_DAY_LIMIT: 12; /** * Safety ceiling on ALL goal replans of a local day, productive or not * (CREW-265). The daily cap (`replansPerDay` / the mode's * `replansPerDayCap`) counts only replans that opened no tickets, so an * autopilot whose replans keep producing work keeps going; this ceiling * still stops a runaway loop. Never below the daily cap itself. */ readonly REPLAN_HARD_CEILING_PER_DAY: 16; /** Tickets closed (done / cancelled) this many days back are listed in the replan brief */ readonly REPLAN_CLOSED_LOOKBACK_DAYS: 7; /** Most closed tickets listed in the replan brief */ readonly REPLAN_MAX_CLOSED_TICKETS: 20; /** Most characters of goal text quoted in the replan brief */ readonly REPLAN_GOAL_MAX_CHARS: 2000; /** Most goals.md entries quoted (newest first) */ readonly REPLAN_GOAL_MAX_ENTRIES: 3; /** Most open experiment cards listed in the replan brief */ readonly REPLAN_MAX_EXPERIMENTS: 5; /** * A replan still live (any live status) after this many hours is expired: * cancelled where the pool allows it, and never counted as live again, so * it cannot hold triage forever. Default and bounds of `replanTtlHours`. */ readonly DEFAULT_REPLAN_TTL_HOURS: 4; /** Idle and empty: a replan waits at least this long after the last one (ms; keeps it from looping) */ readonly IDLE_REPLAN_DEBOUNCE_MS: number; readonly REPLAN_TTL_HOURS_LIMIT: 48; /** A live replan older than this stops holding triage once there are tickets to triage (ms) */ readonly REPLAN_YIELD_AFTER_MS: number; /** Goals-log entries older than this are not an active goal (days); OKRs keep their own status */ readonly REPLAN_GOAL_ACTIVE_DAYS: 30; /** Speed modes, slowest last */ readonly SPEED_MODE_NAMES: readonly string[]; /** Mode of a project that never set one (every project before speed modes) */ readonly DEFAULT_SPEED_MODE: "normal"; /** * What each mode supplies. Explicit settings win: `dailyBudgetTokens` * (and boosts) over the mode's budget, `replansPerDay` over its daily cap. * * - `replanMinGapMs` — a replan never starts sooner than this after the last one * - `replansPerDayCap` — hard cap per local day (the safety brake) * - `selfReviewEveryMs` — cadence of the driver's self-review * - `emptyReplanRetry` — after a replan that opened no tickets: retry this * many hours later, or on the local day this many days after the replan's day * - `dailyBudgetTokens` — the budget when the project sets none */ readonly SPEED_MODES: Readonly; dailyBudgetTokens: number; /** * Stalled work: an in-progress ticket whose assignee has sat idle * (registered, no turn running) with no ticket / WorkItem change for * this long gets its brief re-delivered; after * STALL_MAX_REDELIVERIES it goes back to ready for the next idle member. */ stallAfterMs: number; }>>>; /** WorkItem type / `metadata.kind` of the driver's self-review */ readonly SELF_REVIEW_WORK_ITEM_TYPE: "autopilot_self_review"; readonly SELF_REVIEW_METADATA_KIND: "autopilot_self_review"; /** A self-review still live after this long no longer holds the next one (a queued one is cancelled) */ readonly SELF_REVIEW_LIVE_MAX_MS: number; /** Longest self-review field accepted (chars) */ readonly SELF_REVIEW_FIELD_MAX_CHARS: 600; /** Self-reviews kept per project (newest last) */ readonly SELF_REVIEW_HISTORY: 10; /** Most characters of the goal quoted in the self-review brief */ readonly SELF_REVIEW_GOAL_MAX_CHARS: 800; /** A self-review older than this is not shown in the evening digest */ readonly SELF_REVIEW_DIGEST_MAX_AGE_MS: number; /** Added to the budget-paused notice when no spend cap covers the team (`` is replaced) */ readonly NO_TEAM_CAP_NOTE: "This budget only pauses the autopilot: no spend cap is set for , so its other work (scheduled tasks, follow-ups after a task) is not stopped. Reply `set team cap for to 50M` to stop it all at a limit."; /** Stop reasons (why the autopilot is not producing work), most decisive first */ readonly STOP_REASONS: readonly string[]; /** Failed project WorkItems this recent count as a system error (ms) */ readonly STOP_SYSTEM_ERROR_LOOKBACK_MS: number; /** An autopilot WorkItem still queued this long is stuck delivery (a system error, ms) */ readonly STOP_STUCK_DELIVERY_MS: number; /** Re-deliveries of a stalled ticket's brief before it goes back to ready (unassigned) */ readonly STALL_MAX_REDELIVERIES: 2; /** The metric line of a replan ticket (`Metric: → `) */ readonly METRIC_LINE_PREFIX: "Metric: "; /** Shortest metric reference accepted (chars) */ readonly METRIC_MIN_CHARS: 5; /** Longest metric reference accepted (chars) */ readonly METRIC_MAX_CHARS: 300; /** How often the owner's speed DM command re-reads the project names (ms) */ readonly SPEED_PROJECT_CACHE_REFRESH_MS: number; }; /** * Safe restart: drain in-flight agent turns before a shutdown kills the PTYs, * and resume the ones that were cut off after the next boot. * * The shared budget (drain timeout, env override, supervisor margin) lives in * `config/constants.ts` so the CLI and the systemd unit read the same numbers; * the rest is backend-only. */ export declare const SAFE_RESTART: { /** How often the drain re-checks whether any agent is still mid-turn (ms) */ readonly DRAIN_POLL_INTERVAL_MS: 2000; /** How often the drain repeats its "still waiting on …" log line (ms) */ readonly DRAIN_LOG_INTERVAL_MS: 15000; /** * A turn is over once the PTY has been quiet this long and shows no * "esc to interrupt" status bar (ms). Claude Code and Codex repaint a * ticking timer while working, so a quiet screen means a resting agent. */ readonly TURN_QUIET_MS: 15000; /** A delivery younger than this is always treated as in progress (ms) */ readonly TURN_START_GRACE_MS: 5000; /** Trailing screen lines inspected for the busy status bar */ readonly PROBE_TAIL_LINES: 15; /** Open messages kept per session (oldest dropped beyond this) */ readonly MAX_OPEN_MESSAGES_PER_SESSION: 5; /** Sessions whose last delivered message is remembered (labels runtime-started turns) */ readonly MAX_LAST_DELIVERED_SESSIONS: 500; /** Characters of the delivered text kept as a preview */ readonly PREVIEW_CHARS: 160; /** File under CREWLY_HOME holding turns cut off by the last shutdown */ readonly INTERRUPTED_TURNS_FILE: "interrupted-turns.json"; /** Interrupted turns older than this are not resumed (ms) */ readonly INTERRUPTED_TURN_MAX_AGE_MS: number; /** How long the resumer waits for the orchestrator to become active (ms) */ readonly RESUME_ORC_READY_TIMEOUT_MS: number; /** Poll interval while waiting for the orchestrator to become active (ms) */ readonly RESUME_ORC_POLL_MS: 5000; /** * Resume note sent once to an agent whose work a restart cut off; * `{work}` is the ticket (TKT-194) or "this request". */ readonly RESUME_NOTICE_TEMPLATE: "Crewly restarted while you were working on {work}. Continue where you left off and deliver."; /** `{work}` when no ticket is known */ readonly RESUME_DEFAULT_WORK_LABEL: "this request"; /** Matches a resume note at the start of a message (so notes never stack) */ readonly RESUME_NOTICE_PATTERN: RegExp; /** Line that introduces the original message under a resume note */ readonly RESUME_ORIGINAL_HEADER: "The message you were handling:"; /** Line that introduces an owner promise under a resume note */ readonly RESUME_PROMISE_HEADER: "You promised the owner:"; readonly DRAIN_TIMEOUT_MS: 120000; readonly DRAIN_ENV_VAR: "CREWLY_RESTART_DRAIN_MS"; readonly BACKGROUND_DRAIN_TIMEOUT_MS: 600000; readonly BACKGROUND_DRAIN_ENV_VAR: "CREWLY_RESTART_DRAIN_BACKGROUND_MS"; readonly SHUTDOWN_MARGIN_MS: 30000; readonly SIGNAL_DEDUP_WINDOW_MS: 1000; readonly READINESS_ENDPOINT: "/api/system/restart-readiness"; readonly SHUTDOWN_MARKER_DIR: "run"; readonly SHUTDOWN_MARKER_FILE: "shutdown-requested"; readonly WIND_DOWN_RESTART_GRACE_SECONDS: 180; readonly WIND_DOWN_SHUTDOWN_GRACE_SECONDS: 300; readonly WIND_DOWN_MAX_GRACE_SECONDS: 900; readonly WIND_DOWN_POLL_MS: 2000; readonly WIND_DOWN_TAG: "[Crewly wind-down]"; }; /** * Runtime turn state (specs/2026-10-02-restart-busy-and-resume.md): whether * an agent is mid-turn according to its runtime (Claude Code hooks and * transcript), not according to screen output. */ export declare const TURN_STATE_CONSTANTS: { /** An active turn with no hook event for this long (and nothing open) is no longer trusted (ms) */ readonly HOOK_SILENCE_MS: number; /** An open tool call or a running subagent / background task expires after this (ms) */ readonly OPEN_WORK_MAX_MS: number; /** * A background subagent / fork whose transcript has not grown for this long, * while the agent's own turn is over, is stalled: it stops counting as busy * and the agent is told (ms). Pia's fork died after 2 minutes and the agent * stayed "busy" for the full OPEN_WORK_MAX_MS (2026-10-12). */ readonly BACKGROUND_STALL_MS: number; /** Progress probes (a directory read plus a few stats) are reused for this long (ms) */ readonly BACKGROUND_PROBE_CACHE_MS: 30000; /** How often the stalled-background watchdog looks (ms) */ readonly BACKGROUND_WATCHDOG_INTERVAL_MS: 60000; /** After a delivery, wait this long for the runtime to report the turn started (ms) */ readonly DELIVERY_START_MS: number; /** A transcript that says "mid-turn" counts only if it changed within this window (ms) */ readonly TRANSCRIPT_FRESH_MS: number; /** Re-check interval for end-of-turn settling skipped while background work runs (ms) */ readonly SETTLE_RECHECK_MS: 60000; /** Clock slack between a Stop hook and the transcript's own turn-end entry (ms) */ readonly TRANSCRIPT_LAG_MS: 30000; /** Accepted SessionStart sources from the hook */ readonly SESSION_START_SOURCES: readonly ["startup", "resume", "clear", "compact"]; /** How long a located transcript path is reused before re-resolving (ms) */ readonly LOCATOR_CACHE_MS: 60000; /** Bytes read from the end of a transcript */ readonly TRANSCRIPT_TAIL_BYTES: number; /** Sessions tracked in memory (oldest dropped beyond this) */ readonly MAX_TRACKED_SESSIONS: 500; /** Open tool calls / subagents remembered per session */ readonly MAX_OPEN_PER_SESSION: 64; /** Accepted toolUseId / agentId values from the hook */ readonly ID_PATTERN: RegExp; /** Gemini CLI's busy footer ("esc to cancel, 12s"); only read for Gemini sessions */ readonly GEMINI_BUSY_MARKER: "esc to cancel"; }; export declare const AGENT_IDENTITY_CONSTANTS: { readonly ORCHESTRATOR: { readonly ID: "orchestrator"; readonly SESSION_NAME: "crewly-orc"; readonly ROLE: "orchestrator"; }; }; export declare const TIMING_CONSTANTS: { readonly RETRIES: { readonly MAX_ATTEMPTS: 3; readonly BASE_DELAY: 1000; readonly MAX_DELAY: 10000; }; readonly INTERVALS: { readonly HEALTH_CHECK: 30000; readonly MEMORY_CLEANUP: number; readonly STATUS_UPDATE: 10000; readonly ACTIVITY_MONITOR: 15000; readonly CLEANUP: 60000; readonly BATCH_DELAY: 500; readonly RATE_LIMIT_WINDOW: 1000; readonly TASK_CLEANUP: 300000; }; readonly TIMEOUTS: { readonly CLAUDE_INIT: 45000; readonly AGENT_SETUP: 90000; readonly TASK_COMPLETION: number; readonly WEBSOCKET: 30000; readonly ORCHESTRATOR_SETUP: 30000; readonly TASK_MONITOR_POLL: 2000; readonly HEALTH_CHECK_TIMEOUT: 1000; readonly HTTP_HEALTH_CHECK: 3000; readonly API_REQUEST_QUICK: 2000; readonly SHUTDOWN: 2000; readonly CONNECTION: 10000; readonly AGENT_DEFAULT: 300000; readonly CONTEXT_REFRESH: 1800000; readonly WS_PING: 60000; readonly WS_PING_INTERVAL: 25000; readonly BACKUP: 3600000; readonly RATE_LIMIT_WINDOW: 900000; readonly COMMAND_TIMESTAMP_OFFSET: 300000; readonly COMMAND_TIMESTAMP_OFFSET_LONG: 600000; }; }; export declare const MEMORY_CONSTANTS: { readonly PATHS: { readonly AGENTS_DIR: "agents"; readonly KNOWLEDGE_DIR: "knowledge"; readonly SESSIONS_DIR: "sessions"; readonly LATEST_SUMMARY: "latest-summary.md"; readonly DAILY_LOG_DIR: "logs/daily"; readonly GOALS_DIR: "goals"; readonly GOALS_FILE: "goals.md"; readonly FOCUS_FILE: "current_focus.md"; readonly DECISIONS_LOG: "decisions_log.md"; readonly LEARNING_DIR: "learning"; readonly WHAT_WORKED_FILE: "what_worked.md"; readonly WHAT_FAILED_FILE: "what_failed.md"; readonly GLOBAL_LEARNING_DIR: "learning"; readonly CROSS_PROJECT_INSIGHTS: "cross_project_insights.md"; readonly AGENTS_INDEX: "agents-index.json"; }; readonly AGENT_FILES: { readonly MEMORY: "memory.json"; readonly ROLE_KNOWLEDGE: "role-knowledge.json"; readonly PREFERENCES: "preferences.json"; readonly PERFORMANCE: "performance.json"; readonly SOP_CUSTOM_DIR: "sop-custom"; }; readonly PROJECT_FILES: { readonly INDEX: "index.json"; readonly PATTERNS: "patterns.json"; readonly DECISIONS: "decisions.json"; readonly GOTCHAS: "gotchas.json"; readonly RELATIONSHIPS: "relationships.json"; readonly TASK_HISTORY: "task-history.json"; readonly LEARNINGS: "learnings.md"; readonly ARCHIVE_DIR: "archive"; readonly ARCHIVED_TASK_DECISIONS: "task-completion-decisions.json"; readonly ARCHIVED_TASK_LEARNINGS: "task-completion-learnings.md"; }; readonly TASK_LOG: { readonly SKIPPED_ENTRY_ID: "skipped-task-log"; readonly WRAPPER_PREFIXES: readonly [RegExp, RegExp]; readonly CONTENT_PATTERNS: readonly [RegExp, RegExp]; }; readonly LIMITS: { readonly MAX_ROLE_KNOWLEDGE_ENTRIES: 500; readonly MAX_PATTERN_ENTRIES: 200; readonly MAX_DECISION_ENTRIES: 100; readonly MAX_GOTCHA_ENTRIES: 200; readonly MAX_RELATIONSHIP_ENTRIES: 500; readonly MAX_FILE_SIZE_BYTES: 1048576; readonly MAX_QUERY_RESULTS: 50; readonly MIN_CONFIDENCE_THRESHOLD: 0.2; readonly PRUNE_AFTER_DAYS: 90; }; readonly DEFAULTS: { readonly INITIAL_CONFIDENCE: 0.5; readonly CONFIDENCE_REINFORCEMENT: 0.1; readonly MAX_CONFIDENCE: 1; readonly MIN_CONFIDENCE: 0; }; readonly DEDUP: { readonly NEAR_DUPLICATE_JACCARD: 0.8; readonly MIN_TOKENS_FOR_NEAR: 4; readonly SUBSTRING_MIN_CHARS: 20; }; readonly SCHEMA: { readonly CURRENT_VERSION: 1; readonly MIN_SUPPORTED_VERSION: 1; }; readonly CATEGORIES: { readonly ROLE_KNOWLEDGE: readonly ["best-practice", "anti-pattern", "tool-usage", "workflow"]; readonly PATTERN: readonly ["api", "component", "service", "testing", "styling", "database", "config", "other"]; readonly GOTCHA_SEVERITY: readonly ["low", "medium", "high", "critical"]; readonly LEARNING: readonly ["pattern", "decision", "gotcha", "insight", "improvement"]; readonly RELATIONSHIP: readonly ["depends-on", "uses", "extends", "implements", "calls", "imported-by"]; }; }; export declare const CONTINUATION_CONSTANTS: { readonly DETECTION: { readonly IDLE_CYCLES_BEFORE_CHECK: 2; readonly STALE_THRESHOLD_MINUTES: 20; readonly ACTIVITY_POLL_INTERVAL_MS: 120000; }; readonly EVENTS: { readonly DEBOUNCE_MS: 5000; readonly DEDUP_WINDOW_MS: 10000; readonly CLEANUP_INTERVAL_MS: 60000; }; readonly ITERATIONS: { readonly DEFAULT_MAX: 10; readonly ABSOLUTE_MAX: 50; readonly WARNING_THRESHOLD: 0.8; }; readonly TIMEOUTS: { readonly ANALYSIS_MS: 10000; readonly ACTION_MS: 30000; readonly PROMPT_INJECTION_MS: 15000; }; readonly PATTERNS: { readonly COMPLETION: readonly ["task completed", "task done", "successfully completed", "implementation complete", "all tests pass", "ready for review"]; readonly WAITING: readonly ["waiting for", "awaiting", "blocked by", "need input", "please provide", "let me know"]; readonly ERROR: readonly ["error:", "failed:", "exception:", "cannot", "unable to", "permission denied"]; }; readonly CONFIDENCE: { readonly ACTION_THRESHOLD: 0.6; readonly HIGH: 0.8; readonly MEDIUM: 0.5; readonly LOW: 0.3; }; }; export declare const ORCHESTRATOR_RESTART_CONSTANTS: { readonly MAX_RESTARTS_PER_WINDOW: 3; readonly COOLDOWN_WINDOW_MS: 3600000; readonly RESTART_DELAY_MS: 5000; readonly MAX_CONSECUTIVE_FAILURES: 3; readonly GAVE_UP_RETRY_MS: 3600000; }; export declare const AGENT_SUSPEND_CONSTANTS: { readonly DEFAULT_IDLE_TIMEOUT_MINUTES: 30; readonly IDLE_CHECK_INTERVAL_MS: 120000; readonly DEBUG_LOG_MAX_AGE_HOURS: 24; readonly REHYDRATION_TIMEOUT_MS: 45000; readonly EXEMPT_ROLES: readonly ["orchestrator"]; readonly STARTED_AGENT_IDLE_TIMEOUT_MINUTES: 15; readonly IDLE_STOP_MEMORY_USED_PERCENT: 85; readonly IDLE_STOP_MIN_FREE_MB: 1024; readonly IDLE_STOP_SWAP_USED_PERCENT: 75; readonly ALWAYS_ON_ROLES: readonly ["orchestrator", "auditor"]; readonly AGENT_IDLE_STOP_TIMEOUT_MS: 1800000; readonly CRON_KEEPALIVE_WINDOW_MINUTES: 30; readonly OWNER_THREAD_IDLE_TIMEOUT_MINUTES: 30; }; export declare const VERSION_CHECK_CONSTANTS: { readonly NPM_REGISTRY_URL: "https://registry.npmjs.org/crewly/latest"; readonly CHECK_CACHE_FILE: ".update-check"; readonly CACHE_TTL_MS: number; readonly REQUEST_TIMEOUT_MS: 5000; }; export declare const AUTO_UPDATE_CONSTANTS: { readonly CHECK_INTERVAL_MS: number; readonly FIRST_CHECK_DELAY_MS: number; readonly REGISTRY_MAX_AGE_MS: number; readonly QUIET_CONFIRM_MS: number; readonly BUSY_RETRY_MS: number; readonly MAX_BUSY_DEFER_MS: number; readonly FAILURE_BACKOFF_MS: number; readonly FAILURE_NOTIFY_THRESHOLD: 2; readonly INSTALL_TIMEOUT_MS: number; readonly FAILURE_LOG_TAIL_LINES: 40; readonly FAILURE_REASON_MAX_CHARS: 240; readonly NOTIFY_WAIT_MS: number; readonly NOTIFY_POLL_MS: number; readonly MARKER_MAX_AGE_MS: number; readonly ENV_VAR: "CREWLY_AUTO_UPDATE"; readonly SUPERVISOR_ENV_VAR: "CREWLY_RESTART_SUPERVISOR"; readonly SUPERVISOR_CLI_START: "cli-start"; readonly LOG_FILE: "auto-update.log"; readonly STATE_FILE: "auto-update-state.json"; readonly MARKER_FILE: "auto-update-pending.json"; readonly PACKAGE_NAME: "crewly"; }; export declare const CLOUD_DISCONNECT_NOTICE_CONSTANTS: { readonly ENV_SWITCH: "CREWLY_CLOUD_DISCONNECT_NOTICE"; readonly DISCONNECT_THRESHOLD_MS: number; readonly CHECK_INTERVAL_MS: number; readonly REPEAT_INTERVAL_MS: number; readonly QUEUE_FAILURE_THRESHOLD_MS: number; readonly RELAY_QUEUE_NOTICE_FILE: "cloud/relay-queue-notice.json"; readonly STATE_FILE: "cloud/disconnect-notice.json"; readonly CLI_LOGIN_ARGS: readonly ["cloud", "login", "--no-browser"]; readonly CLI_ENTRY: "dist/cli/cli/src/index.js"; readonly LINK_WAIT_MS: number; readonly LOGIN_TIMEOUT_MS: number; readonly LOGIN_RETRY_MS: number; readonly REPLY_POLL_INTERVAL_MS: number; readonly REPLY_MAX_WAIT_MS: number; readonly PTY_COLS: 500; readonly PTY_ROWS: 50; readonly RAW_BUFFER_MAX_CHARS: number; readonly SLACK_API_BASE: "https://slack.com/api"; readonly SLACK_REQUEST_TIMEOUT_MS: number; }; export declare const AGENT_HEARTBEAT_MONITOR_CONSTANTS: { readonly CHECK_INTERVAL_MS: 30000; readonly HEARTBEAT_REQUEST_THRESHOLD_MS: 300000; readonly STARTUP_GRACE_PERIOD_MS: 60000; readonly MAX_RESTARTS_PER_WINDOW: 3; readonly COOLDOWN_WINDOW_MS: 3600000; }; export declare const ORCHESTRATOR_HEARTBEAT_CONSTANTS: { readonly CHECK_INTERVAL_MS: 30000; readonly HEARTBEAT_REQUEST_THRESHOLD_MS: 300000; readonly HEARTBEAT_REQUEST_THRESHOLD_PTY_ACTIVE_MS: 900000; readonly PTY_ACTIVE_WINDOW_MS: 120000; readonly RESTART_THRESHOLD_MS: 60000; readonly HEARTBEAT_REQUEST_MESSAGE: "Please run your heartbeat skill now: bash config/skills/orchestrator/heartbeat/execute.sh"; readonly STARTUP_GRACE_PERIOD_MS: 120000; readonly IN_PROGRESS_TIMEOUT_MS: 1800000; }; export declare const WEB_CONSTANTS: { readonly PORTS: { readonly BACKEND: 8787; readonly FRONTEND: 8788; }; readonly ENDPOINTS: { readonly HEALTH: "/health"; readonly API_BASE: "/api"; readonly TEAMS: "/api/teams"; readonly PROJECTS: "/api/projects"; readonly ORCHESTRATOR: "/api/orchestrator"; readonly TERMINAL: "/api/terminal"; readonly SESSIONS: "/api/sessions"; readonly TERMINAL_SESSIONS: "/api/terminal/sessions"; readonly TASKS: "/api/tasks"; readonly FACTORY_SSE: "/api/factory/sse"; }; readonly SSE: { readonly POLL_INTERVAL: 3000; readonly HEARTBEAT_INTERVAL: 30000; readonly MAX_RECONNECT_ATTEMPTS: 5; readonly BASE_RECONNECT_DELAY: 1000; readonly MAX_RECONNECT_DELAY: 30000; readonly FALLBACK_POLL_INTERVAL: 5000; }; }; export declare const ADDON_CONSTANTS: { readonly PATHS: { readonly ADDONS_DIR: "addons"; readonly DOWNLOADS_DIR: "downloads"; }; readonly MANIFEST_FILE: "manifest.json"; readonly DEFAULT_ENTRYPOINT: "dist/index.js"; readonly CLOUD_API: { readonly BASE_URL: "https://api.crewlyai.com/v1"; readonly DOWNLOAD_ENDPOINT: "/addons"; }; readonly PRO_ADDON: { readonly NAME: "crewly-pro"; readonly DEFAULT_TARBALL: "crewly-pro.tar.gz"; }; readonly TIMEOUTS: { readonly LOAD_TIMEOUT_MS: 10000; }; }; export declare const AUDITOR_CONSTANTS: { readonly ENABLED_BY_DEFAULT: false; readonly ENV_VAR: "CREWLY_ENABLE_AUDITOR"; }; export declare const API_SECURITY_CONSTANTS: { readonly ENV: { readonly BIND_HOST: "CREWLY_BIND_HOST"; readonly API_TOKEN: "CREWLY_API_TOKEN"; readonly TRUST_PROXY: "CREWLY_TRUST_PROXY"; readonly HEADLESS: "CREWLY_HEADLESS"; readonly PUBLIC_HEALTH: "CREWLY_PUBLIC_HEALTH"; }; readonly AGENT_ENV_DENYLIST: readonly string[]; readonly DEFAULT_BIND_HOST: "0.0.0.0"; readonly LOOPBACK_BIND_HOST: "127.0.0.1"; readonly TOKEN_FILE_NAME: "api-token"; readonly TOKEN_BYTES: 32; readonly TOKEN_FILE_MODE: 384; readonly FINGERPRINT_HEX_LENGTH: 8; readonly TOKEN_HEADER: "x-crewly-token"; readonly TOKEN_COOKIE: "crewly_token"; readonly TOKEN_QUERY_PARAM: "token"; readonly TOKEN_STORAGE_KEY: "crewly_api_token"; readonly AUTH_SCHEME: "Crewly-Token"; readonly ERRORS: { readonly UNAUTHORIZED: "unauthorized"; readonly OWNER_APPROVAL_REQUIRED: "owner_approval_required"; }; readonly OWNER_ACTOR: "owner"; readonly CALLER_HEADER: "x-crewly-caller"; readonly DASHBOARD_CALLER: "dashboard"; readonly AGENT_SESSION_HEADER: "x-agent-session"; readonly AGENT_SESSION_HEADER_LEGACY: "x-crewly-agent-session"; readonly UNAUTHORIZED_HINT: "Non-loopback callers must send the API token: `Authorization: Bearer `, `X-Crewly-Token: ` or a `crewly_token` cookie. Run `crewly token` on the server to print it."; readonly LOOPBACK_ADDRESSES: readonly ["127.0.0.1", "::1", "::ffff:127.0.0.1"]; }; /** Owner authentication (#999, specs/2026-10-03-owner-auth.md) — see config/constants.ts. */ export declare const OWNER_AUTH_CONSTANTS: { readonly AGENT_BADGE_ENV: "CREWLY_AGENT_BADGE"; readonly AGENT_BADGE_HEADER: "x-agent-badge"; readonly AGENT_BADGE_PREFIX: "cab1"; readonly OWNER_SESSION_COOKIE_PREFIX: "crewly_owner_"; readonly OWNER_SESSION_PREFIX: "cos1"; readonly OWNER_SESSION_MAX_AGE_S: number; readonly CSRF_HEADER: "x-crewly-csrf"; readonly INTERNAL_HEADER: "x-crewly-internal"; readonly SCHEDULER_NAME_HEADER: "x-crewly-scheduler"; readonly SCHEDULER_CREDENTIAL_ENV: "CREWLY_SCHEDULER_CREDENTIAL"; readonly SCHEDULER_NAME_ENV: "CREWLY_SCHEDULER_NAME"; readonly SCHEDULER_SENDER_PREFIX: "scheduler:"; readonly SESSION_ROUTE: "/auth/session"; readonly MUTATING_METHODS: readonly string[]; readonly RELOAD_MESSAGE: "Reload this page — Crewly was updated."; readonly BUILD_HEADER: "x-crewly-dashboard-build"; readonly BUILD_EVENT: "dashboard_build"; readonly ERRORS: { readonly OWNER_AUTH_REQUIRED: "owner_auth_required"; readonly OWNER_ONLY: "owner_only"; readonly AGENT_BADGE_REQUIRED: "agent_badge_required"; readonly SCHEDULER_MESSAGE_ONLY: "scheduler_message_only"; }; readonly PEER_LOOKUP_TIMEOUT_MS: 3000; readonly PEER_MAX_ANCESTRY_DEPTH: 64; readonly LEGACY_WARN_THROTTLE_MS: number; }; export declare const ORCHESTRATOR_SESSION_NAME: "crewly-orc"; export declare const ORCHESTRATOR_ROLE = "orchestrator"; export declare const ORCHESTRATOR_WINDOW_NAME = "Crewly Orchestrator"; export declare const AGENT_INITIALIZATION_TIMEOUT = 90000; export declare const CLAUDE_INITIALIZATION_TIMEOUT = 45000; export declare const CREWLY_CONSTANTS: { readonly INIT_SCRIPTS: { readonly CLAUDE: "initialize_claude.sh"; }; readonly SESSIONS: { readonly ORCHESTRATOR_NAME: "crewly-orc"; readonly DEFAULT_TIMEOUT: 120000; readonly REGISTRATION_CHECK_INTERVAL: 5000; readonly CLAUDE_DETECTION_CACHE_TIMEOUT: 30000; readonly DEFAULT_SHELL: "/bin/bash"; }; readonly PATHS: { readonly CREWLY_HOME: ".crewly"; readonly TEAMS_FILE: "teams.json"; readonly PROJECTS_FILE: "projects.json"; readonly RUNTIME_FILE: "runtime.json"; readonly SCHEDULED_MESSAGES_FILE: "scheduled-messages.json"; readonly MESSAGE_DELIVERY_LOGS_FILE: "message-delivery-logs.json"; readonly CONFIG_DIR: "config"; readonly PROMPTS_DIR: "prompts"; readonly TASKS_DIR: "tasks"; readonly SPECS_DIR: "specs"; readonly MEMORY_DIR: "memory"; readonly DOCS_DIR: "docs"; readonly DOCS_INDEX_FILE: "docs-index.json"; readonly LOGS_DIR: "logs"; readonly SESSION_LOGS_DIR: "sessions"; readonly BUG_REPORTS_DIR: "bug-reports"; readonly SKILLS_DIR: "skills"; readonly SKILLS_CATALOG_FILE: "AGENT_SKILLS_CATALOG.md"; }; readonly AGENT_STATUSES: { readonly INACTIVE: "inactive"; readonly STARTING: "starting"; readonly STARTED: "started"; readonly ACTIVE: "active"; readonly SUSPENDED: "suspended"; readonly ACTIVATING: "activating"; }; readonly WORKING_STATUSES: { readonly IDLE: "idle"; readonly IN_PROGRESS: "in_progress"; }; readonly ROLES: { readonly ORCHESTRATOR: "orchestrator"; readonly PROJECT_MANAGER: "pm"; readonly TECH_LEAD: "tpm"; readonly DEVELOPER: "developer"; readonly QA: "qa"; readonly DEVOPS: "devops"; }; readonly AGENT_IDS: { readonly ORCHESTRATOR_ID: "orchestrator"; }; readonly ROLE_DISPLAY_NAMES: { readonly orchestrator: "Orchestrator"; readonly pm: "Project Manager"; readonly tpm: "Technical Project Manager"; readonly developer: "Developer"; readonly qa: "Quality Assurance"; readonly devops: "DevOps Engineer"; }; readonly ORCHESTRATOR: { readonly DISPLAY_NAME: "Orchestrator"; readonly WINDOW_NAME: "Crewly Orchestrator"; readonly SESSION_PREFIX: "crewly_"; }; }; /** * Checking a skill request's claimed agent identity against the process it * came from. Skills send `X-Agent-Session` (from CREWLY_SESSION_NAME) and * `X-Agent-Pid` (the skill shell's pid); on loopback the backend walks that * pid's parents to the agent PTY it belongs to. */ export declare const AGENT_ORIGIN_CONSTANTS: { readonly SESSION_HEADER: "x-agent-session"; readonly PID_HEADER: "x-agent-pid"; /** Set on a corrected request: the session the shell claimed */ readonly CLAIMED_SESSION_HEADER: "x-agent-session-claimed"; /** Longest parent chain walked from the skill shell */ readonly MAX_ANCESTRY_DEPTH: 64; /** Give up on the process lookup after this long and keep the claimed identity */ readonly LOOKUP_TIMEOUT_MS: 1500; /** One skill run makes several calls from the same shell pid */ readonly RESULT_CACHE_TTL_MS: 30000; readonly RESULT_CACHE_MAX_ENTRIES: 500; /** The same mismatch is logged at most this often */ readonly WARN_THROTTLE_MS: number; /** Command line of Codex's shared background app server (`codex app-server --listen unix:// --managed-daemon`) */ readonly SHARED_DAEMON_ARGS_RE: RegExp; readonly PS_MAX_BUFFER_BYTES: number; }; export declare const ENV_CONSTANTS: { /** PTY session name used for agent identity and heartbeat tracking */ readonly CREWLY_SESSION_NAME: "CREWLY_SESSION_NAME"; readonly CREWLY_ROLE: "CREWLY_ROLE"; /** Base URL for the Crewly backend API (used by orchestrator bash skills) */ readonly CREWLY_API_URL: "CREWLY_API_URL"; /** Gemini API key for embedding-based knowledge search */ readonly GEMINI_API_KEY: "GEMINI_API_KEY"; /** Project path for auto-injecting into memory skill calls (#187) */ readonly CREWLY_PROJECT_PATH: "CREWLY_PROJECT_PATH"; /** Enable Claude Code telemetry for token tracking */ readonly CLAUDE_CODE_ENABLE_TELEMETRY: "CLAUDE_CODE_ENABLE_TELEMETRY"; /** #222: Absolute path to the Crewly installation directory (where config/skills/ lives) */ readonly CREWLY_INSTALL_DIR: "CREWLY_INSTALL_DIR"; /** * Explicit working directory for the orchestrator's PTY. When unset the * orchestrator starts in the first assigned project path, then CREWLY_HOME — * never in the package install dir or the server's process.cwd(). */ readonly CREWLY_ORC_CWD: "CREWLY_ORC_CWD"; }; export declare const AGENT_TIMEOUTS: { readonly ORCHESTRATOR_INITIALIZATION: 120000; readonly REGULAR_AGENT_INITIALIZATION: 75000; /** #227: Extended timeout for Claude Code — PI protection evaluation takes 2-3 min */ readonly CLAUDE_CODE_INITIALIZATION: 300000; /** #252: Watchdog timeout — if agent is still in 'starting' state after this period, mark as error */ readonly STARTING_WATCHDOG: 300000; }; export declare const BROWSER_BRIDGE_CONSTANTS: { /** WebSocket server path for Chrome Extension connections */ readonly WS_PATH: "/ws/browser"; /** Default timeout for commands sent to Chrome Extension (ms) */ readonly COMMAND_TIMEOUT_MS: 30000; /** * Timeout for screenshot / full-page-screenshot (ms). Capturing a heavy * page (Slack web, long feeds) and relaying a multi-MB base64 image takes * well beyond the 30 s default; the shorter limit made them look like a * missing browser (crewly#1080). */ readonly SCREENSHOT_TIMEOUT_MS: 90000; /** * Per-tab dispatch (1 agent : 1 tab) — see * `.crewly/specs/crewly-in-chrome-per-tab-fix-2026-04-25.md`. */ /** Hard cap on concurrent agent→tab bindings. POST /api/browser/bind returns 503 above this. */ readonly TAB_BIND_HARD_CAP: 50; /** Soft warning threshold — log + alert when crossed, but bind still succeeds. */ readonly TAB_BIND_SOFT_WARN: 25; /** Default TTL for an idle binding in minutes (configurable via CREWLY_TAB_BIND_TTL_MINUTES). */ readonly TAB_BIND_TTL_MINUTES: 30; /** Cadence of the orphan/TTL sweep timer in milliseconds. */ readonly TAB_BIND_SWEEP_MS: number; /** * Minutes an agent's tab may sit untouched after its turn ended before it * is closed (env `CREWLY_TAB_IDLE_CLOSE_MINUTES`; 0 disables). The agent * working again, or touching the tab, resets the clock. */ readonly TAB_IDLE_CLOSE_MINUTES: 10; /** Default tabId on a 503 retry hint (milliseconds the skill should back off). */ readonly TAB_BIND_RETRY_AFTER_MS: 30000; /** * Logged reason when tab-inventory reconcile closes a tab: this backend * created it and no agent has it bound any more. Reconcile never closes a * Crewly tab this backend did not create (it may be another client's). */ readonly RECONCILE_CLOSE_REASON_OWN_UNBOUND: "own_unbound_tab"; /** Longest agent goal forwarded to the extension banner and the live view. */ readonly MAX_AGENT_GOAL_LENGTH: 200; }; /** * Crewly Cloud Relay proxy constants for browser instance lifecycle hygiene. * Used by `BrowserProxyService` to keep its in-memory instances Map free of * stale entries when the relay drops a `disconnected` event or when the user * reinstalls the extension (fresh `instanceId` UUID — old one would otherwise * linger forever). * * See `.crewly/specs/browser-ext-stale-status-fix-2026-04-30.md`. */ export declare const BROWSER_PROXY_CONSTANTS: { /** Cadence of the wall-clock TTL sweep (ms). Every interval the service * walks `this.instances` and purges entries whose `lastSeenAt` exceeds * `STALE_PURGE_THRESHOLD_MS`. */ readonly SWEEP_INTERVAL_MS: 60000; /** Inactivity threshold (ms) after which an instance is considered stale * and purged by the sweep. 5 min is generous enough to tolerate transient * relay/network blips without prematurely evicting a live ext connection * (heartbeat fires every 25s, so 5 min = ~12 missed heartbeats). */ readonly STALE_PURGE_THRESHOLD_MS: number; /** Re-issue `list_browsers` to the relay every Nth heartbeat tick. * Defensive sync against missing `browser_event:updated` pushes from * the cloud relay. With heartbeat at 25s and N=8, refresh fires every * ~200s — comfortably under STALE_PURGE_THRESHOLD_MS (300s) so a live * ext that is mid-refresh never crosses the purge boundary. * Boundary math: register@T0 → first refresh@T0+200s → next sweep * evaluates lastSeenAt at most 200s old → far below 300s threshold. */ readonly LIST_REFRESH_EVERY_N_HEARTBEATS: 8; /** * Deadline (ms) for receiving a `heartbeat_ack` from the relay after a * `heartbeat` is sent. If no ack arrives within this window the relay * socket is treated as dead/half-open and the proxy reconnects proactively * rather than riding the socket until the relay's 180s `4002` eviction. * * Set to 2x the 25s heartbeat interval (50s): comfortably above one RTT on * slow networks (avoids false reconnects) yet well under the relay's 180s * timeout (so the backend never churns drop→re-register→count:0 every * ~3min). Liveness invariant — see the long-term browser-relay fix design. */ readonly HEARTBEAT_ACK_DEADLINE_MS: 50000; }; export declare const RUNTIME_TYPES: { readonly CLAUDE_CODE: "claude-code"; readonly GEMINI_CLI: "gemini-cli"; readonly CODEX_CLI: "codex-cli"; /** OpenCode (opencode.ai) — open-source TUI coding agent, issue #306 */ readonly OPENCODE_CLI: "opencode-cli"; /** * Google Antigravity CLI (`agy`), the successor to Gemini CLI for * individual users. Driven only with a Gemini API key — see * specs/antigravity-runtime.md for why account (OAuth) login is refused. */ readonly ANTIGRAVITY_CLI: "antigravity-cli"; readonly CREWLY_AGENT: "crewly-agent"; }; /** * The canonical managed binary name for the Crewly Agent runtime. * * After PR #599 (Plan A) the agent reasoning loop lives in the standalone * `crewly-agent` npm package; OSS spawns it as a subprocess. This is the * default value of `settings.general.runtimeCommands['crewly-agent']` and the * command `resolveRuntimeCommand()` returns (no shell) when no genuine custom * shell command is configured. */ export declare const CREWLY_AGENT_MANAGED_COMMAND: "crewly-agent"; /** * Legacy / sentinel values for `runtimeCommands['crewly-agent']` that must NOT * be treated as real shell commands. * * `'crewly-agent-in-process'` was the factory default before PR #599 switched * the runtime to an external binary. It is not a real executable — when shelled * out via `sh -lc` it fails with exit-127 (issue #693). Any value listed here is * recognized as "use the managed default binary" and is rewritten to * `CREWLY_AGENT_MANAGED_COMMAND` on settings load (migration) and at command * resolution time (defensive guard). */ export declare const LEGACY_CREWLY_AGENT_SENTINELS: readonly ["crewly-agent-in-process"]; /** Error patterns indicating non-recoverable failures (e.g. missing CLI binary) that should not be retried. */ export declare const NON_RECOVERABLE_ERROR_PATTERNS: readonly ["command not found", "not installed", "No such file"]; export declare const PTY_CONSTANTS: { readonly MAX_DATA_LISTENERS: 100; readonly MAX_EXIT_LISTENERS: 50; readonly DEFAULT_MAX_HISTORY_SIZE: number; readonly DEFAULT_SCROLLBACK: 5000; readonly DEFAULT_COLS: 80; readonly DEFAULT_ROWS: 24; readonly MAX_RESIZE_COLS: 1000; readonly MAX_RESIZE_ROWS: 1000; /** Delay before escalating from SIGTERM to SIGKILL in forceKill (ms) */ readonly FORCE_KILL_ESCALATION_DELAY: 500; /** Delay before escalating from SIGTERM to SIGKILL in forceDestroyAll (ms) */ readonly FORCE_DESTROY_ESCALATION_DELAY: 1000; /** Minimum non-whitespace characters in stripped PTY output to count as meaningful activity. * Set to 8 to filter out Gemini CLI TUI noise (cursor movements, spinner chars, single-char * redraws) that have 1-5 residual chars after ANSI stripping. Real output (tool results, * response text, bash output) is always longer. */ readonly MIN_MEANINGFUL_OUTPUT_BYTES: 8; /** Minimum time (ms) an agent must remain in_progress before emitting busy/idle events */ readonly MIN_BUSY_DURATION_MS: 10000; /** * Cadence of the orphan-PTY reaper sweep (ms). * * The reaper walks the live sessionName→PtySession registry and tears down * any PTY whose owning session is no longer active (killed, exited, or whose * child shell has died) but which was never routed through centralized * teardown — a defensive backstop against `/dev/ptmx` + `/dev/ttysNNN` FD * accumulation (the 6-day backend that leaked 319 orphaned master FDs). * 5 minutes is frequent enough to bound leakage well under the open-file * ceiling yet cheap (an O(n) walk over a handful of sessions). */ readonly ORPHAN_REAPER_INTERVAL_MS: number; }; /** * Control-plane guard for Claude Code agent sessions (Request 72c9427a, * specs/2026-09-24-control-plane-isolation.md Part 3). * * The backend writes a per-session settings file (permissions.deny + a * PreToolUse Bash hook) and passes it with `--settings`, so an agent cannot * edit the files that stop, start and configure agents. All paths are * relative to the root named by the group: CREWLY_HOME (`~/.crewly`), the * install root (the Crewly package / checkout), or the agent's project path. */ export declare const CONTROL_PLANE_GUARD_CONSTANTS: { /** Backend env var; `0` turns the guard off for every session launched by this backend. */ readonly KILL_SWITCH_ENV: "CREWLY_CONTROL_PLANE_GUARD"; /** Value of KILL_SWITCH_ENV that disables the guard. Any other value (or unset) keeps it on. */ readonly KILL_SWITCH_OFF_VALUE: "0"; /** Directory under CREWLY_HOME that holds the generated per-session files. */ readonly RUNTIME_DIR: "runtime/control-plane"; /** Suffix of the generated Claude Code settings file (``). */ readonly SETTINGS_FILE_SUFFIX: ".settings.json"; /** Suffix of the protected-paths list the Bash hook reads (``). */ readonly PATHS_FILE_SUFFIX: ".paths"; /** The PreToolUse Bash hook, relative to the install root. */ readonly HOOK_SCRIPT: "config/hooks/control-plane-guard/pretooluse-bash.sh"; /** CLI flag the settings file is passed with. */ readonly SETTINGS_FLAG: "--settings"; /** Claude Code tool the PreToolUse hook is attached to. */ readonly HOOK_TOOL_MATCHER: "Bash"; /** * Write-protected directories under CREWLY_HOME (whole subtree). * * `teams` is deliberately NOT here (WorkItem 70e54fbc / #798 review): * specs/2026-09-24-control-plane-isolation.md Part 2 protects only * `teams/*\/config.json`, not the whole subtree. A team directory also * holds `norms/`, `wiki/`, `prompts/`, `sops/` and `cron-tasks.json` — * files agents write routinely (`remember`, `record-learning`, * norm/SOP authoring, follow-up scheduling). Blanket-protecting `teams` * would block all of that. See TEAM_CONFIG_FILE_NAME below for how the * narrower rule is built (one entry per existing team, resolved at * guard-prep time since the hook needs literal paths, not globs). */ readonly CREWLY_HOME_DIRS: readonly ["triggers", "runtime/control-plane", "runtime/credential-guard"]; /** * Directory under CREWLY_HOME holding one subdirectory per team, each * expected to contain a `config.json` (see TEAM_CONFIG_FILE_NAME). */ readonly TEAMS_DIR_NAME: "teams"; /** File name, within each team's directory, that the guard protects. */ readonly TEAM_CONFIG_FILE_NAME: "config.json"; /** Write-protected files under CREWLY_HOME. */ readonly CREWLY_HOME_FILES: readonly ["recurring-checks.json", "one-time-checks.json", "scheduled-messages.json", "settings.json", "runtime-pids.json", "session-state.json", "api-token"]; /** Files under CREWLY_HOME that agents must not read with the built-in tools either. */ readonly CREWLY_HOME_READ_DENIED_FILES: readonly ["api-token"]; /** Write-protected directories under the install root (whole subtree). */ readonly INSTALL_DIRS: readonly ["config/skills/orchestrator/stop-agent", "config/skills/orchestrator/start-agent", "config/skills/orchestrator/terminate-agent", "config/skills/orchestrator/stop-team", "config/skills/orchestrator/start-team", "config/skills/orchestrator/restart-crewly", "config/hooks/control-plane-guard", "config/hooks/agent-status", "config/hooks/subagent-guard", "config/hooks/credential-guard", "config/hooks/live-checkout-guard", "config/hooks/destructive-git-guard", "dist"]; /** Write-protected files under the install root. */ readonly INSTALL_FILES: readonly ["config/skills/_common/lib.sh"]; /** Write-protected directories under the agent's project path (whole subtree). */ readonly PROJECT_DIRS: readonly [".claude/agents", ".crewly/triggers"]; }; /** * Live-checkout guard: agents may not change the checkout the running Crewly * is built from (git state changes or file edits). Incident 2026-10-08/09: an * agent switched branches in it and the next restart refused to start. */ export declare const LIVE_CHECKOUT_GUARD_CONSTANTS: { /** Backend env var; `0` turns the guard off for sessions launched by this backend. */ readonly KILL_SWITCH_ENV: "CREWLY_LIVE_CHECKOUT_GUARD"; /** Value of KILL_SWITCH_ENV that disables the guard. */ readonly KILL_SWITCH_OFF_VALUE: "0"; /** The hook script (node), relative to the install root. */ readonly HOOK_SCRIPT: "config/hooks/live-checkout-guard/guard.mjs"; /** Claude Code tools the hook is attached to. */ readonly CLAUDE_MATCHER: "Bash|Edit|Write|MultiEdit|NotebookEdit"; /** Codex tools the hook is attached to (shell, plus its patch tool). */ readonly CODEX_MATCHER: "Bash|apply_patch|Edit|Write"; }; /** * Destructive-git guard: blocks force-push, push to main/master, reset --hard, * clean -f, branch -D, checkout . and restore . in an agent's Bash tool, so an * agent cannot destroy its own unmerged work. */ export declare const DESTRUCTIVE_GIT_GUARD_CONSTANTS: { /** Backend env var; `0` turns the guard off for sessions launched by this backend (the owner/TL override). */ readonly KILL_SWITCH_ENV: "CREWLY_DESTRUCTIVE_GIT_GUARD"; /** Value of KILL_SWITCH_ENV that disables the guard. */ readonly KILL_SWITCH_OFF_VALUE: "0"; /** The hook script (node), relative to the install root. */ readonly HOOK_SCRIPT: "config/hooks/destructive-git-guard/guard.mjs"; /** Claude Code tool the hook is attached to. */ readonly CLAUDE_MATCHER: "Bash"; }; /** * Credential guard (specs/2026-10-04-agent-credential-isolation.md, layer 2): * a pre-tool hook, on every runtime that has one, that refuses an agent's * tool call touching Crewly's own credentials. */ export declare const CREDENTIAL_GUARD_CONSTANTS: { /** Backend env var; `0` turns the guard off for sessions launched by this backend. */ readonly KILL_SWITCH_ENV: "CREWLY_CREDENTIAL_GUARD"; /** Value of KILL_SWITCH_ENV that disables the guard. */ readonly KILL_SWITCH_OFF_VALUE: "0"; /** Directory under CREWLY_HOME with the paths file and the per-runtime wrappers. */ readonly RUNTIME_DIR: "runtime/credential-guard"; /** The paths file the hook reads. */ readonly PATHS_FILE: "paths"; /** The hook script, relative to the install root. */ readonly HOOK_SCRIPT: "config/hooks/credential-guard/guard.sh"; /** Agent env var naming the paths file (the hook and crewly-agent read it). */ readonly PATHS_ENV: "CREWLY_CREDENTIAL_GUARD_PATHS"; /** Agent env var naming the hook script (crewly-agent runs it before its own tools). */ readonly SCRIPT_ENV: "CREWLY_CREDENTIAL_GUARD_SCRIPT"; /** Hook event the script reports to POST /api/agent-hooks. */ readonly BLOCKED_EVENT: "CredentialAccessBlocked"; /** Claude Code tools the hook is attached to. */ readonly CLAUDE_MATCHER: "Bash|Read|Grep|Glob|NotebookRead"; /** Codex tool the hook is attached to (Codex reads files through its shell). */ readonly CODEX_MATCHER: "Bash"; /** Codex flag that lets a `-c` (session) hook run without a trust prompt. */ readonly CODEX_HOOK_TRUST_FLAG: "--dangerously-bypass-hook-trust"; /** Gemini CLI tools the hook is attached to. */ readonly GEMINI_MATCHER: "run_shell_command|read_file|read_many_files|glob|search_file_content|list_directory"; /** Gemini CLI env var for a system settings file (highest-priority layer). */ readonly GEMINI_SYSTEM_SETTINGS_ENV: "GEMINI_CLI_SYSTEM_SETTINGS_PATH"; /** Gemini CLI default system settings files, merged into ours so none is shadowed. */ readonly GEMINI_DEFAULT_SYSTEM_SETTINGS: Record; /** File name of Crewly's Gemini system settings under RUNTIME_DIR. */ readonly GEMINI_SETTINGS_FILE: "gemini-system-settings.json"; /** Hook timeout for Gemini CLI (ms). */ readonly GEMINI_HOOK_TIMEOUT_MS: 5000; /** agy's global hooks file, under $HOME (agy has no per-process hooks path). */ readonly ANTIGRAVITY_HOOKS_FILE_SEGMENTS: readonly string[]; /** Name of Crewly's entry in agy's hooks file (hooks are keyed by name). */ readonly ANTIGRAVITY_HOOK_NAME: "crewly-credential-guard"; /** Hook timeout for agy (seconds). */ readonly ANTIGRAVITY_HOOK_TIMEOUT_S: 10; /** Owner is told at most once per agent per this window. */ readonly NOTIFY_WINDOW_MS: number; /** Max sessions remembered for the once-a-day notice. */ readonly MAX_TRACKED_SESSIONS: 500; /** Accepted rule id. */ readonly RULE_PATTERN: RegExp; /** Accepted runtime label. */ readonly RUNTIME_PATTERN: RegExp; }; /** * Agent-status hook (#815, specs/2026-09-26-agent-waiting-on-human.md): Claude * Code hook events that tell the backend when an agent waits on the user. * Registered in the control-plane guard's per-session settings file, never as * a second settings file, and never on PreToolUse (that event is the guard's). */ export declare const AGENT_STATUS_HOOK_CONSTANTS: { /** Hook script, relative to the install root. */ readonly HOOK_SCRIPT: "config/hooks/agent-status/report.sh"; /** * Hook events the script is registered for. PreToolUse, SubagentStart, * SubagentStop and SessionStart feed the runtime turn state (specs/2026-10-02-restart-busy-and-resume.md). */ readonly EVENTS: readonly ["Notification", "PermissionRequest", "Stop", "UserPromptSubmit", "PreToolUse", "PostToolUse", "SubagentStart", "SubagentStop", "SessionStart"]; /** Events that carry a tool matcher; they match every tool. */ readonly TOOL_EVENTS: readonly ["PermissionRequest", "PreToolUse", "PostToolUse"]; /** Matcher that selects every tool. */ readonly ALL_TOOLS_MATCHER: "*"; /** Notification types that mean "waiting on the user". */ readonly WAITING_NOTIFICATION_TYPES: readonly ["permission_prompt", "elicitation_dialog"]; /** Every notification type the endpoint accepts (others are rejected). */ readonly KNOWN_NOTIFICATION_TYPES: readonly ["permission_prompt", "elicitation_dialog", "idle_prompt", "auth_success"]; /** Max length of an accepted event or notification-type identifier. */ readonly MAX_IDENTIFIER_LENGTH: 64; /** Sessions whose latest hook signal is kept in memory (oldest dropped past this). */ readonly MAX_TRACKED_SESSIONS: 500; /** Accepted X-Agent-Session header value. */ readonly SESSION_NAME_PATTERN: RegExp; }; /** * Subagent guard (#852, specs/2026-10-03-subagent-guard.md): a Claude Code * SubagentStart / SubagentStop hook. It injects Crewly's subagent rules when a * subagent starts, and sends back, once, a subagent that stops without having * made any tool call. Registered in the control-plane guard's per-session * settings file, like the agent-status hook. */ export declare const SUBAGENT_GUARD_CONSTANTS: { /** Hook script, relative to the install root. */ readonly HOOK_SCRIPT: "config/hooks/subagent-guard/subagent.sh"; /** Hook events the script is registered for. */ readonly EVENTS: readonly ["SubagentStart", "SubagentStop"]; /** Environment variable that turns the subagent guard off when set to KILL_SWITCH_OFF_VALUE. */ readonly KILL_SWITCH_ENV: "CREWLY_SUBAGENT_GUARD"; /** Value of KILL_SWITCH_ENV that disables the subagent guard. */ readonly KILL_SWITCH_OFF_VALUE: "0"; }; /** * Session recreation (Step 2 full recreation in AgentRegistrationService). * * D3 (2026-09-21): the runtime init sequence leads with Ctrl-C * (session-command-helper sendCtrlC / clearCurrentCommandLine). Written a few * ms after spawn, before zsh has installed its interactive SIGINT handling, * that Ctrl-C ends the shell (Max's Step-2 shell died 11 ms after spawn). The * primary path is immune only by accident (it types five `export`s first, * ~0.5 s). Step 2 therefore waits — bounded — for the shell's first output * (its prompt) before the first write. */ export declare const SESSION_RECREATION_CONSTANTS: { /** Upper bound on waiting for the fresh shell to print its prompt (ms). */ readonly SHELL_READY_TIMEOUT_MS: 3000; /** First non-whitespace byte the shell prints — its prompt, whatever the shell/theme. */ readonly SHELL_READY_PATTERN: RegExp; /** Terminal lines to inspect for a prompt that landed before we subscribed. */ readonly SHELL_READY_CAPTURE_LINES: 5; }; export declare const SESSION_COMMAND_DELAYS: { /** Delay after sending a message (allows terminal to process bracketed paste) */ readonly MESSAGE_DELAY: 1000; /** Delay after sending a key (allows key to be processed) */ readonly KEY_DELAY: 200; /** Delay after clearing command line (allows terminal to reset) */ readonly CLEAR_COMMAND_DELAY: 100; /** Delay after setting environment variable */ readonly ENV_VAR_DELAY: 100; /** Delay for Claude Code to recover from state changes */ readonly CLAUDE_RECOVERY_DELAY: 300; /** Delay between message delivery retry attempts */ readonly MESSAGE_RETRY_DELAY: 1000; /** Additional delay for Claude Code to start processing after message sent */ readonly MESSAGE_PROCESSING_DELAY: 500; /** Max idle time (ms) to consider an agent busy. If PTY output occurred within this window, skip delivery. */ readonly AGENT_BUSY_IDLE_THRESHOLD_MS: 5000; /** * How long delivery waits on ActivityMonitor before assuming "not busy". * * `getWorkingStatusForSession` has hung before (2026-05-14); a message * must not wedge behind it, so the probe is bounded and a timeout keeps * the pre-existing behaviour. */ readonly WORKING_STATUS_PROBE_TIMEOUT_MS: 2000; /** Progressive re-check intervals for Claude Code delivery verification (ms). * Total window: 500ms (processing delay) + 1000 + 2000 + 3000 = 6.5s */ readonly CLAUDE_VERIFICATION_INTERVALS: readonly [1000, 2000, 3000]; }; export declare const TERMINAL_CONTROLLER_CONSTANTS: { readonly DEFAULT_CAPTURE_LINES: 50; readonly MAX_CAPTURE_LINES: 500; readonly MAX_OUTPUT_SIZE: 131072; }; /** Constants for the TerminalGateway orchestrator output buffer management. */ export declare const TERMINAL_GATEWAY_CONSTANTS: { /** Maximum buffer size for orchestrator output (bytes) */ readonly MAX_BUFFER_SIZE: number; /** Trim threshold for partial line buffer (characters) */ readonly BUFFER_TRIM_THRESHOLD: 1000; /** * Backoff schedule (ms) for retrying eager orchestrator chat monitoring * when the PTY session is not yet registered in the SessionBackend. * * Background: startOrchestratorChatMonitoring is invoked from boot, * setup, and restart paths immediately after createAgentSession resolves. * The SessionBackend may not have fully registered the new session by * then — leaving the orchestrator's PTY without an onData listener for * the rest of its lifetime, which silently drops responses to user * messages until the side panel is opened (RCA 2026-04-29 / 2026-04-30). * * Total budget ≈ 8.6s with 5 attempts. Tuned to outlast typical * createAgentSession→backend-register lag (sub-1s on local, up to ~3s * under heavy load) without unbounded retries. */ readonly MONITORING_RETRY_BACKOFFS_MS: readonly number[]; }; /** * Orchestrator reply routing (2026-09-26 incident: the owner asked the orc a * question in a Slack DM, and its answer landed in #pro-think-tank). * See `services/orc/orc-reply-route.service.ts`. */ export declare const ORC_REPLY_ROUTE_CONSTANTS: { /** Characters of the last delivered message kept to tell whether a harness prompt was the last thing an agent got */ readonly LAST_DELIVERED_HEAD_CHARS: 400; /** * How long after the last user message the orchestrator received its * conversation stays "the one its turn came from". Covers a long turn and * the system-triggered turns (WorkItem dispatch, reminders) that follow it. */ readonly ORIGIN_TTL_MS: number; /** * A conversation the orchestrator received a user message from within this * window is one it is actively in; replies there are never re-routed. */ readonly RECENT_INBOUND_MS: number; /** Cap on remembered inbound conversations per session (oldest dropped). */ readonly MAX_TRACKED_CONVERSATIONS: 50; /** Slack channel-id prefix of a direct-message conversation. */ readonly SLACK_DM_PREFIX: "D"; }; export declare const CHAT_ROUTING_CONSTANTS: { /** Message format prefix for chat routing */ readonly MESSAGE_PREFIX: "CHAT"; /** Message format prefix for Google Chat routing (distinguishes from Slack) */ readonly GOOGLE_CHAT_PREFIX: "GCHAT"; /** chat-v2 channel id prefix for recorded Google Chat conversations */ readonly GOOGLE_CHAT_CHANNEL_PREFIX: "gchat"; /** chat-v2 channel id prefix for recorded Telegram conversations */ readonly TELEGRAM_CHANNEL_PREFIX: "telegram"; }; /** * Unified notification marker constants. * Used by the orchestrator to send messages to chat, Slack, or both * via a single `[NOTIFY]...[/NOTIFY]` block with a JSON payload. */ export declare const NOTIFY_CONSTANTS: { /** Opening marker string for detection */ readonly OPEN_TAG: "[NOTIFY]"; /** Closing marker string */ readonly CLOSE_TAG: "[/NOTIFY]"; }; /** * Slack proactive notification constants. * @deprecated Use NOTIFY_CONSTANTS instead. Legacy [SLACK_NOTIFY] markers are still * processed for backward compatibility but new orchestrator output should use [NOTIFY]. */ export declare const SLACK_NOTIFY_CONSTANTS: { /** Opening marker string for detection */ readonly OPEN_TAG: "[SLACK_NOTIFY]"; /** Closing marker string */ readonly CLOSE_TAG: "[/SLACK_NOTIFY]"; }; export declare const EVENT_DELIVERY_CONSTANTS: { /** Timeout for waiting for prompt detection (ms) */ readonly PROMPT_DETECTION_TIMEOUT: 10000; /** Timeout for waiting for delivery confirmation (ms) */ readonly DELIVERY_CONFIRMATION_TIMEOUT: 5000; /** Total timeout for message delivery with retries (ms) */ readonly TOTAL_DELIVERY_TIMEOUT: 30000; /** Default timeout for pattern matching (ms) */ readonly DEFAULT_PATTERN_TIMEOUT: 30000; /** Initial delay for terminal to echo short messages (ms) */ readonly INITIAL_MESSAGE_DELAY: 300; /** Extra time for multi-line paste indicator detection (ms) */ readonly PASTE_CHECK_DELAY: 1200; /** Delay between Enter key retry attempts (ms) */ readonly ENTER_RETRY_DELAY: 800; /** Maximum number of Enter key retry attempts */ readonly MAX_ENTER_RETRIES: 3; /** Delay after Enter retries exhausted before verifying message left input line (ms) */ readonly POST_ENTER_VERIFICATION_DELAY: 500; /** Maximum buffer size for terminal output collection (bytes) */ readonly MAX_BUFFER_SIZE: 10000; /** Minimum buffer length to consider processing detection valid */ readonly MIN_BUFFER_FOR_PROCESSING_DETECTION: 50; /** Timeout for waiting for agent to return to prompt before delivery (ms) */ readonly AGENT_READY_TIMEOUT: 120000; /** Shorter timeout for user messages (Slack/web chat) to reduce delivery delay (ms) */ readonly USER_MESSAGE_TIMEOUT: 30000; /** Whether to force-deliver user messages after timeout instead of re-queuing */ readonly USER_MESSAGE_FORCE_DELIVER: true; /** Shorter timeout for system events to reduce notification delay (ms). * Reduced from 60s to 15s (#124) — agent completion notifications were * taking 3-10 minutes because system events waited too long for prompt. */ readonly SYSTEM_EVENT_TIMEOUT: 15000; /** Whether to force-deliver system events after timeout instead of re-queuing. * System events are fire-and-forget (no response expected), so force-delivery * is lower risk than for user messages. Prevents the 5×120s=10min retry loop. */ readonly SYSTEM_EVENT_FORCE_DELIVER: true; /** Interval for polling agent prompt readiness (ms) */ readonly AGENT_READY_POLL_INTERVAL: 2000; /** Interval for deep-scan polling with larger buffer when fast poll misses prompt (ms) */ readonly DEEP_SCAN_INTERVAL: 5000; /** Number of lines to capture for deep-scan prompt detection */ readonly DEEP_SCAN_LINES: 500; /** Number of PTY lines to scan when verifying force-delivered message acknowledgment */ readonly PENDING_ACK_SCAN_LINES: 300; /** Maximum re-delivery attempts for force-delivered messages that were not acknowledged */ readonly PENDING_ACK_MAX_RETRIES: 3; /** Time in milliseconds before a pending-ack entry is considered stale and discarded */ readonly PENDING_ACK_TTL_MS: 600000; }; /** * Constants for terminal content formatting. * Used by formatMessageContent to safely process terminal output. */ export declare const TERMINAL_FORMATTING_CONSTANTS: { /** Maximum repeat count for cursor movement sequences (prevents memory exhaustion) */ readonly MAX_CURSOR_REPEAT: 1000; }; /** * Terminal detection patterns for Claude Code interaction. * These patterns are used across multiple services to detect terminal state. */ export declare const TERMINAL_PATTERNS: { /** * Combined pattern for detecting spinner/working processing activity. * Simple single-char alternation — O(n), no backtracking risk. * Kept as RegExp for session-command-helper.ts waitForPattern() compatibility. */ readonly PROCESSING: RegExp; /** * Pattern for detecting paste indicator in bracketed paste mode. * Appears as "[Pasted text #N +M lines]" in Claude Code. */ readonly PASTE_INDICATOR: RegExp; /** * Pattern to detect if paste indicator is still visible (stuck state). */ readonly PASTE_STUCK: RegExp; /** * Agent prompt indicators (characters that appear at input prompts). * Includes Claude Code (❯, >, ⏵), bash ($), and Gemini CLI (!). */ readonly PROMPT_CHARS: readonly ["❯", ">", "›", "⏵", "$", "!"]; }; /** * Runtime features that put predicted "user" text into an agent's input * box, switched off for every session Crewly launches (2026-10-03: a * Claude Code prompt suggestion "按这个草稿回吧" was submitted as if the * owner had approved a LinkedIn post). * * - Claude Code: prompt suggestions (a faint predicted next message in an * empty input, accepted by Tab). Off via the env var (read first) and the * `promptSuggestionEnabled` setting in the control-plane `--settings` file. * Verified in the Claude Code 2.1.288 binary: env `false` short-circuits * the feature ("tengu_prompt_suggestion_init", source "env"); the setting * is documented as "When false, prompt suggestions are disabled". * - Gemini CLI: AI prompt completion (`general.enablePromptCompletion`, * off by default) — pinned off in the project's `.gemini/settings.json`. * - Codex CLI: no feature that fills the composer; its rotating placeholder * is faint and is never submitted (the input guard ignores it). */ export declare const RUNTIME_INPUT_SAFETY: { readonly CLAUDE_CODE_ENV: Readonly>; readonly CLAUDE_CODE_SETTINGS: { readonly promptSuggestionEnabled: false; }; readonly GEMINI_GENERAL_SETTINGS: { readonly enablePromptCompletion: false; }; }; /** * Claude Code auto-compacts each agent's conversation at this many tokens * (`autoCompactWindow` in the control-plane `--settings` file; Claude Code * accepts 100k–1M). Owner 2026-10-10: compact at ~350k instead of near the * 1M window, so long sessions stop paying for huge contexts on every turn. * Override with CREWLY_CLAUDE_AUTOCOMPACT_WINDOW (tokens, or `auto` for * Claude Code's own default). */ export declare const CLAUDE_CODE_CONTEXT: { readonly DEFAULT_AUTO_COMPACT_WINDOW: 350000; readonly MIN_AUTO_COMPACT_WINDOW: 100000; readonly MAX_AUTO_COMPACT_WINDOW: 1000000; readonly ENV_OVERRIDE: "CREWLY_CLAUDE_AUTOCOMPACT_WINDOW"; }; /** * The auto-compact window Crewly sets for Claude Code sessions. * * @param env - Environment (tests) * @returns Tokens, or null to leave Claude Code's default (`auto`) */ export declare function claudeAutoCompactWindow(env?: NodeJS.ProcessEnv): number | null; /** * Input guard for typing into agent runtimes (2026-10-03 phantom-input * incident). See services/session/tui-input-guard.ts. */ export declare const TUI_INPUT_GUARD: { /** * An `unknown` reading before typing is re-read after the screen has * caught up (crewly 1.20.232, 2026-10-05): first after the parser has * drained (a read can land mid-frame), then once after asking the * runtime to repaint (a broken byte from Claude Code 2.1.289 can leave * stale cells that only a full repaint clears). */ readonly UNKNOWN_REREAD_MS: 250; /** Pause between the two terminal sizes of a repaint request */ readonly REPAINT_RESIZE_SETTLE_MS: 300; /** Wait after a repaint request before reading again */ readonly REPAINT_READ_SETTLE_MS: 1200; /** At most one repaint request per session per this long */ readonly REPAINT_MIN_INTERVAL_MS: 30000; /** * Unreadable recovery ladder (2026-10-08 Ella: a frame taller than the * 80x24 window scrolled the box off screen, and the small repaint above * cannot fix a window that is too short). This many unreadable settled * reads in a row, and the session is enlarged to RECOVERY_TERMINAL_COLS x * RECOVERY_TERMINAL_ROWS before reading again. Still only re-reads: the * reading decides what is typed, exactly as before. */ readonly UNREADABLE_RESIZE_AFTER_READS: 2; /** Wait after enlarging an unreadable session before reading again (ms) */ readonly RESIZE_READ_SETTLE_MS: 1500; /** * Output a session produces this long after Crewly resized it is the * runtime redrawing its screen, not the agent working: it does not count * as activity (ms). */ readonly RESIZE_OUTPUT_IGNORE_MS: 3000; /** Bottom screen rows logged (as a shape, letters masked) when a box stays unreadable */ readonly UNKNOWN_SHAPE_ROWS: 8; /** Shortest run of `─` that counts as an input-box rule */ readonly RULE_MIN_CHARS: 10; /** * Most U+FFFD cells a rule may hold. Claude Code 2.1.289 on a loaded * machine (2026-10-05) reaches the PTY with a `─` (3 UTF-8 bytes) broken * apart, which xterm paints as 1-3 replacement cells inside the rule. */ readonly RULE_MAX_GARBLED_CELLS: 6; /** * A labelled top rule whose left end was overwritten by stale transcript * text (a garbled cell shifted the runtime's cursor maths, so a later * repaint started at the wrong column): at most this many leading * non-rule characters, and only when at least RULE_STALE_PREFIX_MIN_CHARS * `─` remain. */ readonly RULE_STALE_PREFIX_MAX_CHARS: 16; readonly RULE_STALE_PREFIX_MIN_CHARS: 40; /** Most status/footer lines allowed below an input box */ readonly FOOTER_MAX_LINES: 6; /** * Most rows below the bottom rule when searching for a Claude Code / * Antigravity box. Claude Code lists background agents and shells under * the box, so a busy session can have well over FOOTER_MAX_LINES rows * there (crewly#1113). Only the box search uses this; turn-signal reading * keeps FOOTER_MAX_LINES. */ readonly BOX_FOOTER_MAX_LINES: 14; /** Most lines an input box may span */ readonly MAX_BOX_LINES: 60; /** * A runtime's collapsed marker for a paste, alone in the box: * Claude Code "[Pasted text #1 +4 lines]" / "[Pasted text #2]", * Codex "[Pasted Content 1234 chars]". */ readonly PASTE_MARKER_PATTERN: RegExp; /** * An image file path in a pasted message. Claude Code pulls each one out of * the paste and shows it as "[Image #N]"; the own-paste check needs one per * such marker. */ readonly IMAGE_PATH_PATTERN: RegExp; /** Layouts verified against live captures (fixtures under services/session/__fixtures__/tui) */ readonly VERIFIED_LAYOUTS: readonly string[]; /** * Empty-box hints a runtime paints in solid (not faint) text, lower case, * whole-box match: Gemini 0.40.1. */ readonly SOLID_PLACEHOLDERS: readonly string[]; /** * Placeholder hints of layouts not verified live (Antigravity, older * Gemini), lower case, prefix match. */ readonly UNVERIFIED_PLACEHOLDERS: readonly string[]; /** Clear-line key sent to empty a box before typing (Ctrl+U: kill to line start) */ readonly CLEAR_KEY: "\u0015"; /** * Backspace sent after each Ctrl+U: joins the now-empty line to the one * above, so the next Ctrl+U clears it. Verified live: one Ctrl+U + * Backspace pair clears one line in Claude Code 2.1.288, Codex 0.160.0 * and Gemini 0.40.1 (Ctrl+U alone stalls on Gemini's first empty line). */ readonly JOIN_KEY: ""; /** Ctrl+U+Backspace pairs on top of one per box line */ readonly CLEAR_PAIRS_EXTRA: 2; /** Hard cap on pairs for one clear */ readonly CLEAR_PAIRS_MAX: 200; /** Wait after each clear-key press before re-reading the box (ms) */ readonly CLEAR_SETTLE_MS: 150; /** * A paste of ours not yet seen in the box is given up after this (ms): * a busy Claude Code renders a paste seconds late, never minutes. */ readonly OWN_PASTE_PENDING_MAX_MS: number; /** * A paste of ours the box has shown stays ours while every readable box * keeps showing it; one unreadable this long in a row ends it (ms). */ readonly OWN_PASTE_UNSEEN_MAX_MS: number; /** * A paste of ours not yet seen in the box may still render this long * after it was written (a busy Claude Code: ~10 s); no other paste goes * in meanwhile, and a delivery of the same message waits for it (ms) */ readonly PASTE_PENDING_HOLD_MS: 15000; /** * A paste of ours the box has never shown stays in the input ledger this * long, through empty readings (ms): a busy Claude Code renders a paste * only at its next turn boundary, which can be many minutes later * (2026-10-05 21:20Z edu-game-milo: rendered after 2 min as * "[Pasted text #3 +3 lines]" + the rest as text, then read as foreign * because the 15 s pruning had dropped it). Any outside input still * clears the ledger at once. */ readonly LEDGER_UNSEEN_PASTE_MAX_MS: number; /** An idle agent's box holding text Crewly cannot attribute this long is reported once (ms) */ readonly STUCK_INPUT_NOTIFY_MS: number; /** How often boxes holding a paste of ours are re-read (ms) */ readonly OWN_PASTE_WATCH_MS: 5000; /** Bottom screen lines searched for a turn in progress (busy bar, spinner line) */ readonly BUSY_BAR_TAIL_LINES: 40; /** Screen samples taken to confirm a busy screen is repainting */ readonly BUSY_REPAINT_SAMPLES: 3; /** Pause between those samples (ms); a live spinner changes several times a second */ readonly BUSY_REPAINT_SAMPLE_MS: 400; /** A screen showing a turn whose PTY has been silent this long is frozen: idle (ms) */ readonly BUSY_FROZEN_MS: 20000; /** A busy hold this long tells the owner/orchestrator once (ms) */ readonly BUSY_HOLD_NOTIFY_MS: number; /** How often messages held for a busy agent are retried (ms) */ readonly BUSY_HOLD_RECHECK_MS: 30000; /** How long to keep looking for a paste to render in a busy agent's box (ms) */ readonly PASTE_RENDER_MAX_WAIT_MS: 15000; /** * HTTP client timeout for an in-process caller of * `POST /api/terminal/:session/write` with `mode: "message"` (#1431). The * server answers only after the guarded paste has finished, which in the * worst case is: pending-paste hold (`PASTE_PENDING_HOLD_MS`, 15 s) + * scaled paste delay (≤ 5 s) + render retries (1 s) + render wait * (`PASTE_RENDER_MAX_WAIT_MS`, 15 s) + settle reads ≈ 37 s. A client that * gives up earlier (the dispatcher used 5 s) records "write failed" while * the server still goes on to submit the brief, so the same brief is * redispatched behind a delivery that did land. */ readonly WRITE_CLIENT_TIMEOUT_MS: 45000; /** Re-read interval while waiting for a late paste to render (ms) */ readonly PASTE_RENDER_POLL_MS: 500; /** Wait after pressing Enter on our own lost paste before looking again (ms) */ readonly OWN_MARKER_SUBMIT_SETTLE_MS: 1500; /** Consecutive refusals for one session after which the hold is logged as an error */ readonly ESCALATE_AFTER_REFUSALS: 5; /** Extra waits for a paste to render before refusing to press Enter (ms) */ readonly PASTE_RENDER_RETRY_MS: readonly number[]; /** * Directory under CREWLY_HOME for messages delivered by file reference: * our paste was collapsed into markers that do not add up to it, so the * message is written there and a one-line pointer is pasted (crewly#1028). */ readonly FILE_DELIVERY_DIR: "deliveries"; /** Delivery files older than this are pruned (ms) */ readonly FILE_DELIVERY_KEEP_MS: number; /** Most characters of routing tags kept in front of a file-reference line */ readonly FILE_REFERENCE_TAGS_MAX: 200; }; /** * Circuit breaker for deliveries into an input box that stays someone * else's (crewly#1028: ~1,100 failed redeliveries and as many failed wakes * in 2h45m against one agent whose box the guard would not touch). */ export declare const INPUT_CIRCUIT_CONSTANTS: { /** Refusals for this long in a row open the circuit (ms) */ readonly OPEN_AFTER_MS: number; /** First wait between probe deliveries once open (ms); doubles each probe */ readonly PROBE_FIRST_MS: number; /** Longest wait between probe deliveries (ms) */ readonly PROBE_MAX_MS: number; }; /** * Patterns for detecting Claude Code plan mode in terminal output. * When plan mode is detected, the session command helper should send * Escape to dismiss it before delivering messages. * * Re-exported from waiting-patterns to maintain a single source of truth. */ export { PLAN_MODE_PATTERNS as PLAN_MODE_DISMISS_PATTERNS } from './services/continuation/patterns/waiting-patterns.js'; /** * Message queue constants for sequential message processing. * Used by the MessageQueueService for orchestrator communication. */ export declare const MESSAGE_QUEUE_CONSTANTS: { /** Maximum number of messages allowed in the queue */ readonly MAX_QUEUE_SIZE: 100; /** Default timeout for a single message response (ms) */ readonly DEFAULT_MESSAGE_TIMEOUT: 180000; /** Maximum number of completed/failed messages retained in history */ readonly MAX_HISTORY_SIZE: 50; /** Delay between processing consecutive messages (ms) */ readonly INTER_MESSAGE_DELAY: 500; /** Maximum number of requeue retries before permanently failing a message */ readonly MAX_REQUEUE_RETRIES: 5; /** Maximum number of system events to batch into a single delivery */ readonly MAX_SYSTEM_EVENT_BATCH: 100; /** Max combined chars when coalescing pending system events in-queue */ readonly MAX_SYSTEM_EVENT_COALESCE_CHARS: 12000; /** Maximum pending system events in queue before oldest are dropped (#218). * Prevents agent status event floods from burying user messages. */ readonly MAX_PENDING_SYSTEM_EVENTS: 10; /** Queue persistence file name (stored under crewly home) */ readonly PERSISTENCE_FILE: "message-queue.json"; /** Queue persistence directory name */ readonly PERSISTENCE_DIR: "queue"; /** Socket.IO event names for queue status updates */ readonly SOCKET_EVENTS: { /** Emitted when a new message is enqueued */ readonly MESSAGE_ENQUEUED: "queue:message_enqueued"; /** Emitted when a message starts processing */ readonly MESSAGE_PROCESSING: "queue:message_processing"; /** Emitted when a message is completed */ readonly MESSAGE_COMPLETED: "queue:message_completed"; /** Emitted when a message fails */ readonly MESSAGE_FAILED: "queue:message_failed"; /** Emitted when a message is cancelled */ readonly MESSAGE_CANCELLED: "queue:message_cancelled"; /** Emitted with full queue status update */ readonly STATUS_UPDATE: "queue:status_update"; }; }; /** * Message replay constants for replaying pending messages after orchestrator restarts (#247). * Used by the MessageReplayService to scan chat history for unreplied user messages * that arrived while the orchestrator was offline, and replay them into the queue. */ export declare const MESSAGE_REPLAY_CONSTANTS: { /** Maximum age of messages to consider for replay (ms). Default: 24 hours */ readonly MAX_REPLAY_WINDOW_MS: number; /** Minimum age of the persisted queue state before replaying (ms). * Prevents replaying on quick restarts where no messages were likely missed. */ readonly MIN_OFFLINE_DURATION_MS: 30000; /** Maximum number of messages to replay in a single startup cycle */ readonly MAX_REPLAY_COUNT: 20; /** Prefix added to replayed messages for orchestrator awareness */ readonly REPLAY_PREFIX: "[REPLAYED]"; }; /** * Thread Status Queue constants for tracking inbound message thread lifecycle. * Used by ThreadStatusQueueService for persistence, expiry, and recovery. */ export declare const THREAD_STATUS_CONSTANTS: { /** Persisted JSON filename under crewly home directory */ readonly STORAGE_FILE: "thread-status-queue.json"; /** Maximum number of entries to retain (oldest terminal entries pruned first) */ readonly MAX_ENTRIES: 500; /** Timeout in minutes before a non-terminal thread is marked as expired */ readonly STALE_TIMEOUT_MINUTES: 30; /** Retention period in hours for terminal entries before cleanup */ readonly CLEANUP_RETENTION_HOURS: 24; /** * How long a compact tombstone of a cleaned-up terminal entry is kept. * The restart resume scan looks back 24h by thread-file mtime, which can * be later than the entry's last update (an agent posting a completion * report into the thread). Without a record the thread read as * "unreplied" on every restart (#757), so this must comfortably exceed * that lookback. */ readonly TOMBSTONE_RETENTION_HOURS: number; /** Maximum tombstones kept (oldest dropped first) */ readonly MAX_TOMBSTONES: 5000; /** Debounce interval in ms for persisting state to disk */ readonly PERSIST_DEBOUNCE_MS: 100; /** Maximum number of delivery retries before marking as error */ readonly MAX_RETRY_COUNT: 3; /** Maximum length of the message preview stored with each entry */ readonly MAX_PREVIEW_LENGTH: 200; /** Markers in orchestrator replies that indicate delegation occurred */ readonly DELEGATION_MARKERS: readonly ["[DELEGATED]", "[ASSIGNED]", "delegate-task", "delegated to"]; /** Markers in orchestrator replies that indicate a follow-up question */ readonly FOLLOW_UP_MARKERS: readonly ["?", "please clarify", "could you", "can you elaborate"]; }; /** * Activity monitor constants for agent idle/busy detection. * Used by ActivityMonitorService for terminal output polling. */ export declare const ACTIVITY_MONITOR_CONSTANTS: { /** Polling interval for checking terminal output changes (ms). * Reduced from 120s to 30s (#124) to detect agent idle state faster, * cutting notification delay from 2min to 30s worst-case. */ readonly POLLING_INTERVAL_MS: 30000; /** Maximum time an agent can stay in_progress before auto-reset to idle (ms). * Prevents workingStatus from getting stuck due to continuous terminal output * (spinners, TUI re-renders). Default: 15 minutes. */ readonly MAX_IN_PROGRESS_MS: 900000; }; /** * Antigravity "Running command..." hang (2026-10-10): agy's TUI kept a * "Running command..." spinner for 8+ minutes after the command had finished * and agy had no child process. The agent stayed busy forever, so queued * messages and the runtime switch (which waits for a safe point) never ran. * Escape un-sticks it. */ export declare const ANTIGRAVITY_STUCK_COMMAND_CONSTANTS: { /** The spinner must have been on screen this long (ms) before Escape is sent */ readonly STUCK_AFTER_MS: number; /** Not more than one Escape per session in this window (ms) */ readonly REPEAT_COOLDOWN_MS: number; /** Bottom screen lines searched for the spinner */ readonly SCREEN_LINES: 20; /** The spinner label (case-insensitive) */ readonly SPINNER_PATTERN: RegExp; }; /** * Give-up recovery (#841, specs/2026-09-27-give-up-recovery.md): when a worker * stops on a feasibility give-up, queue a retry with a different approach, * bounded per root WorkItem, then one escalation to the team lead. */ export declare const GIVE_UP_RECOVERY_CONSTANTS: { /** Retries per root WorkItem when the team sets no recoveryPolicy. */ readonly DEFAULT_MAX_RETRIES: 2; /** Retry WorkItem id: `${rootId}${RETRY_ID_INFIX}${attempt}`. */ readonly RETRY_ID_INFIX: ":giveup:"; /** Escalation review WorkItem id: `${rootId}${REVIEW_ID_SUFFIX}`. */ readonly REVIEW_ID_SUFFIX: ":review:gave_up"; /** WorkItem.metadata key for the recorded stop classification. */ readonly STOP_METADATA_KEY: "stop"; /** WorkItem.metadata key for the attempt log carried by retries. */ readonly GIVE_UP_METADATA_KEY: "giveUp"; /** Characters of stop text kept per attempt. */ readonly MAX_REASON_CHARS: 2000; /** Audit label for transitions this feature makes. */ readonly ACTOR_VIA: "give-up-recovery"; }; /** * Completion evidence contract (#873, specs/2026-10-03-completion-evidence.md): * a WorkItem is marked done only with evidence — artifacts that exist, * commands with their exit codes — and a worker that could not finish reports * `blocked` evidence instead. */ export declare const COMPLETION_EVIDENCE_CONSTANTS: { /** The two rollout modes for a completion that carries no evidence. */ readonly MODES: readonly string[]; /** * Default mode for a completion with no evidence. `enforce` (default since * #1312): 400 `evidence_required`. `warn` (the escape hatch, set with * {@link ENV_MODE}): accepted, logged, and the response carries a `warning`. * Malformed evidence, missing artifacts, failing commands and `blocked` * entries are handled the same in both modes. */ readonly EVIDENCE_ENFORCEMENT_MODE: "warn" | "enforce"; /** * WorkItem types whose completion must carry evidence: a worker's * deliverable. Every other type is harness bookkeeping (triage, replans, * retros, reconciles, notifications) and may complete without it. An item * with no known type is treated as a deliverable. */ readonly REQUIRED_FOR_TYPES: readonly string[]; /** Env var that overrides {@link EVIDENCE_ENFORCEMENT_MODE} (`warn` | `enforce`). */ readonly ENV_MODE: "CREWLY_EVIDENCE_MODE"; /** Most evidence entries accepted on one completion. */ readonly MAX_ENTRIES: 50; /** Longest string accepted in any evidence field. */ readonly MAX_FIELD_CHARS: 8000; /** URL schemes accepted for an artifact without checking it exists. */ readonly URL_SCHEMES: readonly string[]; /** Response codes for each rejection. */ readonly CODES: { readonly MALFORMED: "evidence_malformed"; readonly MISPLACED: "evidence_misplaced"; readonly MISSING: "evidence_required"; readonly ARTIFACT_NOT_FOUND: "evidence_artifact_not_found"; readonly ARTIFACT_UNRESOLVABLE: "evidence_artifact_relative_path"; readonly ARTIFACT_EMPTY: "evidence_artifact_empty"; readonly ARTIFACT_STALE: "evidence_artifact_stale"; readonly COMMAND_FAILED: "evidence_command_failed"; readonly REVIEW_UNCHECKED: "review_unchecked"; readonly REVIEW_CHECKS_MALFORMED: "review_checks_malformed"; readonly REVIEW_CHECK_FAILED: "review_check_failed"; }; /** Worker evidence entries listed, numbered, in a review item's description (#1314). */ readonly REVIEW_LISTED_ENTRIES: 10; /** * The worker's decision record (#875): optional completion fields the * reviewer reads beside the evidence. Each is a list of short strings. */ readonly DECISION_FIELDS: readonly ["decisions", "notTaken", "assumptions"]; /** Heading the review brief gives each decision field (#875). */ readonly DECISION_LABELS: Record; /** Entries of one decision field shown in a review brief (#875). */ readonly REVIEW_LISTED_DECISIONS: 5; /** Max characters of one decision entry in a review brief (#875). */ readonly DECISION_ENTRY_MAX_CHARS: 300; /** Outcomes a reviewer records for one check (#1314). */ readonly CHECK_OUTCOMES: readonly string[]; /** The shape of a reviewer's check record, quoted in every refusal (#1314). */ readonly CHECKS_HINT: string; /** * A local file artifact last modified more than this before its WorkItem * was created is stale: it was not made for this task (#1313). The slack * covers clock skew and a file saved just before the item was filed. */ readonly STALE_GRACE_MS: number; /** The shape a worker must send, quoted in every rejection and warning. */ readonly SHAPE_HINT: string; }; /** * Constants for the waiting_on_human attention verdict (#815). * See specs/2026-09-26-agent-waiting-on-human.md. */ export declare const AGENT_ATTENTION_CONSTANTS: { /** Non-empty lines at the bottom of the screen the dialog rules examine. * Dialogs render at the bottom; older answered dialogs sit above it. */ readonly BOTTOM_LINES: 20; /** Lines captured from the terminal buffer for the verdict (more than the * 5 lines the activity diff uses, because a dialog spans ~15 lines). */ readonly CAPTURE_LINES: 60; /** How long an agent must stay waiting_on_human before the reconciler moves * its running WorkItems to blocked (5 minutes). */ readonly BLOCK_WORK_ITEM_AFTER_MS: 300000; /** WorkItem blockedReason / escalation reason used for this state. */ readonly BLOCKED_REASON: "waiting_on_human"; }; /** * Event bus constants for the agent event pub/sub system. * Used by EventBusService for subscription management and notification delivery. */ export declare const EVENT_BUS_CONSTANTS: { /** Debounce window for batching event notifications (ms). * Events within this window are deduplicated per agent and delivered * as a single combined message to reduce orchestrator context consumption. */ readonly EVENT_DEBOUNCE_WINDOW_MS: 5000; /** Maximum wait time before forcing a flush of buffered notifications (ms). * Prevents indefinite deferral when new events keep resetting the debounce timer. * Set to 30 seconds — a reasonable ceiling for non-critical event delivery. */ readonly MAX_BATCH_WAIT_MS: 30000; /** Default subscription time-to-live in minutes (8 hours). * Increased from 2h to handle long-running tasks without subscription expiry. */ readonly DEFAULT_SUBSCRIPTION_TTL_MINUTES: 480; /** Maximum allowed subscription TTL in minutes (24 hours) */ readonly MAX_SUBSCRIPTION_TTL_MINUTES: 1440; /** Maximum subscriptions per subscriber session */ readonly MAX_SUBSCRIPTIONS_PER_SESSION: 50; /** Maximum total subscriptions across all sessions */ readonly MAX_TOTAL_SUBSCRIPTIONS: 200; /** Interval for cleaning up expired subscriptions (ms) */ readonly CLEANUP_INTERVAL: 60000; /** Prefix for event notification messages delivered to orchestrator */ readonly EVENT_MESSAGE_PREFIX: "EVENT"; /** Threshold for cleaning stale entries from recentPublishMap */ readonly DEDUP_MAP_CLEANUP_THRESHOLD: 100; /** How long a delivered event id is remembered per subscription, so a * replayed publish of the same event is not delivered twice (#926) */ readonly DELIVERED_EVENT_TTL_MS: number; }; /** * Recurring "TL progress check" scheduled by the team-leader delegate-task * skill. Off by default (the lead is woken by the worker's agent:idle event * and by report-status); an opted-in check never fires more often than * MIN_INTERVAL_MINUTES. The 5-minute default woke Claude sessions 219 times * in 3.5 days (2026-10-16). */ export declare const DELEGATION_PROGRESS_CHECK_CONSTANTS: { /** Message prefix the skill uses; identifies these checks in the scheduler */ readonly MESSAGE_PREFIX: "TL progress check:"; /** Shortest allowed interval (minutes) for a recurring delegation progress check */ readonly MIN_INTERVAL_MINUTES: 30; }; /** * Constants for Slack thread file storage. * Used by SlackThreadStoreService to persist thread conversations * and agent-thread associations. */ export declare const SLACK_THREAD_CONSTANTS: { /** Directory name under crewly home for thread files */ readonly STORAGE_DIR: "slack-threads"; /** JSON file mapping agents to their originating threads */ readonly AGENT_INDEX_FILE: "agent-index.json"; /** File extension for thread conversation files */ readonly FILE_EXTENSION: ".md"; /** Maximum age for thread files before cleanup (30 days) */ readonly MAX_THREAD_AGE_MS: number; }; /** * Constants for Google Chat thread file storage. * Used by GoogleChatThreadStoreService to persist thread conversations. */ export declare const GCHAT_THREAD_CONSTANTS: { /** Directory name under crewly home for thread files */ readonly STORAGE_DIR: "gchat-threads"; /** File extension for thread conversation files */ readonly FILE_EXTENSION: ".md"; /** Max recent messages to include as thread context in deliveries (#195) */ readonly MAX_CONTEXT_MESSAGES: 10; }; /** * Constants for Slack bridge fallback delivery. * When the reply-slack skill doesn't deliver within the wait window, * the bridge sends the response directly as a fallback. */ export declare const SLACK_BRIDGE_CONSTANTS: { /** Time to wait for the reply-slack skill to deliver before fallback (ms) */ readonly SKILL_DELIVERY_WAIT_MS: 10000; }; /** * Constants for Slack team channels — one Slack channel + one chat-v2 huddle * per Crewly team, so the owner talks to a whole team (and to individual * agents by `@name`) without going through the orchestrator. */ /** * Slack DMs to an agent's own bot user (routed into the owner's chat-v2 DM * channel with that agent, never to the orchestrator). */ export declare const SLACK_AGENT_DM_CONSTANTS: { /** Link store filename under CREWLY_HOME */ readonly STORE_FILENAME: "slack-agent-dms.json"; /** Reaction added to a routed inbound DM while the agent works on it */ readonly INBOUND_REACTION: "eyes"; /** * Owner of the DM channels on a single-user OSS install — the same * principal the dashboard (no-JWT auth fallback) and the portal relay * adapter use, so Slack DMs land in the DM channel the owner already sees. */ readonly OWNER_USER_ID: "dev-user-001"; /** * Slack threads in one DM still waiting for the agent's answer, oldest * first. An answer the agent does not attribute goes to the oldest of * them — never silently to the newest (2026-09-28, Ella: an EFT answer * owed in thread A landed in thread B). Capped so a DM the agent never * answers cannot grow the link store without bound. */ readonly MAX_OPEN_THREADS: 20; /** * A file an agent attaches without naming a thread, this soon after it * posted an answer, goes into that answer's thread (the file and the * sentence about it stay together). */ readonly ATTACH_FOLLOWS_REPLY_MS: number; /** * An unanswered DM thread older than this no longer attracts an answer * that names no thread: an orc answer to a new question landed under an * 18-hour-old one (steamfun-ops 2026-10-01, crewly#1015 §8). The answer * goes where the agent's current turn came from instead. */ readonly OPEN_THREAD_MAX_AGE_MS: number; }; /** * Stable per-thread keys for Slack conversations (2026-09-28). * * Every Slack message delivered to an agent carries `[SLACK-THREAD:]`, * `` = `:` (a top-level message is its * own thread root). The reply tools (`reply-chat --thread`, `reply-channel * --thread`, `attach-file --thread`, `slack-post --thread`) take the key and * post in exactly that thread, so an answer to an earlier thread is never * posted under the newest one. */ export declare const SLACK_THREAD_KEY_CONSTANTS: { /** Tag name as the agent sees it: `[SLACK-THREAD:]` */ readonly TAG: "SLACK-THREAD"; /** chat-v2 message metadata field an agent reply's thread key is recorded under */ readonly METADATA_KEY: "slackThreadKey"; /** * One-line rule for every agent's prompt. Kept short: it rides in the * communication module of every role. */ readonly PROMPT_RULE: string; }; /** * "Agent is typing…" placeholder in Slack. Slack has no typing indicator * for bots, so the agent's bot posts a placeholder the moment a message is * handed to the agent and edits it into the reply. */ /** * LLM-wiki as a knowledge base (2026-09-19 review): retention gate, * two-step retrieval index, usage ledger, proposals, history, privacy. */ export declare const WIKI_KB_CONSTANTS: { /** Why a page earns a place in the vault (one is required to create a page). */ readonly KEEP_BECAUSE: readonly ["changes_decision", "contradicts", "hard_fact", "reusable_method"]; /** One-line conclusion ("what this means for us") — required on every page. */ readonly SUMMARY_MAX_CHARS: 240; readonly TITLE_MAX_CHARS: 120; /** * Human-owned folder at the vault root (#914 P2). Holds the owner's * explicit rules page; frozen for every automated path (ingest, curation, * cleanup, lint) whether or not the vault's SCHEMA.md lists it. */ readonly OWNER_FOLDER: "owner"; /** Per-vault one-line-per-page index read as retrieval step 1. */ readonly INDEX_FILENAME: "index.md"; /** Index bytes returned to an agent before it is cut down to the query's folders. */ readonly INDEX_MAX_BYTES: number; /** Retrieval ledger (JSONL): every query, what it read, and misses. */ readonly USAGE_FILENAME: "usage.jsonl"; readonly USAGE_WINDOW_DAYS: 7; readonly USAGE_MAX_BYTES: number; /** Pages written by `proposed_only` roles wait here until a canonical role accepts. */ readonly PROPOSED_DIR: "llm-curated/_proposed"; /** Per-page prior revisions (who changed what) — capped per page. */ readonly HISTORY_DIR: ".wiki-history"; readonly HISTORY_MAX_REVISIONS: 20; /** Bridge: stop re-creating a legacy-migrate WI after this many no-progress strikes. */ readonly MIGRATE_MAX_STRIKES: 3; /** Lint: token-set Jaccard on title+summary above which two pages are flagged as possible duplicates/contradictions. */ readonly DUPLICATE_JACCARD: 0.5; /** Frontmatter keys agents may set. */ readonly FRONTMATTER_KEYS: readonly ["title", "summary", "keep_because", "tags", "visibility", "source", "caller", "recorded", "updated", "superseded_by", "superseded_at", "superseded_reason", "proposed_by"]; }; /** * Wiki ingest queue (`~/.crewly/wiki-queue/`) hygiene (#914). The bridge * sweeps the queue once per tick: claims an agent abandoned go back to * pending, items nobody processed within the max age move to the * dead-letter folder, and a vault whose oldest pending item is older than * the alert age is reported to the owner. */ export declare const WIKI_QUEUE_CONSTANTS: { /** Pending/claimed items older than this (by `queuedAt`) are moved to the dead-letter folder. */ readonly MAX_ITEM_AGE_MS: number; /** Warn the owner when a vault's oldest pending item is older than this. */ readonly STALE_ALERT_AGE_MS: number; /** Minimum gap between two stale-queue alerts for the same vault. */ readonly STALE_ALERT_COOLDOWN_MS: number; /** A claim older than this with no process/skip is released back to pending. */ readonly CLAIM_TIMEOUT_MS: number; /** Sub-folder of the queue root that holds expired items (kept, never deleted). */ readonly DEAD_LETTER_DIR: "dead-letter"; /** * Code drain (#1345): a pending item nobody claimed within this age is * filed into the vault's `log.md` by the backend, without an LLM. */ readonly DRAIN_MIN_AGE_MS: number; /** Items filed per drain pass (the pass runs hourly, so a backlog of hundreds clears within a day). */ readonly DRAIN_BATCH_SIZE: 100; /** Pause between two filed items, so a long backlog does not saturate disk I/O. */ readonly DRAIN_ITEM_PAUSE_MS: 25; /** `claimedBy` / caller the drain stamps on items it files. */ readonly DRAIN_CLAIMANT: "system:wiki-queue-drain"; }; /** * The vault activity log (`llm-curated/log.md`) and what the backend does * with it (#1345): dedupe on append, rotation into an archive, recent * entries in search, and a weekly digest page compiled from them. */ export declare const WIKI_LOG_CONSTANTS: { /** Vault-relative path of the activity log. */ readonly LOG_RELATIVE_PATH: "llm-curated/log.md"; /** Vault-relative folder rotated logs move to (`log-YYYY-MM-DD.md`). Never read by search. */ readonly ARCHIVE_DIR: "llm-curated/log-archive"; /** Rotate when the log is larger than this. */ readonly ROTATE_MAX_BYTES: number; /** Rotate when the log holds more entries than this. */ readonly ROTATE_MAX_ENTRIES: 2000; /** After a rotation the fresh log keeps the non-noise entries from this many days. */ readonly CARRY_FORWARD_DAYS: 30; /** Cap on entries carried into the fresh log. */ readonly CARRY_FORWARD_MAX_ENTRIES: 500; /** Search/recall index the entries from this many days. */ readonly SEARCH_WINDOW_DAYS: 30; /** Cap on log entries one vault contributes to a search. */ readonly SEARCH_MAX_ENTRIES_PER_VAULT: 300; /** BM25 score multiplier for a log entry (pages rank above entries of equal relevance). */ readonly SEARCH_ENTRY_WEIGHT: 0.6; /** Separator between the log path and an entry's timestamp in a search hit path. */ readonly ENTRY_PATH_SEPARATOR: "#"; /** Recent entry bodies remembered per vault to drop an identical append. */ readonly DEDUPE_RECENT_ENTRIES: 500; /** Bytes of the log tail read once to seed the dedupe set. */ readonly DEDUPE_SEED_TAIL_BYTES: number; /** The digest compiles the uncompiled non-noise entries from this many days. */ readonly COMPILE_WINDOW_DAYS: 7; /** Fewer uncompiled entries than this: no digest page is written. */ readonly COMPILE_MIN_ENTRIES: 3; /** Cap on entries one digest page takes (the rest wait for the next run). */ readonly COMPILE_MAX_ENTRIES: 150; /** Characters of an entry body quoted in the digest. */ readonly COMPILE_ENTRY_MAX_CHARS: 400; /** Vault-relative folder of digest pages. */ readonly COMPILE_DIR: "llm-curated/digests"; /** Vault-root file remembering which entries were compiled (dot-file: walkers skip it). */ readonly COMPILE_STATE_FILE: ".wiki-compile-state.json"; /** Cap on remembered compiled entry keys per vault. */ readonly COMPILE_STATE_MAX_KEYS: 5000; /** Hourly maintenance tick (drain every tick; rotation + compile once per day). */ readonly MAINTENANCE_INTERVAL_MS: number; /** Rotation + compile run at most this often. */ readonly MAINTENANCE_DAILY_MS: number; /** Delay before the boot-time pass, so boot I/O settles first. */ readonly MAINTENANCE_BOOT_DELAY_MS: number; /** Maintenance bookkeeping (last daily pass), under CREWLY_HOME. */ readonly MAINTENANCE_STATE_FILE: "wiki-maintenance-state.json"; /** Kill switch env var; the value `false` disables all wiki maintenance. */ readonly MAINTENANCE_ENV_KILL_SWITCH: "CREWLY_WIKI_MAINTENANCE"; }; /** * Knowledge gaps (#914 P1): the retrieval ledger records every query that * found nothing, and nothing turned those misses into work. Once per ISO * week the maintenance daily pass rolls the window's zero-hit queries into * one page per vault under `llm-curated/gaps/` and hands the team lead one * WorkItem to decide which gaps deserve a page. Deterministic, no LLM. */ export declare const WIKI_GAPS_CONSTANTS: { /** Queries from this many days back are rolled up. */ readonly WINDOW_DAYS: 7; /** Vault-relative folder of the weekly gap pages. */ readonly DIR: "llm-curated/gaps"; /** Vault-root file remembering the last week compiled and its WorkItem (dot-file: walkers skip it). */ readonly STATE_FILE: ".wiki-gaps-state.json"; /** A query shorter than this is noise (a stray token), not a question. */ readonly QUERY_MIN_CHARS: 3; /** A query longer than this is pasted text, not a question; counted but not listed. */ readonly QUERY_MAX_CHARS: 160; /** Cap on distinct gaps one page lists (the most-asked first). */ readonly MAX_GAPS_PER_PAGE: 50; /** Cap on agent names listed per gap. */ readonly MAX_AGENTS_PER_GAP: 5; /** WorkItem `metadata.kind` of the lead's review item. */ readonly WORKITEM_KIND: "wiki-knowledge-gaps"; /** Session that authors the page. */ readonly CALLER_SESSION: "system:wiki-knowledge-gaps"; }; /** * Daily agent-memory refresh (#1345): what an agent recorded in the wiki * log is reflected into its `memory.json` and the project's memory. */ export declare const AGENT_MEMORY_REFRESH_CONSTANTS: { readonly INTERVAL_MS: number; /** Delay before the boot-time catch-up run. */ readonly BOOT_DELAY_MS: number; /** Entries newer than this are considered on the first run (later runs use the last-run watermark). */ readonly LOOKBACK_MS: number; /** Cap on role-knowledge entries added per agent per run. */ readonly MAX_ENTRIES_PER_AGENT: 10; /** Cap on characters of one entry kept as knowledge. */ readonly ENTRY_MAX_CHARS: 600; /** Confidence of a knowledge entry derived from the agent's own log entries. */ readonly CONFIDENCE: 0.5; /** State file (last run per agent), under CREWLY_HOME. */ readonly STATE_FILE: "agent-memory-refresh-state.json"; /** Kill switch env var; the value `false` disables the refresh. */ readonly ENV_KILL_SWITCH: "CREWLY_AGENT_MEMORY_REFRESH"; }; /** * Automatic per-task work records (#1349): one `(record)` entry per finished * WorkItem in the project vault log, compiled into the weekly digest. */ export declare const WORK_RECORD_CONSTANTS: { /** Hourly tick: catch-up for finished items without a record, blocked items, deferred items. */ readonly TICK_MS: number; /** Delay before the boot-time pass, after the other wiki jobs settled. */ readonly BOOT_DELAY_MS: number; /** A terminal event waits this long before the record is assembled (ticket sync, verify item, evidence land first). */ readonly EVENT_DELAY_MS: number; /** Catch-up looks at items that ended within this many days. */ readonly CATCHUP_WINDOW_DAYS: 7; /** Cap on records written per pass (the rest wait for the next tick). */ readonly CATCHUP_MAX_PER_PASS: 200; /** A record waiting for the owner's OK on a ticket is written anyway after this long. */ readonly DEFER_MAX_MS: number; /** An item parked in `blocked` for longer than this gets a blocked record. */ readonly BLOCKED_AFTER_MS: number; /** Hard cap on one record's body (the `meta:` line always fits). */ readonly MAX_CHARS: 2500; /** Characters of the ask quoted. */ readonly QUOTE_MAX_CHARS: 160; /** Characters of the result summary kept. */ readonly DID_MAX_CHARS: 400; /** Cap on output lines and characters per line. */ readonly MAX_OUTPUTS: 8; readonly OUTPUT_MAX_CHARS: 160; /** Cap on error lines and characters per line. */ readonly MAX_ERRORS: 5; readonly ERROR_MAX_CHARS: 160; /** Cap on helper lines and characters per line. */ readonly MAX_HELPERS: 6; readonly HELPER_MAX_CHARS: 120; /** Bytes read from the end of the agent's transcript for the task window. */ readonly TRANSCRIPT_TAIL_BYTES: number; /** Transcripts are consulted only for items that ended within this long (older windows are not in the tail). */ readonly TRANSCRIPT_MAX_AGE_MS: number; /** Slack around the task window when slicing the transcript. */ readonly TRANSCRIPT_WINDOW_SLACK_MS: number; /** Cap on trace events read per record. */ readonly TRACE_MAX_EVENTS: 2000; /** State file (recorded keys), under CREWLY_HOME. */ readonly STATE_FILE: "work-records-state.json"; /** Cap on remembered record keys. */ readonly STATE_MAX_KEYS: 5000; /** Kill switch env var; the value `false` disables work records. */ readonly ENV_KILL_SWITCH: "CREWLY_WORK_RECORDS"; /** Source type of a record entry in the vault log. */ readonly SOURCE_TYPE: "work_record"; /** Prefix of a record's `ref:` (`work-record:#`). */ readonly REF_PREFIX: "work-record:"; /** Records the startup briefing lists. */ readonly BRIEFING_MAX: 5; /** Characters of one record line in the briefing. */ readonly BRIEFING_LINE_MAX_CHARS: 240; /** WorkItem types that are bookkeeping, never recorded. */ readonly SKIPPED_TYPES: readonly string[]; }; /** * Memory-aware boot (crewly#1353). On 2026-10-17 a 16 GB Mac kernel-panicked * a minute after a Crewly restart: 10 agents restored back-to-back, then the * wiki, memory and work-record boot passes on fixed delays, while the machine * was already swapping. Pressure mode only engages after two 30 s samples, * so the boot restore reads the raw snapshot itself and the boot maintenance * passes wait for a quiet machine. Env overrides exist for the two memory * thresholds and the launch spacing. */ export declare const BOOT_PRESSURE_CONSTANTS: { /** Swap in use at or above this share counts as tight for the boot (`CREWLY_BOOT_PRESSURE_SWAP_PERCENT`). */ readonly SWAP_USED_PERCENT: 80; /** Free memory below this counts as tight for the boot (`CREWLY_BOOT_PRESSURE_MIN_FREE_MB`). */ readonly MIN_FREE_MB: number; /** The free floor is capped at this share of physical memory, so small machines are not always "tight". */ readonly MIN_FREE_FRACTION: 0.2; /** Minimum gap between two boot launches, pressure or not, so RSS growth is visible before the next (`CREWLY_RESTORE_LAUNCH_SPACING_MS`). */ readonly MIN_LAUNCH_SPACING_MS: number; /** Under pressure: how long after an agent registered its process counts as settled before the next launch. */ readonly SETTLE_AFTER_READY_MS: number; /** Under pressure: how often the snapshot is re-read while a launch waits for the machine to calm down. */ readonly PRESSURE_POLL_MS: number; /** A held launch is logged on the first poll and then every this many polls (once a minute at the default poll). */ readonly PRESSURE_HOLD_LOG_EVERY_POLLS: 6; /** Under pressure: an owner-waiting agent waits at most this long for the machine to calm, then launches anyway. */ readonly PRESSURE_HOLD_MAX_MS: number; /** Under pressure: at most this many boot launches in flight at once. */ readonly PRESSURE_MAX_CONCURRENCY: 2; /** Boot maintenance passes re-check the restore queue and the snapshot this often. */ readonly MAINTENANCE_POLL_MS: number; /** Boot maintenance passes give up waiting after this long and run reduced. */ readonly MAINTENANCE_WAIT_MAX_MS: number; /** Work records written by the boot pass when it runs reduced (instead of CATCHUP_MAX_PER_PASS). */ readonly WORK_RECORD_REDUCED_BATCH: 50; /** A vault log larger than this is rotated by streaming (tail read, header count) instead of being read whole. */ readonly ROTATION_STREAM_BYTES: number; /** Bytes read from the end of a streamed log to pick the entries carried forward. */ readonly ROTATION_TAIL_BYTES: number; /** Chunk size of the streaming entry count. */ readonly ROTATION_CHUNK_BYTES: number; /** The owner gets one line when boot restores have been held back for longer than this. */ readonly OWNER_NOTE_AFTER_MS: number; /** * Boot launches outside the restore queue (the deferred-start retry) are * held for memory at most this long after boot; afterwards the pressure * mode cap alone decides, because macOS swap stays full for hours after * the pressure is gone and a held agent is still owed something. */ readonly RETRY_HOLD_MAX_MS: number; /** Env override of SWAP_USED_PERCENT. */ readonly ENV_SWAP_PERCENT: "CREWLY_BOOT_PRESSURE_SWAP_PERCENT"; /** Env override of MIN_FREE_MB. */ readonly ENV_MIN_FREE_MB: "CREWLY_BOOT_PRESSURE_MIN_FREE_MB"; /** Env override of MIN_LAUNCH_SPACING_MS. */ readonly ENV_LAUNCH_SPACING_MS: "CREWLY_RESTORE_LAUNCH_SPACING_MS"; }; /** * Startup briefing content (#1345): wiki pages, recent lessons and open * owner threads an agent gets at registration, on top of its session memory. */ export declare const STARTUP_BRIEFING_CONSTANTS: { /** Hard cap on the whole section. */ readonly MAX_CHARS: 12000; /** Cap on the wiki index excerpt. */ readonly INDEX_MAX_CHARS: 4000; /** Most-recently-changed topic pages listed. */ readonly RECENT_PAGES: 5; /** Characters of a page summary shown. */ readonly PAGE_SUMMARY_MAX_CHARS: 240; /** Lessons from this many days back. */ readonly LESSONS_DAYS: 7; /** Cap on lessons listed. */ readonly LESSONS_MAX: 12; /** Characters of one lesson shown. */ readonly LESSON_MAX_CHARS: 300; /** Cap on owner threads listed. */ readonly THREADS_MAX: 5; /** Characters of a thread preview shown. */ readonly THREAD_PREVIEW_MAX_CHARS: 160; /** Section heading. */ readonly HEADING: "## Your Project Knowledge"; }; /** * Standing-answer pages (#816): a few question-anchored pages per scope, * read at boot as a plain file read (no retrieval, no LLM) and refreshed by * an agent only when the memory behind them has moved. */ export declare const STANDING_ANSWERS_CONSTANTS: { /** Project pages live here, under the project's `.crewly` data dir. */ readonly PROJECT_DIR: "wiki/llm-curated/standing"; /** * The owner's explicit rules page (#914 P2), under the project's `.crewly` * data dir: inside the wiki vault so it is browsable, but in the frozen * `owner/` folder, outside `llm-curated/`, so no automated pass rewrites it. */ readonly OWNER_RULES_FILE: "wiki/owner/rules.md"; /** Page id of the owner rules page. */ readonly OWNER_RULES_PAGE_ID: "owner-rules"; /** * Per-project standing questions (#914 P2), under the project's `.crewly` * data dir: `{ "questions": [{ id, question, source, match? }] }`. */ readonly QUESTIONS_FILE: "standing-questions.json"; /** Custom questions read per project; the rest of the file is ignored. */ readonly CUSTOM_QUESTIONS_MAX: 10; /** Cap on a custom question. */ readonly QUESTION_MAX_CHARS: 200; /** Cap on `match` keywords of a custom question. */ readonly MATCH_MAX_TERMS: 10; /** The agent page, under `/agents//`. */ readonly AGENT_FILE: "standing.md"; /** Page id of the agent page. */ readonly AGENT_PAGE_ID: "unfinished-work"; /** Hard cap on the whole Standing Answers prompt section (~2 000 tokens). */ readonly PROMPT_MAX_CHARS: 8000; /** Cap on one page's body inside the prompt. */ readonly PROMPT_PAGE_MAX_CHARS: 3000; /** Cap on one section body written through the API. */ readonly SECTION_MAX_CHARS: 1500; /** Cap on a section heading. */ readonly HEADING_MAX_CHARS: 80; /** Cap on citations per section. */ readonly MAX_CITES_PER_SECTION: 20; /** Entries listed in a refresh WorkItem brief. */ readonly BRIEF_MAX_ENTRIES: 30; /** Characters of each entry shown in a refresh brief. */ readonly BRIEF_ENTRY_MAX_CHARS: 240; /** Shared topic words that flag two same-day entries as a possible supersede pair (#884). */ readonly SUPERSEDE_MIN_SHARED_WORDS: 2; /** Refresh tick interval when CREWLY_WIKI_REFLECT_INTERVAL_MS is unset (the reflect default). */ readonly REFRESH_INTERVAL_MS: number; /** Minimum time between two refresh WorkItems for the same page. */ readonly REFRESH_COOLDOWN_MS: number; /** At most this many refresh WorkItems per tick (PTY paste-flood guard). */ readonly REFRESH_MAX_CREATES_PER_TICK: 2; /** * A refresh WorkItem that failed or was cancelled without writing the page * is retried even though the source watermark has not moved again (it * otherwise would not be re-raised until new memory arrives). Bounded so a * page that keeps failing does not retry forever. */ readonly REFRESH_MAX_RETRIES: 2; /** Refresh bookkeeping (last raised watermark per page), under CREWLY_HOME. */ readonly REFRESH_STATE_FILE: "standing-refresh-state.json"; /** * Change history of a page (#914 P1 delta edits): the page as it was before * each content change, plus one `changes.log` line per section write. Project * pages keep it under `///`, outside * the wiki vault so the index never sees it; the agent page under * `agents///`. */ readonly HISTORY_DIR: "standing-history"; /** Snapshots kept per page; the oldest are pruned on each write. */ readonly HISTORY_KEEP: 30; /** Lines kept in a page's `changes.log`. */ readonly HISTORY_LOG_KEEP: 500; /** `metadata.kind` of refresh WorkItems. */ readonly WORKITEM_KIND: "standing-refresh"; /** * Decisions whose title starts with this are task-completion records that * report-status files automatically, not decisions; they are out of scope * for "decisions in force" (in this repo: 229 of 327 decision entries). */ readonly COMPLETED_DECISION_PREFIX: "[COMPLETED]"; /** * Agent-memory entries starting with this are report-status completion * learnings ("Task completed: "). Done work is neither * unfinished nor blocking, so they are out of scope for the agent page * and do not move its watermark; "Task failed:"/"Task blocked:" stay in. */ readonly COMPLETED_LEARNING_PREFIX: "Task completed:"; }; /** * Per-WorkItem git worktrees (#814). Opt-in per project in v1 * (`Project.worktrees = 'on'`); a team can opt out (`Team.worktrees = * 'off'`); `CREWLY_WORKTREES=off` disables them everywhere. */ export declare const WORKTREE_CONSTANTS: { /** Worktrees live here, relative to the repo root (gitignored via .crewly). */ readonly DIR: ".crewly/worktrees"; /** Per-worktree manifests (symlinks, copies, base) live here, inside DIR. */ readonly META_DIR: ".meta"; /** Branch name prefix: `wi/`. */ readonly BRANCH_PREFIX: "wi/"; /** Repo-root file listing gitignored-but-needed files to copy in. */ readonly INCLUDE_FILE: ".worktreeinclude"; /** Cap on `.worktreeinclude` entries honoured. */ readonly INCLUDE_MAX_ENTRIES: 50; /** Heavy directories symlinked (not copied) from the repo root when present and untracked. */ readonly DEFAULT_SHARED_DIRS: readonly string[]; /** * Workspace packages' install dirs (`backend/node_modules`, * `packages/ui/node_modules`) are symlinked too when the lock file is * identical in repo and worktree (crewly#1354): searched this deep. */ readonly NESTED_INSTALL_DEPTH: 2; /** Lock files compared (first one present in the repo decides). */ readonly LOCK_FILES: readonly string[]; /** Directories never descended into while looking for nested install dirs. */ readonly NESTED_INSTALL_SKIP_DIRS: readonly string[]; /** Marker line for the entries this feature adds to `.git/info/exclude`. */ readonly EXCLUDE_MARKER: "# crewly worktree shared paths (#814) — never commit these"; /** Timeout for local git commands (checkout of a large repo can take minutes under load). */ readonly GIT_TIMEOUT_MS: number; /** Timeout for `git ls-remote` (offline must not hang the sweep). */ readonly REMOTE_TIMEOUT_MS: 15000; /** Orphan sweep interval. */ readonly SWEEP_INTERVAL_MS: number; /** Kill switch env var; the value `off` disables worktrees everywhere. */ readonly ENV_KILL_SWITCH: "CREWLY_WORKTREES"; /** WorkItem metadata key holding the worktree record. */ readonly METADATA_KEY: "worktree"; }; /** One of {@link WIKI_KB_CONSTANTS.KEEP_BECAUSE}. */ export type WikiKeepBecause = (typeof WIKI_KB_CONSTANTS.KEEP_BECAUSE)[number]; /** How many recent thread channels an owner notification tries before giving up when SLACK_DEFAULT_CHANNEL is unset. */ export declare const SLACK_NOTIFICATION_FALLBACK_MAX_CANDIDATES = 4; /** * Appended to every reply instruction an agent gets (owner, 2026-09-24): size * the job first; a long one gets a short interim note (understanding + plan) * before the work, so the owner is not left with only "is working on it…". * `{cmd}` is the interim form of the agent's reply skill. */ export declare const CHAT_REPLY_PACING_HINT: string; /** Unassigned work goes to a decider and moves up when not taken (owner, 2026-09-24). */ export declare const UNASSIGNED_ROUTE_CONSTANTS: { /** A decider has this long to take an item before it moves one level up (ms) */ readonly ESCALATE_AFTER_MS: number; /** How often routed items are checked (ms) */ readonly SWEEP_INTERVAL_MS: number; }; export declare const SLACK_TYPING_CONSTANTS: { /** Placeholder text once the agent holds the message. "working on it", not "typing": an agent that has the message is usually reading files, running commands or looking things up — typing is the last step. */ readonly TYPING_TEXT: "⚙️ {name} is working on it…"; /** Shown while an idle agent is being started + registered (cold start) */ readonly WAKING_TEXT: "🌙 {name} is waking up…"; /** Shown when a cold start is taking longer than WAKING_SLOW_MS */ readonly WAKING_SLOW_TEXT: "🌙 {name} is still starting up (a cold start takes 1–2 minutes)…"; readonly WAKING_SLOW_MS: number; /** Shown while the agent's start waits for a free slot: the message is held and delivered when it starts (not a failure) */ readonly QUEUED_TEXT: "🕐 {name} will pick this up shortly — the reply will follow here."; /** Shown when the message could not be delivered even after Crewly retried; never asks the owner to resend */ readonly FAILED_TEXT: "⚠️ {name} couldn't take this message just now — Crewly will keep trying and the reply will follow here if it gets through."; /** What the placeholder becomes when the agent has not replied in time */ readonly TIMEOUT_TEXT: "⏱ {name} is still working on this — the reply will follow."; /** How long a placeholder waits for the reply before it is edited to TIMEOUT_TEXT */ readonly TIMEOUT_MS: number; /** Slack errors meaning the agent's bot cannot post in that channel at all (not a member of a private channel, or gone) */ readonly UNREACHABLE_CHANNEL_ERROR: RegExp; /** After such an error, no placeholder is tried for that agent in that channel for this long (ms) */ readonly UNREACHABLE_CHANNEL_MS: number; /** A timed-out placeholder is still removed by a reply arriving within this long (ms) */ readonly EXPIRED_KEEP_MS: number; /** * A `slack-post` that names no thread answers an owed placeholder only if * that placeholder went up this recently. Older ones belong to questions * from another turn; a scheduled post was captured into those threads (#808). */ readonly UNTHREADED_ANSWER_MAX_AGE_MS: number; /** A placeholder younger than this is not taken down when the turn ends (race with delivery) */ readonly SETTLE_MIN_AGE_MS: number; /** Answers posted in a thread are remembered this long for settling placeholders at turn end (ms) */ readonly ANSWERED_KEEP_MS: number; /** * A placeholder skipped at turn end for being too young is looked at * again once it is SETTLE_MIN_AGE_MS old, plus this margin — and taken * down then unless the agent is mid-turn. Without the second look it * stayed until the agent's NEXT turn ended, which can be hours * (2026-09-28: "Ella is working on it…" left under an answered thread). */ readonly SETTLE_RECHECK_MARGIN_MS: number; /** * How an answer replaces its thread's placeholder. `true` = the oldest * placeholder is edited into the answer (chat.update) and any others in * the thread are deleted, so nothing is left behind (2026-09-28). * `false` = the 2026-09-23 behaviour: the answer is posted as a new * message (Slack notifies on new messages, not on edits) and the * placeholders are deleted after it. * Owner-facing default is `false`: a reply must notify (edits don't), and * the placeholder races that left "working on it…" behind are fixed. */ readonly REPLACE_BY_EDIT: false; /** Fallback text when a settled placeholder cannot be deleted */ readonly SETTLED_TEXT: "✓ {name} read this — no reply needed."; /** Reaction put on the person's message when the agent settled it without replying */ readonly SETTLED_REACTION: "white_check_mark"; /** Placeholders restored after a restart that nobody claims within this long are taken down */ readonly BOOT_ORPHAN_MS: number; /** File (under CREWLY_HOME) where outstanding placeholders survive a restart */ readonly STORE_FILENAME: "slack-typing-placeholders.json"; /** * Metadata flag on an agent message that is an interim note ("got it — * here is my plan") rather than the answer: the Slack mirror posts it and * puts the working-on-it placeholder back under it, and the ticket loop * does not count it as the answer (owner, 2026-09-24). */ readonly INTERIM_METADATA_KEY: "interim"; /** * How long after an owner's Slack message was delivered the harness * watches its recipients for a turn to start. The first recipient seen * going busy in that window gets the "working on it" placeholder posted * for it — no longer left to the agent calling `reply-channel --working` * (2026-09-30: Owen worked 3.5 min on a #pro-ce message with nothing * showing). 60 s, not 30: busy is observed by the 30 s ActivityMonitor * poll, so a turn that starts right after delivery can be seen up to one * poll later, and the PTY write itself takes a few seconds. */ readonly AUTO_WORKING_WINDOW_MS: number; /** A watched delivery that never reports its outcome is dropped after this (cold starts take 1–2 min) */ readonly AUTO_WORKING_DELIVERY_MAX_MS: number; /** Deliveries still watched at once; the oldest is dropped past this */ readonly AUTO_WORKING_MAX_WATCHES: 200; /** * A scheduled brief (trigger / cron work item, `[SCHEDULED]` message) * delivered to an agent this long before an owner message's watch opened, * or any time after, means the agent's next turn is the scheduled one: it * gets no "working on it" under the owner's message. Busy is seen up to a * 30 s poll late, so a brief written just before the owner's message can * still be the turn that shows up. */ readonly AUTO_WORKING_SCHEDULED_GRACE_MS: number; }; /** * Slack thread context read at delivery time. * * Cloud drops Slack events written by the account's own bots (loop guard), * so a post by an agent on another machine never reaches this one. When a * message in that thread is then delivered here, the agent is shown what the * thread actually says — fetched read-only from Slack, never recorded * locally (2026-09-28, #daily-info: "@Atlas 看看上面的这些"). */ export declare const SLACK_THREAD_CONTEXT_CONSTANTS: { /** Whether the Slack context block is fetched at all */ readonly ENABLED: true; /** Most messages shown (newest kept) */ readonly MAX_MESSAGES: 40; /** * Character budget of the whole block's message lines (newest kept). * 5 000 → 12 000 (crewly#1344): with the thread root always shown and * 1 500 characters per message, eight messages no longer fit in 5 000, * and the owner approved the larger prompt over agents that could not see * what the thread was about. */ readonly MAX_CHARS: 12000; /** Characters kept per message before clipping */ readonly PER_MESSAGE_CHARS: 4000; /** * Characters kept per message in the rendered block. The block is a * compact "thread so far", not a transcript: a 4 000-char draft pasted in * the thread made the prompt 13.5k chars, the busy-agent note cut it at * 6 000, and the newest posts and the owner's ping were the part cut * (2026-10-08, #content-team). 500 → 1 500 (crewly#1344): 500 cut most * owner briefs mid-sentence; the busy-agent note now keeps the block's end. */ readonly BLOCK_PER_MESSAGE_CHARS: 1500; /** The block shows at least this many of the newest messages, or everything since the agent's own last post when that is more */ readonly BLOCK_RECENT_MESSAGES: 8; /** * The thread's root message is always in the block, even when it is * older than the window — it says what the thread is about (crewly#1344). * Shown with this marker and a line saying how many messages were skipped. */ readonly ROOT_MARK: "(thread root)"; /** Marks a line addressed to the agent (or a person's post) after its last post in the thread */ readonly UNANSWERED_MARK: "⟵ not answered by you yet"; /** * Said after a bare @-mention (no text). `{name}` = who pinged, `{where}` = * "in this thread" / "in this channel", `{what}` = what to read above. */ readonly BARE_MENTION_NOTE: "{name} pinged you {where} with no text — read the {what} above and act on what is pending for you there (oldest unanswered request first). Do not switch to another topic."; /** Metadata flag on chat rows recorded from a Slack thread read (posts this machine never received) */ readonly BACKFILL_METADATA_KEY: "slackBackfill"; /** Recent channel messages read before a top-level @-mention */ readonly CHANNEL_HISTORY_LIMIT: 15; /** Page size for `conversations.replies` */ readonly REPLIES_PAGE_SIZE: 200; /** Most `conversations.replies` pages read for one thread */ readonly REPLIES_MAX_PAGES: 5; /** How long one (channel, thread) fetch is reused */ readonly CACHE_TTL_MS: 60000; /** Most cached threads kept */ readonly CACHE_MAX_ENTRIES: 200; /** How long a resolved user / bot name is reused */ readonly NAME_CACHE_TTL_MS: number; /** Most distinct users resolved via `users.info` for one block */ readonly MAX_NAME_LOOKUPS: 25; /** Per-request timeout; delivery never waits longer than a few of these */ readonly FETCH_TIMEOUT_MS: 5000; /** A recurring read failure (same channel and reason) is logged again after this long (ms) */ readonly FAILURE_RELOG_MS: number; /** Back-off after a 429 without a usable Retry-After (ms) */ readonly DEFAULT_RETRY_AFTER_MS: 30000; /** Slack Web API base URL */ readonly API_BASE_URL: "https://slack.com/api"; /** * Slack error codes after which the next candidate token is tried: this * token's app is not in the conversation or cannot read it. */ readonly TRY_NEXT_TOKEN_ERRORS: readonly string[]; /** Message subtypes that are conversation, not channel housekeeping */ readonly CONTENT_SUBTYPES: readonly string[]; }; /** * Crewly channels (specs/2026-10-07-crewly-channels.md): a named room whose * members are agents from any team, matched to a Slack channel when Slack is * connected. */ export declare const CREWLY_CHANNEL_CONSTANTS: { /** Registry filename under CREWLY_HOME */ readonly STORE_FILENAME: "crewly-channels.json"; /** Most agents one channel may hold (same cap as a huddle) */ readonly MAX_MEMBERS: 50; /** Longest purpose line (Slack's purpose limit) */ readonly MAX_PURPOSE_LENGTH: 250; /** A list call refreshes names/members from Slack at most this often (ms) */ readonly SLACK_REFRESH_MIN_INTERVAL_MS: number; /** Purpose used when the owner gives none */ readonly DEFAULT_PURPOSE: "Crewly channel"; /** How often channels are checked against Slack in the background, incl. linking ones made while Slack was off (ms) */ readonly SLACK_SYNC_INTERVAL_MS: number; /** * Agent-to-agent hand-offs in a channel without Slack, in a row, before the * owner speaks again. Past it an agent's @ is recorded but wakes nobody * (the same cap Cloud applies to agent posts in shared Slack rooms). */ readonly AGENT_CHAIN_MAX: 8; }; export declare const SLACK_TEAM_CHANNEL_CONSTANTS: { /** How long a channel member list is trusted when picking between same-named agents. */ readonly MEMBER_CACHE_TTL_MS: number; /** First check of ad-hoc room members against Slack, after start (ms) */ readonly ADHOC_PRUNE_FIRST_DELAY_MS: number; /** How often ad-hoc room members are checked against Slack (ms) */ readonly ADHOC_PRUNE_INTERVAL_MS: number; /** Mapping store filename under CREWLY_HOME */ readonly STORE_FILENAME: "slack-team-channels.json"; /** Slack's hard limit on channel-name length */ readonly MAX_CHANNEL_NAME_LENGTH: 80; /** Slack's hard limit on channel purpose length */ readonly MAX_PURPOSE_LENGTH: 250; /** Max Levenshtein distance for a "did you mean @x?" suggestion */ readonly MENTION_SUGGEST_MAX_DISTANCE: 2; /** Max suggestions offered for one unknown @name */ readonly MENTION_SUGGEST_MAX: 3; /** * Huddle-message metadata key: Slack users the message @'d who are * people, not Crewly agents (nor any bot we can identify). When set, the message * names its addressees, so no agent is drawn in by thread engagement or * the "nobody addressed" fallback (specs/slack-room-presence.md). */ readonly PEOPLE_MENTIONS_METADATA_KEY: "slackMentionedPeople"; /** * Huddle-message metadata key: why a message with no @ of its own was * treated as addressed to people — `same-sender-followup` or * `person-exchange` (specs/slack-room-presence.md "Follow-ups of a * person-to-person exchange"). */ readonly ADDRESSEE_INHERITED_METADATA_KEY: "slackAddresseeInherited"; /** * Huddle-message metadata key: agents Cloud says the message @'d * (`mentionedAgentSessions`), wherever they run. A thread whose latest * addressed human message names an agent is not a person-to-person exchange. */ readonly AGENT_MENTIONS_METADATA_KEY: "slackMentionedAgents"; /** * A message with no @ that its sender posts this soon after their own * message to people only, in the same conversation, is addressed to the * same people (2026-10-02, #personal-assistant-team: the owner answered a * colleague in two messages 35 s apart, and the second, un-@'d, woke Aria). * Overridden by the env var named in PEOPLE_FOLLOWUP_WINDOW_ENV. */ readonly PEOPLE_FOLLOWUP_WINDOW_MS: number; /** Env var that overrides PEOPLE_FOLLOWUP_WINDOW_MS (milliseconds). */ readonly PEOPLE_FOLLOWUP_WINDOW_ENV: "CREWLY_SLACK_PEOPLE_FOLLOWUP_WINDOW_MS"; /** * How long a thread counts as a person-to-person exchange after its last * human-to-human @: an un-@'d message within this time of it is context * only; after it, the normal rules apply again (owner, 2026-10-02). * Overridden by the env var named in PERSON_EXCHANGE_WINDOW_ENV. */ readonly PERSON_EXCHANGE_WINDOW_MS: number; /** Env var that overrides PERSON_EXCHANGE_WINDOW_MS (milliseconds). */ readonly PERSON_EXCHANGE_WINDOW_ENV: "CREWLY_SLACK_PERSON_EXCHANGE_WINDOW_MS"; /** Reaction added to a routed inbound message while the team works on it */ readonly INBOUND_REACTION: "eyes"; /** How many routed Slack messages to remember for duplicate-copy suppression */ readonly SEEN_INBOUND_MAX: 500; /** How long a message's first copy holds off a second copy still racing it (ms) */ readonly INBOUND_IN_FLIGHT_MS: 30000; /** Synthetic team-id prefix for channels linked on the fly (no Crewly team behind them) */ readonly ADHOC_TEAM_PREFIX: "adhoc:"; /** Slack's error code for a channel that was archived (`conversations.join` / `invite`) */ readonly ARCHIVED_ERROR_CODE: "is_archived"; /** * How long an owner's room message that reached nobody on this machine * waits for any agent (here or on another machine) to take it before this * machine wakes the room's lead itself — or, with no lead here, says so in * the thread. Cloud's presence can say "someone elsewhere is awake" while * that agent, told only optionally, stays quiet (2026-09-30, Think Tank room). */ readonly ROOM_UNANSWERED_FALLBACK_MS: number; /** * How long the one backup machine of a shared room waits, for an owner * message it deferred to the room's owner machine, before handing it to * its own best responder. The owner machine's agent normally answers * (or shows "working on it") well inside this; it only fires when that * agent never replied (2026-10-14, #ce-x-wang-law: the owner machine's * runtime was broken and nobody answered). Longer than the 90 s fallback * on purpose: the owner machine runs its own 90 s fallback first. */ readonly ROOM_TAKEOVER_WATCH_MS: number; /** * Pending unanswered-message watches survive a restart in this file * (under CREWLY_HOME): a watch armed seconds before a restart would * otherwise vanish with its timer. */ readonly ROOM_UNANSWERED_STORE_FILENAME: "slack-room-unanswered.json"; /** A restored watch fires no sooner than this after boot (Slack and the agents come up first). */ readonly ROOM_UNANSWERED_RESTORE_GRACE_MS: number; /** A restored watch older than this is dropped: the owner-message watchdog owns long waits. */ readonly ROOM_UNANSWERED_RESTORE_MAX_AGE_MS: number; /** The in-thread line when nobody could take an owner's room message. */ readonly ROOM_UNANSWERED_NOTE: "No agent picked up this message (nobody in the room was awake to take it). Please @ an agent and send it again."; /** * How long routing an owner's recorded room message may take to REACH * dispatch before the route guard rescues it with the unanswered-message * fallback. Dispatch itself (sequential cold starts) is never timed. * crewly#1015 §7. */ readonly ROUTE_STALL_MS: number; /** Env override for ROUTE_STALL_MS (ms; tests) */ readonly ROUTE_STALL_ENV: "CREWLY_ROOM_ROUTE_STALL_MS"; /** * Each step before dispatch (room presence, dispatch plan, seen reaction, * placeholders, roster, ticket intake, thread context) may take this long; * then routing goes on without it. */ readonly ROUTE_STEP_TIMEOUT_MS: number; /** Env override for ROUTE_STEP_TIMEOUT_MS (ms; tests) */ readonly ROUTE_STEP_TIMEOUT_ENV: "CREWLY_ROOM_ROUTE_STEP_TIMEOUT_MS"; /** The in-thread line when routing an owner's room message got stuck and no agent here could take it. */ readonly ROOM_ROUTE_STALLED_NOTE: "Crewly couldn't get this message to an agent (routing it got stuck). Please @ an agent and send it again."; /** How many recent huddle turns to scan for the room's last local speaker. */ readonly ROOM_LAST_SPEAKER_SCAN: 50; /** Fallback icon when a member has no avatar */ readonly DEFAULT_ICON_EMOJI: ":robot_face:"; /** Per-role icon fallbacks (Slack emoji names) */ readonly ROLE_ICON_EMOJI: Record; }; /** * Constants for Slack agent identities — one real Slack bot user per agent, * provisioned through Crewly Cloud (`/api/cloud/slack/*`). */ export declare const SLACK_AGENT_IDENTITY_CONSTANTS: { /** Local identity cache filename under CREWLY_HOME (mode 0600) */ readonly STORE_FILENAME: "slack-agent-identities.json"; /** Cloud API prefix (appended to the cloud URL) */ readonly CLOUD_PATH: "/api/cloud/slack"; /** How often to ask Cloud about pending installs (ms) */ readonly PENDING_POLL_INTERVAL_MS: 30000; /** Stop polling a pending install after this long (ms) */ readonly PENDING_POLL_MAX_AGE_MS: number; /** HTTP timeout for Cloud calls (ms) */ readonly REQUEST_TIMEOUT_MS: 15000; }; /** * Slack v3 — "Crewly Cloud owns Slack". Cloud installs the master Slack app * once per account, receives Slack events over HTTP and pushes them to the * right OSS instance through the relay queue; instances register themselves * (teams, channels, agents) so Cloud can route. */ export declare const SLACK_CLOUD_CONSTANTS: { /** Cached copy of `GET /api/cloud/slack/config` under CREWLY_HOME (mode 0600) */ readonly CONFIG_CACHE_FILENAME: "slack-cloud-config.json"; /** Per-instance Slack settings (currently only the `primary` flag) under CREWLY_HOME */ readonly INSTANCE_SETTINGS_FILENAME: "slack-instance.json"; /** Cloud API prefix (appended to the cloud URL) */ readonly CLOUD_PATH: "/api/cloud/slack"; /** `GET` — master workspace + agent identities for this account */ readonly CONFIG_PATH: "/config"; /** `DELETE` — remove the account's Slack workspace on Cloud */ readonly WORKSPACE_PATH: "/workspace"; /** GET → every workspace on the account (redacted) */ readonly WORKSPACES_PATH: "/workspaces"; /** A Cloud config appearing this soon after boot still counts as the boot decision — only when the recorded source is `cloud` */ readonly BOOT_PRECEDENCE_WINDOW_MS: number; /** Last Slack source that connected (`env` | `cloud`) under CREWLY_HOME; decides boot precedence when both exist (#753) */ readonly SOURCE_PREFERENCE_FILENAME: "slack-source.json"; /** `PUT /instances/:instanceId` — registry heartbeat */ readonly INSTANCES_PATH: "/instances"; /** `POST` — provision per-agent apps for a team roster */ readonly AGENTS_SYNC_PATH: "/agents/sync"; /** DELETE /:agentSession removes one agent's Slack app */ readonly AGENTS_PATH: "/agents"; /** `POST` — deliver a room message to an agent on another machine */ readonly HANDOFF_PATH: "/handoff"; /** `POST` — ask Cloud to put agents of another machine in a Slack channel (orchestrator-created rooms) */ readonly ROOM_JOIN_PATH: "/rooms/join"; /** Event type of the Cloud-pushed room-join envelope */ readonly ROOM_JOIN_EVENT_TYPE: "crewly_room_join"; /** `POST` — the asked machine reports a room-join outcome; Cloud relays it to the asking machine (crewly#1430) */ readonly ROOM_JOIN_RESULT_PATH: "/rooms/join/result"; /** Event type of the Cloud-pushed room-join-result envelope */ readonly ROOM_JOIN_RESULT_EVENT_TYPE: "crewly_room_join_result"; /** `GET` — the account's Slack routing log (where each channel message went and why, 14 days) */ readonly ROUTING_DECISIONS_PATH: "/routing-decisions"; /** GET → who is who across the account (instances, teams, agents, bot users) */ readonly DIRECTORY_PATH: "/directory"; /** Channel rosters are cached this long (users.info is rate-limited) */ readonly DIRECTORY_CACHE_MS: number; /** `GET` — one-click install redirect (token + returnUrl in the query) */ readonly INSTALL_PATH: "/install"; /** Dashboard path the install flow returns to */ readonly INSTALL_RETURN_PATH: "/connections?platform=slack"; /** How often the cached Cloud config is re-fetched (ms) */ readonly CONFIG_REFRESH_INTERVAL_MS: number; /** How often the instance registry heartbeat is sent (ms) */ readonly REGISTRY_HEARTBEAT_INTERVAL_MS: number; /** Coalesce bursts of `team-saved` events into one heartbeat (ms) */ readonly TEAM_SAVED_DEBOUNCE_MS: 5000; /** Short retries while the relay queue is not registered yet (then the 5-min cadence takes over) */ readonly QUEUE_WAIT_MAX_RETRIES: 24; /** HTTP timeout for Cloud calls (ms) */ readonly REQUEST_TIMEOUT_MS: 15000; /** `env` = only local tokens, `cloud` = only Cloud, unset = the last connected source (else the self-hosted app) wins when both exist (#753) */ readonly SOURCE_ENV_VAR: "CREWLY_SLACK_SOURCE"; /** `1`/`true` marks this instance as the account's primary (DM / unmapped-channel target) */ readonly PRIMARY_ENV_VAR: "CREWLY_SLACK_PRIMARY"; /** Relay `type` of a Slack event pushed by Cloud */ readonly MESSAGE_TYPE: "slack_event"; /** `fromDeviceName` Cloud uses on pushed Slack events */ readonly CLOUD_DEVICE_NAME: "crewly-cloud-slack"; /** * Slack `message` subtypes the cloud transport still hands to the shared * inbound handler. Everything else (`message_changed`, `channel_join`, * `bot_message`, …) is dropped before it reaches routing. */ readonly INBOUND_ALLOWED_SUBTYPES: readonly ["file_share", "thread_broadcast"]; }; /** * Which sub-agent `report-status` lines are forwarded to the orchestrator's * message queue. Every forwarded line is a full-context model turn for the * orchestrator, so progress chatter it cannot act on stays out. */ export declare const ORC_STATUS_FORWARDING: { /** * Longest agent status forwarded to the orchestrator in full. A [DONE] * report can run to several thousand characters, and every character * stays in the orchestrator's conversation, re-read on every later turn. * The full report is still in the conversation it was posted to. */ readonly MAX_FORWARD_CHARS: 600; /** * Markers that only say "still going" or "I am up" — not forwarded. * [READY]/[ONLINE] joined the list on 2026-09-18: an agent woken by a * Slack message announced itself and the orchestrator spent a full * turn on it. Message delivery to a freshly started agent is the * system's job (queued messages flush on registration), so the * orchestrator has nothing to do with the announcement. Everything else * ([DONE], [IDLE] (agent is free for the next task), [BLOCKED], [FAILED], * structured reports, unknown formats) is forwarded. */ readonly PROGRESS_ONLY_MARKERS: RegExp; /** * A report-status line: the agent is talking to the orchestrator, not * answering a person. Anything else posted to `agent-response` is content * someone is waiting for — routing it to the orchestrator as "status" * swallows it (2026-09-30, #steamfun运维组: Avery's whole answer to the * owner went to the orc and never reached Slack). */ /** * Any status-shaped opening — `[WAITING]`, `[PENDING]`, `[IN-PROGRESS]`, * an all-caps tag in brackets — from the orchestrator posting as an agent * is its own status report, not an answer to a person (crewly#1015 * review: report-status takes free-form `--status`). */ readonly STATUS_SHAPED: RegExp; readonly STATUS_MARKERS: RegExp; /** * How long after the owner @'d an agent in a Slack room that agent's * reply (with no thread named) is taken as the answer to that message. */ readonly RECENT_ROOM_REQUEST_WINDOW_MS: number; }; /** * Who an agent status report wakes (specs/2026-10-01-orc-status-wakes.md). * * On 2026-09-29 the orchestrator ran 194 turns for system events against 3 * for the owner; 134 of them were team members' [DONE]/[BLOCKED] lines * about work their own team lead owns. Each turn re-reads ~70k tokens. * Status reports now go to whoever is responsible, and the orchestrator is * woken only when it has to act. */ export declare const ORC_WAKE_CONSTANTS: { /** Recorded only (status store, UI, ticket log) — never wake anyone */ readonly RECORD_ONLY_MARKERS: RegExp; /** "This piece of work is finished" */ readonly DONE_MARKERS: RegExp; /** Always the orchestrator's: a shipped milestone it forwards to the owner (issue #435) */ readonly ALWAYS_ORC_MARKERS: RegExp; /** "I am stuck" — the team lead hears first */ readonly ATTENTION_MARKERS: RegExp; /** Non-urgent reports are batched into one orchestrator digest at most this often */ readonly DIGEST_INTERVAL_MS: number; /** Lines listed in one digest (the rest are counted) */ readonly DIGEST_MAX_LINES: 20; /** Characters of each report shown in a digest line */ readonly DIGEST_LINE_CHARS: 160; /** Conversation id of the digest turn */ readonly DIGEST_CONVERSATION_ID: "system:status-digest"; /** Conversation id of status reports forwarded to a team lead */ readonly TEAM_LEAD_CONVERSATION_ID: "system:team-status"; /** A work item finished this recently still counts as the one a [DONE] is about */ readonly RECENT_COMPLETION_MS: number; /** How often the "orc wakes: N (…)" line is logged */ readonly COUNTER_LOG_INTERVAL_MS: number; /** `sourceMetadata` key naming why a queued message wakes the orchestrator */ readonly WAKE_CATEGORY_KEY: "orcWakeCategory"; }; /** * Unanswered-owner-message watchdog (specs/2026-09-30-owner-message-guarantee.md). * * Every owner message delivered to an agent here ends in an answer or in one * plain-words note saying who it is waiting on and why. */ /** * Failed turns of the in-process Crewly Agent runtime (crewly#1015 §2, * specs/2026-10-03-harness-drop-gaps.md §2): one retry, then the owner / * orchestrator is told. */ export declare const IN_PROCESS_TURN_FAILURE_CONSTANTS: { /** Wait before the one re-delivery of a message whose turn failed */ readonly RETRY_DELAY_MS: number; /** Re-deliveries of one message before it is reported */ readonly MAX_RETRIES: 1; /** How long a message's attempt count is remembered */ readonly ATTEMPT_TTL_MS: number; /** Characters of the failed message quoted in a report */ readonly PREVIEW_CHARS: 160; /** Characters of the error quoted in a report */ readonly ERROR_CHARS: 160; }; /** * Mirroring the orchestrator's answers in its own (dashboard) chat to an * owner who is not looking at it (crewly#1015 §11). */ export declare const ORC_CHAT_OWNER_MIRROR_CONSTANTS: { /** At most one DM per conversation this often; later answers are batched into the next */ readonly MIN_INTERVAL_MS: number; /** * A post on a system-event turn that belongs to the chat (a delegated * result, a promise follow-up whose origin is that chat) is mirrored when * the owner wrote in that chat within this window (follow-up H1). */ readonly SYSTEM_TURN_OWNER_WINDOW_MS: number; /** At most this many system-turn mirrors per conversation per 24 h */ readonly SYSTEM_TURN_DAILY_CAP: 3; /** Ids (WorkItems / requests / tickets) read from one system event */ readonly SYSTEM_EVENT_MAX_IDS: 5; /** The same text in the same conversation is mirrored once in this window */ readonly DEDUPE_WINDOW_MS: number; /** Conversations that already reach the owner on another messenger (id prefixes, lower-case) */ readonly OTHER_MESSENGER_PREFIXES: readonly string[]; /** chat-v2 owner-turn sources of other messengers */ readonly OTHER_MESSENGER_SOURCES: readonly string[]; }; /** * Liveness monitor (crewly#1015 §12): a gap in the backend's life — the * computer asleep, the event loop stuck, the process stopped without a * clean shutdown — is told to the owner once it is back. */ export declare const LIVENESS_MONITOR_CONSTANTS: { /** Record under CREWLY_HOME */ readonly STORE_FILENAME: "liveness.json"; /** How often the record is written */ readonly TICK_MS: number; /** A gap longer than this is told to the owner */ readonly GAP_ALERT_MS: number; /** A pending alert is retried each tick (Slack may come up late) for at most this long */ readonly ALERT_RETRY_MAX_MS: number; /** At most one crash DM this often; crashes in between are merged into the next one with a count */ readonly CRASH_MERGE_WINDOW_MS: number; /** Crashes kept waiting to be told (oldest dropped) */ readonly CRASH_UNSENT_MAX: 50; }; export declare const OWNER_MESSAGE_WATCHDOG_CONSTANTS: { /** T1: no answer and no working placeholder → re-deliver to the responsible agent */ readonly NUDGE_AFTER_MS: number; /** T2: still nothing → one note in the thread */ readonly NOTE_AFTER_MS: number; /** A nudge always gets at least this long before the note follows it */ readonly MIN_NOTE_GAP_AFTER_NUDGE_MS: number; /** A visibly-working agent (placeholder showing + mid-turn) is left alone at most this long */ readonly BUSY_EXTEND_CAP_MS: number; /** Evaluation cadence */ readonly TICK_MS: number; /** How often agent display names (used only in notes) are re-read */ readonly NAME_REFRESH_MS: number; /** Entries older than this when restored after downtime are dropped, not noted */ readonly STALE_DROP_MS: number; /** A message parked on a sign-in (`login_wait`) is kept this long for re-delivery after the login */ readonly LOGIN_WAIT_DROP_MS: number; /** * A message parked because its agent's turns keep failing (`failed_wait`, * crewly#1015 §2) is re-delivered after these waits (backing off), and * always when the agent's next turn succeeds; after the last one only a * successful turn re-delivers it. A failure for lack of credit / quota is * never retried on a timer: only a successful turn (credit restored, the * agent moved to another runtime, the owner acted) brings it back. Kept * for LOGIN_WAIT_DROP_MS. */ readonly FAILED_RETRY_BACKOFF_MS: readonly number[]; /** Cap on open entries (oldest dropped with a warning) */ readonly MAX_ENTRIES: 500; /** * Answers seen in a thread are remembered this long: an agent can answer * before the dispatch that delivered the message returns (a fast agent * while a colleague in the same room is still cold-starting). */ readonly RECENT_ANSWER_KEEP_MS: number; /** Recently resolved keys remembered for dedupe (a hand-off re-dispatch must not re-track) */ readonly MAX_RESOLVED_KEYS: 2000; /** Characters of the owner's message quoted in logs, the debug list and the nudge */ readonly PREVIEW_CHARS: 200; /** Persisted state under CREWLY_HOME */ readonly STORE_FILENAME: "owner-message-watchdog.json"; /** * Whole-message acknowledgements that need no answer (compared after * lower-casing and stripping whitespace/punctuation). Approval words such * as 可以 / 行 are deliberately absent: they usually ask for action. */ readonly ACK_WORDS: readonly string[]; /** Nudge delivered to the responsible agent ({waited} = minutes) */ readonly NUDGE_TEXT: string; /** * Note texts (from Crewly's own bot), English like the rest of the * owner-facing UI. {name} = agent display name. The login note names the * English command; 「重新登录 claude」 is still accepted as input. */ readonly NOTE_LOGIN_TEXT: "⏳ Still waiting on {name} — {runtime} on this machine is signed out. Reply `login` here to sign in from your phone (or `relogin {runtimeCmd}`); your message is kept and re-delivered once it's signed in."; readonly NOTE_ASLEEP_TEXT: "⏳ Still waiting on {name} — {name} isn't running and couldn't be woken ({detail})."; readonly NOTE_ERROR_TEXT: "⏳ Still waiting on {name} — your message couldn't be delivered ({detail})."; readonly NOTE_BUSY_CAP_TEXT: "⏳ {name} is still working on your message ({waited} min so far)."; readonly NOTE_SILENT_TEXT: "⏳ {name} got your message but hasn't replied in {waited} min; I've sent a reminder."; /** * The reminder did not reach the agent either. Said instead of "I've sent * a reminder", which would be false (2026-10-08 Ella). * - stuck: its input box cannot be read — Crewly restarts the session; * - foreign: its input box holds text Crewly did not write; * - queued: it is busy, the reminder waits in its queue. */ readonly NOTE_STUCK_TEXT: "⏳ {name}'s session is stuck; Crewly is restarting it — your message is queued."; readonly NOTE_INPUT_FOREIGN_TEXT: "⏳ {name} hasn't replied in {waited} min: its input box holds text Crewly didn't write, so your message is queued. Clear {name}'s input box to let it through."; readonly NOTE_SILENT_QUEUED_TEXT: "⏳ {name} got your message but hasn't replied in {waited} min; a reminder is queued for when {name} is free."; /** At most one "session is stuck" note per agent per this long (ms) */ readonly STUCK_NOTE_COOLDOWN_MS: number; /** Shown in a note when a failed delivery left no error detail */ readonly NOTE_UNKNOWN_DETAIL: "reason unknown"; /** The agent took the message but its run failed (crewly#1015 §2) */ readonly NOTE_TURN_FAILED_TEXT: "⚠️ {name} couldn't answer your message — its run failed ({detail}). Your message is kept and delivered again once {name} is working."; /** The agent hit a daily token cap (specs/2026-10-02-spend-cap.md) */ readonly NOTE_SPEND_CAP_TEXT: "⏳ Still waiting on {name} — {name} hit its daily token cap ({cap}). Your message is kept and delivered when the cap resets at midnight or you boost it (reply `boost {who} by 10M today` or `unlimited today for {who}`)."; }; /** * The single `reply` entry point (specs/2026-09-30-owner-message-guarantee.md §B). */ export declare const AGENT_REPLY_CONSTANTS: { /** The skill every delivered message names */ readonly SKILL_PATH: "config/skills/agent/core/reply/execute.sh"; /** * First line of every delivered reply hint. {identity} = the * `CREWLY_SESSION_NAME= ` prefix (or empty). */ readonly HINT_LINE: string; }; /** * Owner-facing OKR guidance (OKROwnerGuidanceService): approval nudges and * the weekly digest. */ export declare const OKR_GUIDANCE_CONSTANTS: { /** Re-nudge a pending proposal no more often than this */ readonly APPROVAL_NUDGE_COOLDOWN_MS: number; /** Weekly digest schedule (cron, in DIGEST_TZ); env CREWLY_OKR_DIGEST_CRON */ readonly DIGEST_CRON: "0 9 * * 1"; /** Timezone for DIGEST_CRON; env CREWLY_OKR_DIGEST_TZ */ readonly DIGEST_TZ: "UTC"; /** KR lines shown per mission in a message */ readonly MAX_KRS_IN_MESSAGE: 6; /** staleCycles at or above this flags a mission as "no progress" in the digest */ readonly STALE_CYCLES_FLAG: 2; }; /** * `skill_output` Key Result measurement (KRSkillMeasurerService). */ export declare const KR_SKILL_MEASURER_CONSTANTS: { /** Kill a measuring skill after this long */ readonly TIMEOUT_MS: 60000; /** Largest stdout accepted from a measuring skill */ readonly MAX_OUTPUT_BYTES: 1000000; /** Dot path read when `measurementConfig.jsonPath` is absent */ readonly DEFAULT_JSON_PATH: ".value"; }; /** * Constants for agent-initiated Slack posts (the `slack-post` skill). */ export declare const SLACK_AGENT_POST_CONSTANTS: { /** Max characters in one agent-initiated message */ readonly MAX_TEXT_LENGTH: 12000; }; /** * Constants for cross-machine messaging via Slack. * Two Crewly instances communicate through a shared Slack channel. */ export declare const CROSS_MACHINE_CONSTANTS: { /** Maximum number of processed message IDs to track for deduplication */ readonly MAX_TRACKED_MESSAGE_IDS: 500; }; /** * Constants for Slack message deduplication. * Prevents identical messages from being sent to the same channel+thread * within a short time window. */ export declare const SLACK_DEDUP_CONSTANTS: { /** Time window in ms within which duplicate messages are suppressed (30 seconds) */ readonly DEDUP_WINDOW_MS: 30000; /** Maximum number of message fingerprints to track in memory */ readonly MAX_TRACKED_MESSAGES: 100; }; /** * Constants for outgoing Slack message payloads. */ export declare const SLACK_OUTGOING_MESSAGE_CONSTANTS: { /** * Top-level `text` sent when a message has no text of its own and no * block text to derive one from. Slack rejects an empty `text` without * blocks (`no_text`) and uses `text` as the notification/accessibility * fallback when blocks are present, so it must never be empty. */ readonly EMPTY_TEXT_FALLBACK: "New message"; }; /** * Constants for NOTIFY Slack delivery reconciliation. * Used by NotifyReconciliationService to retry failed Slack deliveries * using persisted chat messages as the source of truth. */ export declare const NOTIFY_RECONCILIATION_CONSTANTS: { /** Interval between reconciliation runs (5 minutes) */ readonly RECONCILIATION_INTERVAL_MS: number; /** Maximum age of messages to consider for reconciliation (24 hours) */ readonly MAX_MESSAGE_AGE_MS: number; /** Maximum number of delivery attempts before marking as failed */ readonly MAX_DELIVERY_ATTEMPTS: 5; /** Delay before first reconciliation run after startup (30 seconds) */ readonly STARTUP_DELAY_MS: number; }; /** * Constants for the worktree janitor (`WorktreeJanitorService`). * * Agents create a git worktree per code task and never delete it. The janitor * removes a worktree once its work has landed (merged into origin's default * branch, or its PR is MERGED) and nothing is using it. See the service JSDoc * for the full list of removal rules. */ export declare const WORKTREE_JANITOR_CONSTANTS: { /** Env kill switch: `CREWLY_WORKTREE_JANITOR=0` (or `off`/`false`) disables the janitor. */ readonly ENV_VAR: "CREWLY_WORKTREE_JANITOR"; /** Values of {@link WORKTREE_JANITOR_CONSTANTS.ENV_VAR} that disable it (lower-cased). */ readonly DISABLED_VALUES: readonly string[]; /** Interval between automatic runs (30 minutes). */ readonly INTERVAL_MS: number; /** Delay before the first automatic run after startup (10 minutes). */ readonly FIRST_RUN_DELAY_MS: number; /** * A worktree in a known agent location (`/.claude/worktrees/`, * `/crewly-worktrees/`, or a `worktree-agent-*` branch) touched more * recently than this is kept (2 hours). */ readonly MIN_IDLE_MS: number; /** Any other linked worktree touched more recently than this is kept (24 hours). */ readonly MIN_IDLE_OTHER_MS: number; /** * A Claude Code session scratch dir (`/claude-///`) * touched more recently than this is kept (3 days). */ readonly SCRATCH_MIN_IDLE_MS: number; /** In low-disk mode every idle threshold is divided by this factor… */ readonly LOW_DISK_IDLE_DIVISOR: 2; /** …but never drops below this (2 hours). */ readonly LOW_DISK_MIN_IDLE_FLOOR_MS: number; /** Prefix of Claude Code's per-user temp root under the temp dir (`claude-`). */ readonly SCRATCH_ROOT_PREFIX: "claude-"; /** Extra temp dirs where the per-user Claude root may live besides os.tmpdir(). */ readonly SCRATCH_TMP_DIRS: readonly string[]; /** Session dir name pattern under `//` (a UUID). */ readonly SCRATCH_SESSION_DIR_PATTERN: RegExp; /** How deep under a session dir git repos are searched for. */ readonly SCRATCH_REPO_SEARCH_DEPTH: 3; /** How many levels of a session dir count for its "last touched" time. */ readonly SCRATCH_MTIME_DEPTH: 2; /** Directory names never descended into while searching for repos. */ readonly SEARCH_SKIP_DIRS: readonly string[]; /** How deep under each temp root linked worktrees are searched for (repo discovery). */ readonly DISCOVERY_DEPTH: 4; /** Cap on directories visited per temp root during repo discovery. */ readonly DISCOVERY_MAX_DIRS: 20000; /** Filesystem walks yield to the event loop (one setImmediate) after this many entries. */ readonly YIELD_EVERY_ENTRIES: 50; /** Interval of the low-disk check (10 minutes). */ readonly LOW_DISK_CHECK_INTERVAL_MS: number; /** Free space below this on the CREWLY_HOME volume triggers a low-disk pass (15 GB). */ readonly LOW_DISK_BYTES: number; /** Free space below this is urgent (5 GB). */ readonly CRITICAL_DISK_BYTES: number; /** A low-disk check does not start another pass within this time of the last one (30 minutes). */ readonly LOW_DISK_PASS_GAP_MS: number; /** Owner notice about low disk at most this often (24 hours). */ readonly LOW_DISK_NOTIFY_INTERVAL_MS: number; /** Urgent owner notice (below CRITICAL_DISK_BYTES) at most this often (6 hours). */ readonly CRITICAL_DISK_NOTIFY_INTERVAL_MS: number; /** How many of the biggest kept items the owner notice lists. */ readonly LOW_DISK_REPORT_ITEMS: 5; /** Timeout of one `du -sk` size probe (ms). */ readonly DU_TIMEOUT_MS: 60000; /** Runaway-file watcher (crewly#1078): how often agent scratch dirs and worktrees are scanned (1 minute). */ readonly RUNAWAY_CHECK_INTERVAL_MS: number; /** A file this big in an agent dir is reported to the owner (5 GB); if it is also still growing, its writer is stopped. */ readonly RUNAWAY_FILE_BYTES: number; /** A file that grew by this much per minute between two scans is a runaway whatever its size (1 GB/min). */ readonly RUNAWAY_GROWTH_BYTES_PER_MIN: number; /** Files smaller than this are never tracked between scans (cheap pre-filter, 256 MB). */ readonly RUNAWAY_TRACK_MIN_BYTES: number; /** Bytes of a stopped runaway file kept (the head, so the agent can see what it was writing, 1 MB). */ readonly RUNAWAY_KEEP_HEAD_BYTES: number; /** How deep under each watched dir files are looked for. */ readonly RUNAWAY_SCAN_DEPTH: 8; /** Cap on directory entries visited per scan. */ readonly RUNAWAY_SCAN_MAX_ENTRIES: 200000; /** Wait after SIGTERM before SIGKILL for the writer (ms). */ readonly RUNAWAY_KILL_GRACE_MS: 3000; /** Timeout of the `lsof` that finds a file's writers (ms). */ readonly RUNAWAY_LSOF_TIMEOUT_MS: 10000; /** One line added to every agent's start prompt. */ readonly PROMPT_HINT: "Disk hygiene: never let a command or script write an unbounded file. When you redirect or concatenate output (`> file`, `>> file`, `cat a b > all.md`), cap it (`| head -c 50M`, `| head -n 100000`) and never build a file from its own output. A file in your scratch dir or worktree that grows past a few GB is stopped by Crewly and the owner is alerted. Worktree hygiene: deploy only from a commit that is on main (merge first, never from a side branch); remove your worktree once its ticket is done. In a new worktree, do not run `npm install` when package-lock.json is unchanged: symlink `node_modules` (root and per-package) from the main checkout instead (`ln -s
/node_modules node_modules`). Crewly strips node_modules from worktrees untouched for 3 days and archives untouched ones as a patch after 14 days."; /** State file under CREWLY_HOME remembering when the owner was last told about low disk. */ readonly STATE_FILENAME: "disk-janitor-state.json"; /** File (in CREWLY_HOME) remembering which unmerged worktrees their lead was already told about. */ readonly UNMERGED_NOTICE_STATE_FILENAME: "unmerged-worktree-notices.json"; /** A kept worktree is only reported when its newest commit is older than this (24 hours) — fresh work is still in flight. */ readonly UNMERGED_NOTICE_MIN_AGE_MS: number; /** A worktree's lead is told about it at most this often (3 days). */ readonly UNMERGED_NOTICE_INTERVAL_MS: number; /** * Directory, relative to the repo's main worktree, where agent tools * (Claude Code subagents, Crewly agents) create per-task worktrees. */ readonly AGENT_WORKTREE_DIR: ".claude/worktrees"; /** * Directory name under the system temp dir (`/tmp`, `os.tmpdir()`) that * the developer role prompt tells agents to put worktrees in. */ readonly TMP_WORKTREE_DIR: "crewly-worktrees"; /** * Directory owned by the per-WorkItem worktree feature (#814), which does * its own WorkItem-aware cleanup. The janitor never touches it. */ readonly MANAGED_WORKTREE_DIR: ".crewly/worktrees"; /** * Branch-name prefixes that only agents create. A worktree elsewhere on * disk with such a branch counts as an agent location (2 h idle threshold * instead of 24 h). `worktree-agent-` is Claude Code's automatic subagent branch. */ readonly AGENT_BRANCH_PREFIXES: readonly string[]; /** * Untracked paths under a directory with one of these names (or a `*.log` * file) are regenerable output and do not make a worktree "dirty". Nested * packages often do not ignore their own `node_modules` / `.next`, so * `git status` lists them as untracked. */ readonly REGENERABLE_DIRS: readonly string[]; /** File suffixes treated as regenerable output (logs). */ readonly REGENERABLE_FILE_SUFFIXES: readonly string[]; /** * Directories deleted from kept (dirty / not-merged) worktrees by the strip * tier — reinstalled by `npm install` / rebuilt by the build. A directory * git tracks anything under is never deleted (a repo that commits `dist`). */ readonly RECLAIM_DIRS: readonly string[]; /** A kept worktree must be idle this long before its regenerable dirs are reclaimed under low disk (24 hours). */ readonly RECLAIM_MIN_IDLE_MS: number; /** How deep under a worktree reclaimable dirs are searched for (root = 0; `web/node_modules` = 1). */ readonly RECLAIM_SEARCH_DEPTH: 3; /** Work completion (task done / verified) schedules one tidy pass after this quiet period (2 minutes); bursts share it. */ readonly COMPLETION_TIDY_DELAY_MS: number; /** Idle needed by a completion-triggered pass: the agent said it is finished, so far less than the periodic threshold (10 minutes). */ readonly COMPLETION_MIN_IDLE_MS: number; /** Commits of the default branch searched for a squash-merge patch-equivalent of a branch. */ readonly SQUASH_SEARCH_MAX_COMMITS: 300; /** Branches that are never deleted, whatever the verdict. */ readonly PROTECTED_BRANCHES: readonly string[]; /** Timeout of one local git command (ms). */ readonly GIT_TIMEOUT_MS: 30000; /** Timeout of `git fetch` (ms). */ readonly FETCH_TIMEOUT_MS: 60000; /** Timeout of one `gh` call (ms). */ readonly GH_TIMEOUT_MS: 20000; /** Timeout of the process-cwd probe (`lsof`) (ms). */ readonly CWD_PROBE_TIMEOUT_MS: 20000; /** Max PRs `gh pr list --head ` returns per branch. */ readonly GH_PR_LIMIT: 20; /** Max bytes of command output kept (stdout/stderr each). */ readonly MAX_OUTPUT_BYTES: number; /** A kept worktree untouched (no file, index or commit newer) for this long loses its regenerable dirs (3 days). */ readonly STRIP_MIN_IDLE_MS: number; /** Under low disk, or once its WorkItem finished, the strip tier needs only this much idle (1 day). */ readonly STRIP_MIN_IDLE_LOW_DISK_MS: number; /** A kept worktree untouched for this long is archived as a patch and removed (14 days). */ readonly ARCHIVE_MIN_IDLE_MS: number; /** Directory under CREWLY_HOME holding archived worktree patches (`/-.patch`). */ readonly ARCHIVE_DIR: "worktree-archive"; /** Cap on one archive patch; untracked files that would push it past the cap are listed in the header instead (64 MB). */ readonly ARCHIVE_PATCH_MAX_BYTES: number; /** Cap on untracked files written into one archive patch. */ readonly ARCHIVE_MAX_UNTRACKED_FILES: 2000; /** Ref namespace that keeps the commits of an archived detached worktree alive (`refs/crewly-archive/-`). */ readonly ARCHIVE_REF_PREFIX: "refs/crewly-archive/"; /** How many levels below a worktree root count for its "last touched" time in the tier decision. */ readonly TIER_MTIME_DEPTH: 2; /** State file under CREWLY_HOME: per-worktree strip / archive / finished-work timestamps. */ readonly TIER_STATE_FILENAME: "worktree-tiers.json"; /** Finished-work records (task-end hook) older than this are dropped from the state file (30 days). */ readonly TIER_STATE_MAX_AGE_MS: number; /** * Every delete added in crewly#1354 (strip, archive, transcripts, tmp, logs) * is dry-run only until this long after the first real pass that saw it * (one day): the first pass logs what it would do, the next day it does it. */ readonly RETENTION_ARM_DELAY_MS: number; /** An agent transcript (and its sibling tool-results dir) untouched for this long is pruned (14 days). */ readonly TRANSCRIPT_MAX_AGE_MS: number; /** The transcript sweep runs at most this often (24 hours). */ readonly TRANSCRIPT_SWEEP_INTERVAL_MS: number; /** Bytes read from the head of a transcript to find Claude Code's `agentSetting` entry. */ readonly TRANSCRIPT_HEAD_BYTES: number; /** Handover files newer than this are scanned for conversation ids (30 days). */ readonly TRANSCRIPT_HANDOVER_SCAN_MAX_AGE_MS: number; /** Kinds that handover file names carry between the session name and the timestamp (`--.md`). */ readonly HANDOVER_KINDS: readonly string[]; /** Sub-directory of a Claude home that holds per-cwd transcript folders. */ readonly TRANSCRIPT_PROJECTS_DIR: "projects"; /** Transcript file extension. */ readonly TRANSCRIPT_EXT: ".jsonl"; /** Directory under CREWLY_HOME that agents and skills use as scratch space. */ readonly TMP_DIR: "tmp"; /** A top-level entry of CREWLY_HOME/tmp untouched for this long is removed (7 days). */ readonly TMP_MAX_AGE_MS: number; /** Sub-directories of CREWLY_HOME/tmp swept per child with their own age (ms); the directory itself stays. */ readonly TMP_CHILD_POLICIES: Readonly>; /** How many levels below a tmp entry count for its "last touched" time. */ readonly TMP_MTIME_DEPTH: 2; /** The tmp / logs sweep runs at most this often (6 hours). */ readonly TMP_SWEEP_INTERVAL_MS: number; /** Cap on CREWLY_HOME/logs/archive; the oldest archives go first (300 MB). */ readonly LOG_ARCHIVE_CAP_BYTES: number; /** Cap on CREWLY_HOME/logs/sessions; the oldest logs of sessions that are not live go first (100 MB). */ readonly LOG_SESSIONS_CAP_BYTES: number; /** A log written within this long is never deleted by the cap (1 hour). */ readonly LOG_CAP_MIN_AGE_MS: number; /** Low disk is free < max(LOW_DISK_BYTES, this fraction of the disk) (8 %). */ readonly LOW_DISK_FRACTION: 0.08; /** Critical is free < max(CRITICAL_DISK_BYTES, this fraction of the disk) (3 %). */ readonly CRITICAL_DISK_FRACTION: 0.03; }; /** * Constants for cron next-run computation (`getNextRunTime`). */ export declare const CRON_SCHEDULE_CONSTANTS: { /** * How far ahead a cron expression is searched for its next match. A * specific date such as `0 13 16 10 *` can be up to a year away; the old * 8-day scan fell through to a now+24h fallback for anything further out. */ readonly NEXT_RUN_HORIZON_DAYS: 366; /** * When skipping a day whose date fields do not match, jump to this many * minutes before the day's end and finish hour-by-hour. Larger than any * DST shift, so the jump can never land in (and skip the start of) the * next day. */ readonly DAY_END_SKIP_BUFFER_MINUTES: 120; /** * Fallback returned for an expression with no match inside the horizon * (e.g. `0 0 31 2 *`). Not schedule-aligned; logged at WARN. */ readonly IMPOSSIBLE_EXPRESSION_FALLBACK_MS: number; }; /** * Constants for the V3 TriggerEngine one-shot scheduler. */ export declare const TRIGGER_ENGINE_CONSTANTS: { /** * Largest delay a single Node timer can hold: 2^31 - 1 ms (~24.85 days). * Above this, `setTimeout` emits TimeoutOverflowWarning and silently * arms the timer for 1 ms — which is how a `--fire-at` three weeks out * used to fire the instant it was created. Longer waits are chained in * hops of at most this size. */ readonly MAX_TIMER_DELAY_MS: 2147483647; /** * A recurring trigger with `maxFires` that has this many fires or fewer * left gets one heads-up work item to its team lead (renew or ask the * owner). It is never renewed automatically. */ readonly EXPIRY_NOTICE_REMAINING_FIRES: 3; /** Most cron steps walked when projecting a trigger's final fire time. */ readonly PROJECTION_MAX_STEPS: 400; }; /** * Constants for Claude Code session resume via /resume slash command. * Used when restarting agents that were previously running before a backend restart. */ export declare const CLAUDE_RESUME_CONSTANTS: { /** Delay after sending /resume for the session picker to appear (ms) */ readonly SESSION_PICKER_DELAY_MS: 3000; /** Timeout for Claude to resume and return to prompt (ms) */ readonly RESUME_READY_TIMEOUT_MS: 30000; }; /** * Constants for Gemini CLI shell mode detection and escape. * Gemini CLI enters "shell mode" when it receives a `!` prefix or via `/shell`. * In shell mode, input is executed as shell commands instead of being sent to the model. * The prompt changes from `>` to `!` (or `$` in some versions). */ export declare const GEMINI_SHELL_MODE_CONSTANTS: { /** * Patterns that indicate Gemini CLI is in shell mode. * Matches `!` prompt char (with optional box-drawing border) when NOT followed * by typical chat prompt indicators like "Type your message". */ readonly SHELL_MODE_PROMPT_PATTERNS: readonly [RegExp, RegExp]; /** Delay after sending Escape to wait for mode switch (ms) */ readonly ESCAPE_DELAY_MS: 500; /** Maximum attempts to exit shell mode */ readonly MAX_ESCAPE_ATTEMPTS: 3; }; /** * Constants for Gemini CLI failure retry with exponential backoff. * When Gemini API errors (RESOURCE_EXHAUSTED, UNAVAILABLE, etc.) are detected, * the system waits and retries before declaring the agent dead. Gemini CLI * often recovers automatically from transient API errors. */ export declare const GEMINI_FAILURE_RETRY_CONSTANTS: { /** Maximum retry attempts before triggering exit/restart flow */ readonly MAX_RETRIES: 5; /** Initial backoff delay (ms) — doubles each retry */ readonly INITIAL_BACKOFF_MS: 1000; /** Maximum backoff delay cap (ms) */ readonly MAX_BACKOFF_MS: 30000; /** Backoff multiplier per retry */ readonly BACKOFF_MULTIPLIER: 2; /** Lines of terminal output to capture when checking for recovery */ readonly RECOVERY_CHECK_LINES: 50; }; /** * Named pattern for Gemini CLI stuck-connectivity detection (#128). * Matches "Trying to reach (Attempt N/M)" output, indicating * the CLI is in a retry loop and won't recover without intervention. * Used in both GEMINI_FAILURE_PATTERNS and the sendMessageWithRetry guard. */ export declare const GEMINI_STUCK_CONNECTIVITY_PATTERN: RegExp; /** * Gemini CLI failure patterns that indicate the CLI is stuck and needs recovery. * These patterns are distinct from exit patterns (which indicate the CLI has shut down * cleanly). Failure patterns match error states where the CLI may still be running * but is non-functional and requires a restart. * * Used by GeminiRuntimeService and RuntimeExitMonitorService. * * Note: Explicitly typed as `RegExp[]` instead of using `as const` because * `as const` produces a readonly tuple of regex literals, which complicates * usage with array methods like `.some()` and `.find()`. */ export declare const GEMINI_FAILURE_PATTERNS: RegExp[]; /** * Gemini CLI deliberation loop detection pattern (#188). * Matches repeated "Wait, I'll..." / "Actually, I'll..." planning text * that indicates the agent is stuck in an infinite deliberation cycle * without executing tool calls. A single occurrence is not a loop — * the RuntimeExitMonitorService counts occurrences in the output buffer * and only triggers recovery when the threshold is exceeded. */ export declare const GEMINI_DELIBERATION_PATTERN: RegExp; /** Number of deliberation pattern matches in the terminal buffer before * declaring the agent stuck in a deliberation loop (#188). */ export declare const GEMINI_DELIBERATION_THRESHOLD = 5; /** * #251: Gemini tool-check loop detection pattern. * Matches repeated "Ready. Final response." or "Wait, I will check if I should use mcp_" * cycling that indicates the agent is stuck checking Chrome DevTools MCP tools * without making progress. Time-windowed: triggers when the pattern appears * GEMINI_TOOL_CHECK_LOOP_THRESHOLD times within GEMINI_TOOL_CHECK_LOOP_WINDOW_MS. */ export declare const GEMINI_TOOL_CHECK_LOOP_PATTERN: RegExp; /** #251: Number of tool-check loop pattern matches within the time window * before declaring the agent stuck. */ export declare const GEMINI_TOOL_CHECK_LOOP_THRESHOLD = 5; /** #251: Time window in milliseconds for counting tool-check loop occurrences. * If THRESHOLD matches occur within this window, the agent is considered stuck. */ export declare const GEMINI_TOOL_CHECK_LOOP_WINDOW_MS: number; /** * Gemini update/upgrade markers that should trigger forced recovery. * These indicate the CLI interrupted the current request for self-update. */ export declare const GEMINI_FORCE_RESTART_PATTERNS: RegExp[]; /** * Claude Code start-up states that cannot resolve without the user, so the * agent start must fail fast with an actionable error instead of waiting out * the readiness timeout and every fallback (about 5 minutes before this). */ export declare const CLAUDE_STARTUP_CONSTANTS: { /** Error code on RuntimeStartupBlockedError and on the session result. */ readonly BLOCKED_ERROR_CODE: "RUNTIME_STARTUP_BLOCKED"; /** Claude refuses --dangerously-skip-permissions as root; this is its message. */ readonly ROOT_REFUSAL_MARKER: "cannot be used with root/sudo privileges"; /** * Claude honours IS_SANDBOX=1 as "running in a sandbox" and then allows the * skip-permissions flag as root (containers). Only then is root allowed. */ readonly SANDBOX_ENV: "IS_SANDBOX"; /** * Claude Code's first-run onboarding (theme picker), shown when `claude` * has never been run on this machine. It precedes sign-in and nothing in * Crewly can complete it. */ readonly FIRST_RUN_MARKERS: readonly string[]; readonly MESSAGES: { readonly ROOT: "Crewly agents cannot run as root: Claude Code refuses --dangerously-skip-permissions under root/sudo. Run Crewly as a normal (non-root) user, then start the team again."; readonly FIRST_RUN: "Claude Code has not been set up on this machine yet. Run `claude` once in a terminal, choose a theme and log in, then start the team again."; }; }; /** * Start-up states shared by every CLI runtime that cannot resolve without the * user, so the agent start must fail fast (see {@link CLAUDE_STARTUP_CONSTANTS} * for the Claude-specific ones and the shared error code). */ export declare const RUNTIME_STARTUP_CONSTANTS: { /** * The command each CLI runtime launches. When the shell reports it as * missing, no retry can help until the user installs it. */ readonly CLI_BINARIES: Readonly>>; /** * HTTP status of POST /api/teams/:id/start when no member could start * (424 Failed Dependency: the runtime needs the user first). The dashboard * shows `error` for any non-2xx answer. */ readonly NONE_STARTED_HTTP_STATUS: 424; /** Human names used in start-up errors. */ readonly CLI_LABELS: Readonly>>; readonly MESSAGES: { /** Gemini started without a key or a Google login and asks how to sign in. */ readonly GEMINI_AUTH_REQUIRED: "Gemini CLI is not signed in: it is asking how to authenticate. Add a Gemini API key in Crewly Settings (or set GEMINI_API_KEY), or run `gemini` once in a terminal and choose \"Sign in with Google\", then start the team again."; /** * Gemini sees GEMINI_API_KEY in the agent's shell, but no sign-in method is * saved and Crewly's own environment/settings has no key to pre-select it. */ readonly GEMINI_AUTH_KEY_NOT_SELECTED: "Gemini CLI found a GEMINI_API_KEY in the agent shell, but no sign-in method is selected, so it is waiting for an answer. Add the key in Crewly Settings (Crewly then selects \"Use Gemini API Key\" for you), or run `gemini` once in a terminal and choose \"Use Gemini API Key\", then start the team again."; /** Appended after " (``) is not installed" for a missing CLI. */ readonly RUNTIME_NOT_INSTALLED_HINT: "Install it (see `crewly doctor`), or pick an installed runtime in Crewly Settings, then start again."; }; }; /** * Claude Code fatal error patterns that indicate the CLI is stuck in an * unrecoverable state. Unlike transient API errors, these require an * immediate restart — no retry will resolve them. * * Example: When conversation history gets compacted and thinking blocks * are modified, the Claude API permanently rejects all subsequent requests * with a 400 error. The only recovery is to kill and restart the session. * * Used by RuntimeExitMonitorService to detect stuck Claude Code sessions. */ export declare const CLAUDE_FATAL_PATTERNS: RegExp[]; /** * Gemini CLI ready-state patterns used for recovery detection. * When any of these strings appear in terminal output, the CLI is * considered operational and ready to accept input. * Shared by RuntimeExitMonitorService and GeminiRuntimeService. */ export declare const GEMINI_READY_PATTERNS: readonly string[]; /** * Constants for detecting Gemini CLI error-overlay state (#130). * Used to identify and dismiss MCP connection error overlays * before message delivery. * * IMPORTANT: Detection must be narrow to avoid false positives (#130 follow-up). * Previous issues: STATUS_AREA_LINES=5 and generic /\d+ errors?/ matched normal * content (build output, test results), causing spurious F12 keypresses that * typed literal "F12" text into the terminal (because F12 was missing from KEY_CODES). * * Current approach: Only check the last 2 lines (actual status bar), and require * Gemini-specific patterns ("F12 for deta" or "✖" + "error") to avoid matching * generic error text from build tools, test runners, etc. */ export declare const GEMINI_ERROR_STATE_CONSTANTS: { /** Unicode marker for error state in Gemini CLI status bar */ readonly ERROR_MARKER: "✖"; /** * Regex matching Gemini CLI's specific error format in the status bar. * Requires "F12" nearby to distinguish from generic "N errors" text in * build output or test results. Matches: "3 errors (F12 for details)" */ readonly ERROR_COUNT_PATTERN: RegExp; /** * Number of terminal lines from the bottom to consider as "status area". * Gemini CLI's status bar is 1-2 lines; using 3 for safety margin. * Previously 5, which caught content lines and caused false positives. */ readonly STATUS_AREA_LINES: 3; }; /** * Constants for runtime exit detection monitoring. * Used by RuntimeExitMonitorService to detect when an agent CLI exits. */ export declare const RUNTIME_EXIT_CONSTANTS: { /** Maximum rolling buffer size for terminal output (bytes) */ readonly MAX_BUFFER_SIZE: 8192; /** Debounce delay after exit pattern match before confirming (ms) */ readonly CONFIRMATION_DELAY_MS: 500; /** * Grace period after monitoring start to ignore false positives (ms). * Set to 0 because exit patterns (e.g. "Agent powering down", * "Interaction Summary") are specific enough to not appear during * normal runtime initialization output. */ readonly STARTUP_GRACE_PERIOD_MS: 0; /** * Grace period for API activity before confirming a runtime exit (ms). * If the agent made an API call within this window, the exit detection * is treated as a false positive and skipped. This prevents false * restarts when agents are actively calling skills/APIs but happen to * produce PTY output that matches exit patterns. */ readonly API_ACTIVITY_GRACE_PERIOD_MS: 120000; /** * Interval for polling child process liveness (ms). * Used as a fallback when pattern-based exit detection misses an exit. * Checks if the runtime process (e.g. claude) is still alive via pgrep. */ readonly PROCESS_POLL_INTERVAL_MS: 10000; /** * Grace period after monitoring starts before process polling begins (ms). * Prevents false positives during startup when the CLI process hasn't * spawned yet. */ readonly PROCESS_POLL_GRACE_PERIOD_MS: 30000; /** * An exit with no recognised cause this soon after monitoring started is * recorded as `startup_exit`, not `idle_exit` (#791): no runtime idles * out in its first minute, so calling it idle hides the real cause. */ readonly EARLY_EXIT_WINDOW_MS: 60000; /** Characters of cleaned terminal output logged with an unexplained exit (#791) */ readonly EXIT_DIAGNOSTIC_TAIL_CHARS: 1500; /** * #989: this many `startup_exit`s of one runtime within * STARTUP_EXIT_WINDOW_MS means it keeps dying at start; the owner is told. */ readonly STARTUP_EXIT_ALERT_THRESHOLD: 3; /** Window over which repeated `startup_exit`s of one runtime are counted (30 minutes). */ readonly STARTUP_EXIT_WINDOW_MS: number; /** The owner is told about one runtime's startup exits at most this often (24 hours). */ readonly STARTUP_EXIT_NOTICE_COOLDOWN_MS: number; }; /** * Constants for context window monitoring and auto-recovery. * Used by ContextWindowMonitorService to detect when an agent's Claude Code * session is running low on context and trigger proactive warnings or recovery. */ /** * Claude transcript sync — reads Claude Code's own session JSONL files to get * exact per-turn token usage for `claude-code` agents. */ /** * Live browser view — per-agent browser sessions and the frames that make * them watchable. * * Frames are owner-surface only: in memory, never persisted, never attached * to a chat message or posted to any multi-party surface. */ /** * How much of a conversation an agent is shown when a message is dispatched * to it. * * Until now the answer was none: the prompt carried the one message and * nothing else, so an agent @-mentioned into a channel where a colleague had * just posted ten messages had no idea any of them existed. It also had no * way to check its own account of what was said, which is how one of them * ended up citing an instruction that did not exist. * * The caps are small on purpose. This is paid on *every* dispatch, and a * busy channel would otherwise add thousands of tokens to every wake-up — * the same cost shape that had one agent dragging 726k tokens through each * turn. */ /** * When the orchestrator's conversation is too big to carry on. * * On a 1M-window model Claude Code only compacts near the window, so an orc * that is resumed across every restart re-reads its whole history on every * turn — 612k tokens per turn on one machine (2026-09-23), growing ~64k a day, * with 0.1% of each turn new. At a restart (never mid-conversation) a history * above the threshold is closed and the orc starts fresh with a handover file * holding the tail of the old one. Its real state (tasks, teams, OKRs, wiki) * lives in Crewly and is read back at startup anyway. */ /** Remote desktop — the owner watching and driving this machine from the portal / phone. */ export declare const DESKTOP_REMOTE_CONSTANTS: { /** Under CREWLY_HOME; `{ enabled }`, switchable only on the machine */ readonly SETTINGS_FILE: "desktop-remote.json"; /** Widest frame sent over the relay, in pixels */ readonly FRAME_MAX_WIDTH: 1200; /** sips JPEG quality (0-100) */ readonly JPEG_QUALITY: 60; /** Longest text typed in one input */ readonly MAX_TYPE_CHARS: 2000; }; export declare const ORC_CONVERSATION_CONSTANTS: { /** Start fresh at a restart when the last turn carried at least this many tokens. Env: CREWLY_ORC_FRESH_CONTEXT_TOKENS */ readonly FRESH_CONTEXT_TOKENS: 300000; /** How much of the old transcript's end is read to find the last turn's size */ readonly TAIL_BYTES: number; /** * Messages kept in the handover file, newest last. Raised 40 → 60 with * the per-message and total caps (crewly#1344): a 16k-character tail of * plain words lost the tool traffic entirely, and agents that were reset * several times a day "remembered worse" than plain Claude Code. */ readonly HANDOVER_MESSAGES: 60; /** Characters kept per handover message */ readonly HANDOVER_MESSAGE_CHARS: 2000; /** Total cap on the handover file's body (~40k characters, ~10k tokens) */ readonly HANDOVER_MAX_CHARS: 40000; /** * Characters kept of each tool call / tool result in a handover: the tool * name plus the start of its input or output, one line each, so the new * conversation knows WHAT was done (files read, commands run), not just * what was said about it. Images stay a described placeholder. */ readonly HANDOVER_TOOL_RESULT_CHARS: 200; /** Directory under CREWLY_HOME for handover files */ readonly HANDOVER_DIR: "handover"; /** A plain restart hands over the previous conversation only when its transcript was written within this window */ readonly RESTART_HANDOVER_MAX_AGE_MS: number; /** Where the last conversation id of a stopped session is remembered (under the state dir) */ readonly LAST_CONVERSATIONS_FILE: "last-conversations.json"; /** How much of a transcript's start is read to check it belongs to a session (cwd-wide fallback) */ readonly OWNERSHIP_HEAD_BYTES: number; /** Most of an earlier handover carried into a new one when the source conversation is unreadable */ readonly CHAINED_HANDOVER_MAX_CHARS: 40000; }; /** Pointer header (open work, suggested skills) written at the top of a handover file. */ export declare const HANDOVER_POINTERS_CONSTANTS: { /** Task pool file under CREWLY_HOME (dir, file) */ readonly POOL_DIR: "task-pool"; readonly POOL_FILE: "pool.json"; /** WorkItem statuses that count as still open for the agent */ readonly OPEN_STATUSES: readonly string[]; readonly MAX_OPEN_WORK: 15; readonly MAX_LINKS: 10; readonly TITLE_CHARS: 80; /** Markers around the header, so a chained handover can strip it */ readonly START_MARK: ""; readonly END_MARK: ""; }; /** * Work a restart cut off inside an agent's conversation (crewly#1361): a * Claude Code transcript whose `tool_use` never received its `tool_result` * before the cutoff. Sub-agents (Task / Agent tool) and background or * long-running shell commands die with the process; the handover says so * instead of carrying the agent's last "still in progress" narration. */ export declare const HANDOVER_CONSTANTS: { /** Heading of the section listing the interrupted work */ readonly INTERRUPTED_HEADING: "## Interrupted by the restart"; /** At most this many interrupted calls are listed */ readonly INTERRUPTED_MAX_ITEMS: 5; /** Characters kept of a sub-agent's prompt / a command */ readonly INTERRUPTED_TEXT_MAX_CHARS: 200; /** Tool names that start a sub-agent inside the conversation */ readonly SUBAGENT_TOOLS: readonly string[]; /** Tool names that run shell commands */ readonly SHELL_TOOLS: readonly string[]; /** * A shell command that looks long-running (a render, encode, build or a * command sent to the background) even without `run_in_background` */ readonly LONG_COMMAND_PATTERN: RegExp; /** Bullet prefixes of the section (also how the pointers header reads it back) */ readonly SUBAGENT_LINE_PREFIX: "- A sub-agent was running: "; readonly COMMAND_LINE_PREFIX: "- A background command was running: "; readonly SUBAGENT_LINE_SUFFIX: ". It did not finish and is not running now. Restart it or report that it was lost."; readonly COMMAND_LINE_SUFFIX: ". It is not running now."; }; /** * Fresh conversation per new task for Claude Code team members (never the * orchestrator): before a work item with a different root than the last one * is delivered, the old conversation is saved (handover file + memory) and * `/clear`ed, so a small new task does not re-read hundreds of thousands of * tokens of unrelated history on every turn. */ export declare const FRESH_TASK_CONVERSATION_CONSTANTS: { /** Kill-switch env var; `0` / `false` / `off` disables the feature */ readonly ENV_TOGGLE: "CREWLY_FRESH_TASK_CONVERSATION"; /** Under CREWLY_HOME: `{ [session]: lastDeliveredRootId }` */ readonly STATE_FILE: "fresh-task-conversation.json"; /** * Id suffixes that mark follow-ups of the same task (retries, verifies, * reviews). Deliberately excludes give-up recovery's `:giveup:N` (#841, * #843): a give-up retry is meant to be a materially DIFFERENT approach, * and its WorkItem description already carries the full attempt log, so * nothing is lost by treating it as a new root — the agent starts that * retry with a fresh conversation instead of one anchored on the * reasoning that just failed. See give-up-recovery.service.ts buildRetry. */ readonly ROOT_SUFFIX_MARKERS: readonly [":retry:", ":verify:", ":review:"]; /** The Claude Code command that starts a new conversation */ readonly CLEAR_COMMAND: "/clear"; /** Pause between Escape and the command (same as context compaction) */ readonly ESCAPE_DELAY_MS: 200; /** Wait after `/clear` for the prompt to come back before the task is written */ readonly POST_CLEAR_READY_MS: 2000; /** PTY must have been quiet at least this long for the agent to count as idle */ readonly MIN_QUIET_MS: 8000; /** Skip clearing when someone else delivered a message to the session this recently */ readonly RECENT_DELIVERY_MS: 30000; /** * A task started longer ago than this is work in progress: a re-delivery * of it (auto-claim after the lead's direct hand-over) must not clear. * Auto-claim itself starts the item moments before delivering. */ readonly ALREADY_STARTED_MS: 60000; /** How long a terminal write/deliver waits for an in-progress clear */ readonly WAIT_IF_CLEARING_MAX_MS: 5000; /** How long to look for the new transcript Claude Code starts after `/clear` */ readonly NEW_SESSION_DETECT_MS: 30000; /** Poll interval while looking for the new transcript */ readonly NEW_SESSION_POLL_MS: 1000; /** Cap on the handover text stored through memory (mirrored into the wiki) */ readonly MEMORY_MAX_CHARS: 4000; /** * Members start fresh at launch when their last turn carried this many * tokens. Env: CREWLY_MEMBER_FRESH_CONTEXT_TOKENS. * * Above Claude Code's own auto-compact window (CLAUDE_CODE_CONTEXT, 350k) * on purpose (crewly#1344): Claude compacts its conversation itself and * keeps a far better summary than a handover tail, so Crewly's reset is * only a safety net for a conversation that compaction did not bring * down. At the old 150k the audit of 2026-10-16 counted 58 fresh starts * in 48 h, every one of them replacing a live conversation with 16k * characters of tail. */ readonly MEMBER_FRESH_CONTEXT_TOKENS: 400000; /** * The smallest conversation a new-task `/clear` may close (last-turn * tokens): the same as {@link MEMBER_FRESH_CONTEXT_TOKENS}, read through * `memberFreshContextTokens()` so the env override applies to both. A * conversation under it is cheap to keep and is what the agent remembers * by; 137 clears in 48 h, some at 56k, were the cost of not having a floor. */ readonly MIN_CLEAR_CONTEXT_TOKENS: 400000; /** * A member the owner spoke to this recently (an owner thread the sentinel * watches: the owner's last message, or an open promise) is never cleared * for a new task — the clear would wipe the conversation the owner is in. */ readonly OWNER_CONVERSATION_ACTIVE_MS: number; /** * Idle-boundary context cap: a Claude Code member (never the orchestrator) * whose last turn carried more than this many tokens is saved (handover + * wiki) and cleared between turns, then re-oriented on its WorkItem. One * long task can otherwise run 1000+ turns at 650k–965k context. Above the * fresh-start threshold and Claude Code's 350k auto-compact (crewly#1344), * so compaction runs first and this only catches a conversation that kept * growing through it. */ readonly MEMBER_CONTEXT_CAP_TOKENS: 450000; /** Env override for {@link MEMBER_CONTEXT_CAP_TOKENS}; `0` disables the cap */ readonly MEMBER_CONTEXT_CAP_ENV: "CREWLY_MEMBER_CONTEXT_CAP_TOKENS"; /** At most one context-cap clear per session in this window */ readonly CONTEXT_CAP_MIN_INTERVAL_MS: number; /** * The PTY must have been quiet this long before a context-cap clear — * longer than {@link MIN_QUIET_MS} because nothing is waiting on it and a * turn that merely paused must not be cut. */ readonly CONTEXT_CAP_MIN_QUIET_MS: 30000; /** How often the context-cap sweep checks members */ readonly CONTEXT_CAP_SWEEP_MS: 60000; /** Tag on the re-orientation line written after a context-cap clear */ readonly CONTEXT_CAP_TAG: "[CREWLY-CONTEXT-CAP]"; }; /** * Cooperation between a direct task hand-over (team-leader delegate-task: * `/task-pool/add`, then `/terminal/:s/deliver` with the full brief) and the * WorkItem dispatcher's `workitem:queued` push, so a task reaches its agent * once and the fresh-conversation clear happens once, before that delivery. */ export declare const DIRECT_DELIVERY_CONSTANTS: { /** WorkItem metadata flag set by callers that deliver the task text themselves */ readonly METADATA_FLAG: "directDelivery"; /** * How long the dispatcher holds off a `workitem:queued` push for a * direct-delivery WorkItem. Covers the delegate-task ladder: deliver * (15 s ready wait) → force → auto-start + 10 s + deliver (30 s). After * this, if nobody delivered it, the dispatcher pushes its brief. */ readonly GRACE_MS: 90000; }; /** * Codes the member-start endpoint answers with when a start gate refuses a * wake (`POST /api/teams/:teamId/members/:memberId/start`). */ export declare const AGENT_WAKE_ERROR_CODES: { /** Wake gate: no queued/blocked WorkItem for the member */ readonly NO_POOL_WORK: "wake_gate_no_pool_work"; /** Commitment-approval gate: cold launch of a dormant team without the owner's OK */ readonly OWNER_APPROVAL_REQUIRED: "commitment_requires_owner_approval"; }; /** * Who leads a team — one rule for the whole harness * (specs/2026-09-30-team-lead-rule.md, `utils/team.utils.ts`): the team's * explicit `leaderIds` (or the deprecated `leaderId`) when set, otherwise * the members whose role is one of {@link TEAM_LEAD_CONSTANTS.LEAD_ROLES}. */ export declare const TEAM_LEAD_CONSTANTS: { /** Roles that make a member a team lead when the team names no lead explicitly */ readonly LEAD_ROLES: readonly ["team-leader", "tech-lead"]; /** `POST /api/teams/:id/lead` modes: replace the leads, or add one more */ readonly SET_LEAD_MODES: readonly ["set", "add"]; }; /** * Default model for Claude Code team members that have no `modelId`: members * with a reviewer above them run on Sonnet, leads (and anyone without a * reviewer) keep Claude Code's own default (Opus). Never the orchestrator. */ export declare const MEMBER_MODEL_DEFAULT_CONSTANTS: { /** Model passed to reviewed members when nothing else is set */ readonly DEFAULT_REVIEWED_MEMBER_MODEL: "sonnet"; /** Env override for that model; `''` or `off` disables the default entirely */ readonly ENV_OVERRIDE: "CREWLY_MEMBER_DEFAULT_MODEL"; /** Roles that lead a team and therefore keep the runtime default (same list as {@link TEAM_LEAD_CONSTANTS.LEAD_ROLES}) */ readonly LEAD_ROLES: readonly ["team-leader", "tech-lead"]; /** Rejections of the same task after which the reviewer is told about the upgrade option */ readonly UPGRADE_HINT_AFTER_REJECTIONS: 2; }; /** * Model tiers (crewly#1173, specs/2026-10-08-model-tiers.md): strong / mid / * weak, mapped to a concrete model per runtime. A team may override the map * (`Team.tierModels`); an explicit `TeamMember.modelId` always wins. * * A runtime with no model for `weak` uses its `mid` model (still cheaper than * the strong default). Any other unmapped tier gets no model flag: the * runtime's own default. `opencode-cli` and `crewly-agent` are unmapped (their * model ids are provider/model pairs the owner picks). */ export declare const MODEL_TIER_CONSTANTS: { /** Tiers, strongest first */ readonly TIERS: readonly ["strong", "mid", "weak"]; /** Global tier → model map per runtime (names each harness accepts at launch) */ readonly DEFAULT_TIER_MODELS: Record>>; /** Store of review drafts / applied changes, under CREWLY_HOME */ readonly STORE_FILE: "model-tier-reviews.json"; /** Decision kind of the owner card */ readonly DECISION_KIND: "model_tier_change"; /** Days between automatic reviews while "Optimize usage" is on */ readonly REVIEW_INTERVAL_DAYS: 7; /** Usage window the report covers (days) */ readonly REPORT_WINDOW_DAYS: 7; /** How often the service checks for due reviews / guards */ readonly TICK_MS: number; /** A draft nobody submitted is sent to the owner after this long */ readonly DRAFT_AUTO_SUBMIT_MS: number; /** How long a tier card waits for the owner (default = keep as is) */ readonly DECISION_DEADLINE_MS: number; /** Who changes tiers while "Optimize usage" is on (crewly#1173 phase 2) */ readonly CHANGE_MODES: readonly ["approve", "auto"]; /** Default change mode: the owner approves every change (phase 1 behaviour) */ readonly DEFAULT_CHANGE_MODE: "approve"; /** How long the Undo button of an auto-mode notice stays live (then the notice closes; nothing changes) */ readonly NOTICE_DEADLINE_MS: number; /** Reason recorded for the planned relaunch of an idle member whose model changed */ readonly RELAUNCH_REASON: "model_tier_change"; /** Work-item statuses that mean a member holds work in progress (no restart then) */ readonly BUSY_WORK_STATUSES: readonly ["running", "accepted"]; /** Work titles listed per member in the report */ readonly REPORT_MAX_TITLES: 5; /** Max characters of a reason / routing rule */ readonly MAX_REASON_CHARS: 300; /** Max routing rules per proposal */ readonly MAX_ROUTING_RULES: 10; /** Quality guard: settled work items needed after a lowered tier before judging */ readonly GUARD_MIN_ITEMS: 5; /** Quality guard: settled items before the change used as the baseline */ readonly GUARD_BASELINE_ITEMS: 10; /** Quality guard: send-back rate rise (0..1) over the baseline that counts as worse */ readonly GUARD_WORSE_MARGIN: 0.2; /** Quality guard: at least this many sent-back items after the change */ readonly GUARD_MIN_BAD: 2; /** Quality guard: stop watching a change after this many days */ readonly GUARD_MAX_DAYS: 30; }; export declare const CHAT_CONTEXT_CONSTANTS: { /** Whether preceding messages are included at all */ readonly ENABLED: true; /** * Messages to include from the same thread. * * A thread is the cheap, high-value case: everything in it is by * construction about the same subject, so every line earns its tokens. */ readonly THREAD_MAX: 12; /** * Messages to include for a top-level channel message. * * Lower than the thread cap: a channel's recent traffic is often several * unrelated conversations, so most of it is noise to the agent being * asked. */ readonly CHANNEL_MAX: 8; /** Characters kept per message before truncation */ readonly PER_MESSAGE_CHARS: 300; /** * Ignore anything older than this. Yesterday's argument is not context * for today's question, and including it invites an agent to answer the * wrong one. */ readonly MAX_AGE_MS: number; /** * A top-level owner message in a room or channel (not a thread, not a DM) * is often a follow-up to yesterday ("how is the video plan going?"), so it * gets a longer look back than other top-level messages: the last * {@link TOP_LEVEL_OWNER_MAX} messages or the last * {@link TOP_LEVEL_OWNER_MAX_AGE_MS}, whichever is smaller, within * {@link TOP_LEVEL_OWNER_MAX_CHARS} characters (oldest dropped first). */ readonly TOP_LEVEL_OWNER_MAX: 15; /** See {@link TOP_LEVEL_OWNER_MAX} */ readonly TOP_LEVEL_OWNER_MAX_AGE_MS: number; /** See {@link TOP_LEVEL_OWNER_MAX} */ readonly TOP_LEVEL_OWNER_MAX_CHARS: 3000; }; /** * Browser outbound guard (2026-10-03: a LinkedIn reply posted as the owner * without approval). See services/browser/browser-outbound-guard.ts. */ export declare const BROWSER_OUTBOUND_GUARD: { /** * Sites where an agent's post, comment, reply or message reaches other * people as the owner. Subdomains match too. */ readonly SOCIAL_MESSAGING_HOSTS: readonly string[]; /** * Words for controls that publish or send something that cannot be taken * back, matched on selectors/labels/scripts split at punctuation (`-`, * `_`, `.`, `#`, quotes, brackets) but NOT at camelCase — a code * identifier like `postCount` or `commentsList` is not a control. * Bare "comment"/"tweet"/"reply" are not here: on X and LinkedIn they * name the things being read (a tweet, a comment item). Their submit * buttons are matched as such (`tweetButton`, `comment … submit`), and a * control whose whole label is Reply/Comment/Post is matched by label. */ readonly OUTBOUND_WORDS: ReadonlyArray; /** * Whole control labels that submit (lower case): a button whose visible * text, aria-label or quoted label is exactly one of these. "Reply" and * "Comment" are LinkedIn's submit buttons; "Post"/"Tweet" X's. */ readonly SUBMIT_LABELS: readonly string[]; /** Extra whole labels that only count on social and messaging sites */ readonly SOCIAL_ONLY_LABELS: readonly string[]; /** * What makes a page script act rather than read: a click in any form * (`el.click()`, `el['click']()`, `HTMLElement.prototype.click.call(el)`), * submitting, firing events or editing content. Writing requests are * detected separately (browser-outbound-guard `scriptWritesRequest`). */ readonly SCRIPT_ACTS: RegExp; /** A script that submits a form */ readonly SCRIPT_SUBMITS_FORM: RegExp; /** A script that writes text into the page (a draft) */ readonly SCRIPT_EDITS_CONTENT: RegExp; /** Longest draft text shown on an approval card */ readonly CARD_DRAFT_MAX_CHARS: 500; /** Fields whose typed text is never shown on a card (matched on descriptor words) */ readonly SECRET_FIELD: RegExp; }; /** * Owner approval of held browser actions (irreversible clicks): Slack * decision cards, persistence across restarts, and the answer deadline. */ export declare const BROWSER_APPROVAL_CONSTANTS: { /** Pending held actions under CREWLY_HOME */ readonly STORE_FILENAME: "browser-pending-actions.json"; /** The owner has this long to answer; then the answer is No (ms) */ readonly DEADLINE_MS: number; /** After a restart, how long a held action may wait for its tab to come back (ms) */ readonly REBIND_GRACE_MS: number; /** Retry / expiry evaluation cadence (ms) */ readonly TICK_MS: number; /** A card that could not be created is retried at most this often (ms) */ readonly CARD_RETRY_MS: number; /** Settled records are kept this long, then pruned (ms) */ readonly KEEP_SETTLED_MS: number; /** Option labels of the card */ readonly APPROVE_LABEL: "Let it"; readonly REJECT_LABEL: "No"; /** What a held agent is told to say — the one place the owner answers is the card */ readonly AGENT_SAYS: "I've asked the owner with a card in this thread; wait for their answer."; }; export declare const BROWSER_SESSION_CONSTANTS: { /** How long a writing action waits to read its tab's current URL (ms) */ readonly TAB_URL_LOOKUP_TIMEOUT_MS: 3000; /** How often the capture loop wakes up (ms) */ readonly TICK_INTERVAL_MS: 1500; /** * A session counts as watched for this long after someone fetched its * frame. Interest expires on its own, so there is no subscribe call to * leak and a closed tab stops costing captures within seconds. */ readonly WATCH_WINDOW_MS: 5000; /** Minimum gap between frames while someone is watching (ms) */ readonly WATCHED_FRAME_INTERVAL_MS: 1500; /** * Minimum gap between frames when nobody is watching (ms). Only refreshed * at all when the agent did something — a picture nobody looks at is pure * cost on the agent's browser. */ readonly IDLE_FRAME_INTERVAL_MS: 10000; /** * Frame encoding. A PNG viewport capture on a Retina display runs to * several hundred kilobytes; half-scale JPEG lands around 30–80 KB, which * is what makes once-a-second viable over a relay. */ readonly FRAME_FORMAT: "jpeg"; /** JPEG quality for frames (0-100) */ readonly FRAME_QUALITY: 55; /** Downscale factor for frames */ readonly FRAME_SCALE: 0.5; /** How long a finished session stays listed before being pruned (ms) */ readonly RETAIN_FINISHED_MS: number; }; /** * Owner input into a browser session the owner has taken over * (`POST /api/browser/sessions/:id/input`). The owner is usually on a phone, * tapping a picture of the page, so every limit here is about keeping one tap * or one "Send" a single bounded action. */ export declare const BROWSER_OWNER_INPUT_CONSTANTS: { /** Longest text one `type` may carry (characters) */ readonly MAX_TEXT_LENGTH: 2000; /** Longest URL one `navigate` may carry (characters) */ readonly MAX_URL_LENGTH: 4096; /** Largest single scroll step either way (CSS px) */ readonly MAX_SCROLL_PX: 5000; /** * How long a measured viewport is trusted before a tap measures again * (ms). Each measurement is a round trip to the extension, which over the * relay is the slowest part of a tap. */ readonly VIEWPORT_CACHE_MS: 15000; /** Keys the owner can press from the control bar */ readonly KEYS: readonly ["Enter", "Tab", "Backspace", "Escape", "ArrowUp", "ArrowDown"]; /** How far an arrow key scrolls when focus is not in a text field (CSS px) */ readonly ARROW_SCROLL_PX: 40; /** Command timeout for one owner action (ms) */ readonly COMMAND_TIMEOUT_MS: 15000; /** * Pause after a tap, key or Back before the fresh frame is captured (ms), * so the picture sent back shows what the action did rather than the * instant before it. Navigation needs none: it waits for the page load. */ readonly SETTLE_BEFORE_FRAME_MS: 350; /** * Page-quiet wait before a tap is clicked (ms). The extension's click * waits for the page to go idle (default up to 2 s) because an agent * clicks blind; the owner is looking at the page, so a tap should land * when they tapped. */ readonly TAP_IDLE_QUIET_MS: 50; /** Longest the extension may wait for that quiet before clicking (ms) */ readonly TAP_IDLE_MAX_WAIT_MS: 300; }; export declare const CLAUDE_TRANSCRIPT_SYNC_CONSTANTS: { /** How often to read the unread tail of each agent's transcript (ms) */ readonly SYNC_INTERVAL_MS: 60000; /** Cursor file name, kept under CREWLY_HOME */ readonly CURSOR_FILE: "claude-transcript-cursors.json"; /** * How many recent assistant message ids to remember per session for * exactly-once counting. Only the newest turns can be rewritten in place, * so a small window is enough and keeps the cursor file small. */ readonly MAX_DEDUPE_IDS: 200; /** * Most bytes one pass reads from a transcript. A 745 MB transcript read * whole failed every minute with "Cannot create a string longer than * 0x1fffffe8 characters" and took the other sessions' sync down with it * (2026-10-22). The rest is read on the following passes. */ readonly MAX_READ_BYTES_PER_PASS: number; /** * How many earlier transcripts of a session keep their read offset. * A session that switches between the owner's Claude Code accounts, or * gets a new conversation, can come back to a transcript it already * read; the remembered offset keeps it from being counted twice. */ readonly MAX_REMEMBERED_TRANSCRIPTS: 8; /** * #990 cost repair: a cursor's `cost` is lowered to its session's ledger * cost only when it is above it by more than this many USD… */ readonly COST_REPAIR_MIN_EXCESS_USD: 1; /** …and by more than this fraction of the ledger cost (price-table drift is ~0.5%). */ readonly COST_REPAIR_MIN_EXCESS_FRACTION: 0.02; /** Set to `1` to log what the #990 cost repair would change without changing anything. */ readonly COST_REPAIR_DRY_RUN_ENV: "CREWLY_COST_REPAIR_DRY_RUN"; /** * Context size, in tokens, above which a claude-code agent is asked to * compact. * * Expressed as an absolute ceiling rather than a percentage of the model's * window on purpose: the window differs per model and is not reported by * the runtime, whereas the thing worth acting on — "this agent now drags * N tokens through every single turn" — is directly measurable and is what * actually drives cost. Override with CREWLY_CONTEXT_TOKEN_CEILING. */ readonly CONTEXT_TOKEN_CEILING: 200000; }; export declare const CONTEXT_WINDOW_MONITOR_CONSTANTS: { /** Interval for periodic stale detection and cleanup (ms) */ readonly CHECK_INTERVAL_MS: 30000; /** Context usage threshold for yellow (warning) level (%) */ readonly YELLOW_THRESHOLD_PERCENT: 70; /** Context usage threshold for red (danger) level (%) */ readonly RED_THRESHOLD_PERCENT: 85; /** Context usage threshold for critical level (%) — triggers compact retry */ readonly CRITICAL_THRESHOLD_PERCENT: 95; /** * Whether auto-recovery (session kill + restart) is enabled at critical threshold. * Disabled by default — prefer runtime-native compact/compress commands which * preserve session state. Auto-recovery is a last resort that loses all context. */ readonly AUTO_RECOVERY_ENABLED: false; /** Maximum recovery attempts within the cooldown window */ readonly MAX_RECOVERIES_PER_WINDOW: 2; /** Cooldown window for recovery rate limiting (30 minutes) */ readonly COOLDOWN_WINDOW_MS: number; /** Grace period after monitoring start to ignore early readings (ms) */ readonly STARTUP_GRACE_PERIOD_MS: 60000; /** Maximum rolling buffer size for PTY output (bytes) */ readonly MAX_BUFFER_SIZE: 4096; /** Threshold for considering a context state stale (5 minutes) */ readonly STALE_DETECTION_THRESHOLD_MS: number; /** Time to wait after sending compact command before checking result (ms) */ readonly COMPACT_WAIT_MS: 120000; /** Maximum compact attempts per threshold episode before giving up */ readonly MAX_COMPACT_ATTEMPTS: 3; /** Cooldown between compact retries during periodic checks (ms) */ readonly COMPACT_RETRY_COOLDOWN_MS: 60000; /** Cumulative output bytes threshold before triggering proactive compact (~500KB) */ readonly PROACTIVE_COMPACT_THRESHOLD_BYTES: 512000; /** Cooldown between proactive compact triggers per session (10 minutes) */ readonly PROACTIVE_COMPACT_COOLDOWN_MS: 600000; }; /** * Compact commands per runtime type. * * Each AI runtime has its own slash command to trigger context compression: * - Claude Code: `/compact` * - Gemini CLI: `/compress` * - Codex CLI: `/compact` * - OpenCode CLI: `/compact` * - Antigravity CLI: none (it compacts by itself) */ export declare const RUNTIME_COMPACT_COMMANDS: Record; /** * Constants for OAuth auto-relogin monitoring. * Used by OAuthReloginMonitorService to detect OAuth token expiry errors * in PTY session output and automatically send /login to re-authenticate. */ export declare const OAUTH_RELOGIN_CONSTANTS: { /** Maximum rolling buffer size for PTY output (bytes) */ readonly MAX_BUFFER_SIZE: 4096; /** Cooldown between /login attempts per session (ms) — 2 minutes */ readonly RELOGIN_COOLDOWN_MS: 120000; /** Grace period after session start before monitoring begins (ms) */ readonly STARTUP_GRACE_PERIOD_MS: 30000; /** Delay after detecting error before sending /login (ms) — debounce */ readonly DETECTION_DEBOUNCE_MS: 2000; /** Maximum /login attempts per cooldown window before giving up */ readonly MAX_ATTEMPTS_PER_WINDOW: 3; /** Cooldown window for tracking max attempts (ms) — 10 minutes */ readonly ATTEMPT_WINDOW_MS: 600000; /** Delay after sending Escape before writing /login command (ms) */ readonly PRE_COMMAND_DELAY_MS: 200; /** Timeout for waiting for OAuth URL to appear after /login (ms) — 30 seconds */ readonly URL_CAPTURE_TIMEOUT_MS: 30000; /** Max buffer size during URL capture mode (bytes) — larger to capture full URL */ readonly URL_CAPTURE_BUFFER_SIZE: 8192; }; /** * String patterns that indicate authentication failure in PTY output. * Covers OAuth token expiry AND invalid/revoked credentials. * Uses plain string matching (indexOf) instead of regex to prevent ReDoS. * All patterns must be present (AND logic) within the rolling buffer. */ export declare const OAUTH_ERROR_PATTERN_SETS: string[][]; /** * Patterns that indicate a runtime is sitting on a *first-run* sign-in * screen (as opposed to a mid-session token expiry, which * `OAUTH_ERROR_PATTERN_SETS` covers). A fresh server install matches none of * the expiry sets, so without these the OAuth monitor stays idle while the * runtime waits for a human to log in. * * Keyed by runtime: a session is only checked against its own runtime's * sign-in screens (unknown runtime → every set). Incident 2026-09-29: a * Claude Code agent summarising OpenAI news wrote "…Sign in with ChatGPT…" * in its reply and the Codex pattern paged the owner. * * Each entry is a set of substrings that must ALL be present (AND logic, * case-insensitive) in the recent screen text. Plain string matching — no * regex — to stay ReDoS-free. */ export declare const LOGIN_REQUIRED_PATTERN_SETS: Readonly>; /** * Where on a captured screen a sign-in prompt can actually be. A sign-in * screen is the runtime's own UI at the bottom of the terminal, never a line * of the agent's transcript and never shown while a turn is running. */ export declare const LOGIN_SCREEN_REGION: { /** Trailing non-empty lines inspected for a sign-in prompt */ readonly TAIL_LINES: 15; /** * Line starts that open a block of the agent's own transcript (Claude's * `⏺` reply / `⎿` tool result, Codex's `•` / `└`, Gemini's `✦`). The * block continues over blank lines and lines indented by at least * {@link LOGIN_SCREEN_REGION.TRANSCRIPT_INDENT} spaces. */ readonly TRANSCRIPT_MARKERS: readonly string[]; /** Indent of a transcript block's continuation lines */ readonly TRANSCRIPT_INDENT: 2; /** * Lower-case footer text of a runtime that is busy or sitting at its chat * prompt; a sign-in screen shows neither, so any of these vetoes a match. */ readonly NOT_SIGN_IN_MARKERS: readonly string[]; }; /** * Screen text that means the runtime has just *finished* signing in. The * capture window can still hold the earlier sign-in screen above it, so * these veto a {@link LOGIN_REQUIRED_PATTERN_SETS} match and let the * "Sign-in needed" flag clear. Lower-case substrings. */ export declare const LOGIN_COMPLETED_MARKERS: readonly string[]; /** * Constants for first-run / device-code login detection and notification * in `OAuthReloginMonitorService`. */ export declare const LOGIN_REQUIRED_CONSTANTS: { /** Periodic screen sweep interval for sessions on a sign-in screen (ms) */ readonly SWEEP_INTERVAL_MS: 30000; /** Lines of screen to inspect per sweep */ readonly SWEEP_CAPTURE_LINES: 60; /** Do not re-notify the owner for the same session/code within this window (ms) */ readonly RENOTIFY_COOLDOWN_MS: number; /** Length of the first device-code segment, e.g. `FBVZ` in `FBVZ-MJHKK` */ readonly DEVICE_CODE_HEAD_LEN: 4; /** Minimum length of the second device-code segment */ readonly DEVICE_CODE_TAIL_MIN_LEN: 4; /** Maximum length of the second device-code segment */ readonly DEVICE_CODE_TAIL_MAX_LEN: 8; /** Conversation id used when enqueueing the login notice to the orchestrator */ readonly ORCHESTRATOR_CONVERSATION_ID: "system_login_required"; }; /** * Per-runtime screen markers used by `isReadyForInput()` to decide whether a * TUI will accept typed input *right now*. `waitForRuntimeReady()` matches * banner text that is already on screen while the TUI is still booting (e.g. * Codex prints `OpenAI Codex` while `model: loading`), which is why the * registration instruction used to be typed into a half-booted TUI and * swallowed. */ export declare const RUNTIME_INPUT_READY_PATTERNS: { /** Number of trailing non-empty lines inspected for prompt / busy markers */ readonly TAIL_LINES: 12; readonly CODEX: { /** Substrings (whitespace-collapsed, lower-case) that mean "still booting / not ready" */ readonly NOT_READY_MARKERS: readonly ["model: loading", "sign in with chatgpt", "auth.openai.com/codex/device"]; }; readonly CLAUDE_CODE: { readonly NOT_READY_MARKERS: readonly ["use the url below to sign in", "paste code here if prompted"]; }; readonly GEMINI_CLI: { readonly NOT_READY_MARKERS: readonly ["login with google", "waiting for auth"]; }; readonly OPENCODE_CLI: { /** * OpenCode keeps its input box (and the `Ask anything…` placeholder) on * screen while the model is running, so the busy signal is the * `esc interrupt` hint under the box rather than a missing prompt. The * remaining markers are the `/connect` provider dialog and the * "no provider configured" footer. */ readonly NOT_READY_MARKERS: readonly ["esc interrupt", "esc again to interrupt", "connect a provider", "select auth method", "get started /connect"]; }; readonly ANTIGRAVITY_CLI: { /** * Antigravity (`agy` 1.2.x) keeps its `>` input box painted while the * model runs, and echoes every submitted message as `> text`, so the * idle signal is the `? for shortcuts` footer (see isPromptLine) and * these markers veto it: the busy footer / spinner label, the * double-press exit hint, tool-approval dialogs, and the first-run, * folder-trust and account sign-in screens. Captured from agy 1.2.11 * in a PTY (2026-09-25); the approval and sign-in strings come from * the agy binary because they cannot be shown without a model turn or * an account login. */ readonly NOT_READY_MARKERS: readonly ["esc to cancel", "generating...", "press ctrl+c again to exit", "press ctrl+d again to exit", "run this command?", "accept this file edit?", "do you trust the contents of this project?", "choose your color scheme", "terms of service & data use", "select login method", "you are currently not signed in", "waiting for authentication"]; }; }; /** Reasoning-effort levels `agy --effort` accepts (from `agy --help`, 1.2.11). */ export declare const ANTIGRAVITY_EFFORT_LEVELS: readonly string[]; /** * Facts about Google Antigravity CLI (`agy`) that Crewly relies on. * * Sources: https://antigravity.google/docs/cli/install/ and * https://antigravity.google/docs/cli/headless/ (read 2026-09-25), plus the * screens of agy 1.2.11 captured in a PTY with a sandboxed HOME and a dummy * key. See specs/antigravity-runtime.md. * * Policy: Google prohibits third-party tools from using Antigravity (or * Gemini CLI) product OAuth, so Crewly only ever runs agy with a Gemini API * key: `modelProvider: "gemini"` in agy's settings file plus * `GEMINI_API_KEY` in the environment. With that provider agy "never * establishes an account session", even when the machine has an account * login in its keyring. */ export declare const ANTIGRAVITY_CONSTANTS: { /** Binary name on PATH (the installer puts it in ~/.local/bin) */ readonly BINARY: "agy"; /** * Default launch command. `--dangerously-skip-permissions` approves tool * calls (shell commands included); `--mode=accept-edits` approves file * edits, which the docs govern separately from tool permissions. */ readonly LAUNCH_COMMAND: "agy --dangerously-skip-permissions --mode=accept-edits"; /** The only env var agy reads a Gemini key from (not GOOGLE_API_KEY, not .env) */ readonly API_KEY_ENV: "GEMINI_API_KEY"; /** Stops agy's background self-updater from replacing the binary mid-task */ readonly DISABLE_AUTO_UPDATE_ENV: "AGY_CLI_DISABLE_AUTO_UPDATE"; readonly DISABLE_AUTO_UPDATE_VALUE: "true"; /** agy's config dir, relative to $HOME */ readonly CONFIG_DIR_SEGMENTS: readonly string[]; /** agy's user settings file inside the config dir */ readonly SETTINGS_FILE: "settings.json"; /** Mode agy itself writes its settings file with */ readonly SETTINGS_FILE_MODE: 384; /** Settings key + the only accepted value that selects the Gemini API key provider */ readonly MODEL_PROVIDER_KEY: "modelProvider"; readonly MODEL_PROVIDER_GEMINI: "gemini"; /** Settings key listing folders the user trusted (skips the folder-trust screen) */ readonly TRUSTED_WORKSPACES_KEY: "trustedWorkspaces"; /** One `.db` per conversation, created at the first prompt */ readonly CONVERSATIONS_DIR: "conversations"; readonly CONVERSATION_FILE_EXT: ".db"; /** Workspace path → most recent conversation id (what `agy -c` reads) */ readonly LAST_CONVERSATIONS_FILE_SEGMENTS: readonly string[]; /** Resume flag: `agy --conversation=` (printed by agy itself on exit) */ readonly RESUME_FLAG: "--conversation"; /** Launch flag that adds a folder to the workspace (repeatable) */ readonly ADD_DIR_FLAG: "--add-dir"; /** Slash command that adds a folder to a running session's workspace */ readonly ADD_DIR_COMMAND: "/add-dir"; /** * How long to look for a fresh agent's conversation id. agy creates the * conversation only when the first prompt (the registration kickoff) * arrives, which can be ~90 s after launch. */ readonly CONVERSATION_DISCOVERY_TIMEOUT_MS: number; readonly CONVERSATION_DISCOVERY_INTERVAL_MS: 2000; /** Official installer; fetched only from this exact https URL */ readonly INSTALL_SCRIPT_URL: "https://antigravity.google/cli/install.sh"; /** Update subcommand, used when agy is already installed */ readonly UPDATE_ARGS: readonly string[]; /** Where users create a Gemini API key */ readonly API_KEY_CONSOLE_URL: "https://aistudio.google.com/apikey"; /** Cheap authenticated call used to check a pasted key (key sent as a header, never in the URL) */ readonly GEMINI_API: { readonly MODELS_URL: "https://generativelanguage.googleapis.com/v1beta/models?pageSize=1"; readonly KEY_HEADER: "x-goog-api-key"; readonly CHECK_TIMEOUT_MS: 10000; /** Statuses that mean the key itself is unusable (400 API_KEY_INVALID, 401, 403 PERMISSION_DENIED) */ readonly REJECTED_STATUSES: readonly number[]; }; /** Screen text (agy 1.2.11), matched as plain substrings */ readonly SCREEN: { /** Idle footer, shown only while the prompt box is empty and nothing runs */ readonly IDLE_FOOTER: "? for shortcuts"; /** Header line when the Gemini API key provider is active (instead of an account email) */ readonly API_KEY_HEADER: "Gemini API key"; /** Placeholder of the empty prompt box in accept-edits mode */ readonly ACCEPT_EDITS_PLACEHOLDER: "Accept-edits mode: file edits auto-approved"; /** Folder-trust screen and its pre-selected "yes" option */ readonly TRUST_PROMPT: "Do you trust the contents of this project?"; readonly TRUST_ACCEPT_OPTION: "Yes, I trust this folder"; /** First-run onboarding (colour scheme, then Terms of Service and data use) */ readonly FIRST_RUN_MARKERS: readonly string[]; /** Account sign-in screens (from the binary): agy is NOT on the API-key provider */ readonly ACCOUNT_LOGIN_MARKERS: readonly string[]; /** agy's own startup refusal when the provider is gemini but the key env var is empty */ readonly MISSING_KEY_ERROR: "but the GEMINI_API_KEY environment variable is not set"; /** A rejected key surfaces on the first turn */ readonly INVALID_KEY_ERROR: "API_KEY_INVALID"; /** Printed on exit, before `agy --conversation=` */ readonly EXIT_RESUME_HINT: "Resume with -c (or command below):"; /** Confirmation of `/add-dir ` */ readonly ADD_DIR_CONFIRMATION: "to workspace"; }; readonly MESSAGES: { readonly NO_API_KEY: "Antigravity CLI runs in Crewly only with a Gemini API key, and none is saved. Add one in Settings → Runtimes → Antigravity CLI (or run `crewly login antigravity`), then start the agent again. Crewly never uses a Google account login for Antigravity."; readonly KEY_NOT_IN_SESSION: "Antigravity CLI started without GEMINI_API_KEY in its environment, so it refused to run (Crewly never lets it fall back to a Google account). Save the Gemini API key in Settings → Runtimes → Antigravity CLI (or run `crewly login antigravity`) — a key saved there is given to every Antigravity session at start — then start the agent again."; readonly ACCOUNT_LOGIN: "Antigravity CLI asked for a Google account sign-in. Crewly does not use Antigravity account (OAuth) login — Google does not allow third-party tools to — so the agent was stopped. Check that a Gemini API key is saved in Settings → Runtimes, then start the agent again."; readonly FIRST_RUN: "Antigravity CLI has not been set up on this machine yet: it shows its first-run screens (colour scheme, Google's Terms of Service and data use), which only you can accept. Crewly has sent you a card in Slack (a DM from this machine's Crewly Orc) to agree or not; you can also answer in Settings → Runtimes → Antigravity CLI → Accept terms…. Then start the agent again."; readonly SETTINGS_UNREADABLE: "Crewly could not switch Antigravity CLI to your Gemini API key because ~/.gemini/antigravity-cli/settings.json is not valid JSON. Fix or remove that file, then start the agent again."; readonly SETTINGS_WRITE_FAILED: "Crewly could not write ~/.gemini/antigravity-cli/settings.json to switch Antigravity CLI to your Gemini API key, so the agent was not started (it would otherwise fall back to a Google account login). Check that the file and its folder are writable, then start the agent again."; }; }; /** * Owner consent for a runtime's first-run Terms of Service, asked with a * Slack decision card; the harness never accepts third-party terms on its * own. specs/2026-10-01-runtime-terms-consent.md */ export declare const RUNTIME_TERMS_CONSTANTS: { /** Per-machine consent state under CREWLY_HOME */ readonly STORE_FILENAME: "runtime-terms-consent.json"; /** How long the owner has to answer; then the default (Don't agree) applies */ readonly DEADLINE_MS: number; /** Card buttons, in order (the third is the default) */ readonly OPTIONS: { readonly AGREE_NO_DATA: "Agree, no data sharing"; readonly AGREE_SHARE_DATA: "Agree + share data"; readonly DECLINE: "Don't agree"; }; /** Name prefix of the dedicated PTY session the harness drives */ readonly SESSION_PREFIX: "crewly-terms-"; /** Size of that session (wide enough that the screens do not wrap) */ readonly COLS: 120; readonly ROWS: 40; /** Driving the first-run screens */ readonly DRIVE: { /** Screen read cadence */ readonly POLL_MS: 300; /** After a key: how long to wait for the screen to change */ readonly KEY_SETTLE_MS: 3000; /** Waiting for the first screen after launch */ readonly LAUNCH_TIMEOUT_MS: 60000; /** The same unrecognised screen for this long = abort */ readonly UNKNOWN_SCREEN_TIMEOUT_MS: 20000; /** After Done: waiting for the main prompt */ readonly PROMPT_TIMEOUT_MS: 90000; /** Upper bound on keys sent (a loop that does not converge aborts) */ readonly MAX_KEYS: 60; /** Screen lines quoted in the card thread on an abort */ readonly SCREEN_LINES_IN_THREAD: 40; }; /** Antigravity CLI (agy 1.2.14) first-run screens, matched as text */ readonly ANTIGRAVITY: { readonly WELCOME: "Welcome to Antigravity CLI!"; readonly COLOR_SCHEME_TITLE: "Choose your color scheme:"; /** Schemes in screen order; the first is the default Crewly keeps */ readonly COLOR_SCHEMES: readonly string[]; readonly DEFAULT_COLOR_SCHEME: "terminal"; readonly MIGRATION_TITLE: "Migration options:"; readonly IMPORT_ITEM: "Import extensions from Gemini CLI"; readonly NEXT_BUTTON: "Next"; readonly TERMS_TITLE: "Terms of Service & Data Use"; /** Start of the pre-checked data-sharing item */ readonly DATA_ITEM: "Yes, I agree to help improve Antigravity CLI"; readonly DATA_ITEM_FULL: "Yes, I agree to help improve Antigravity CLI by allowing Google to collect and use my Interactions data, subject to the Google Antigravity CLI Terms of Service and Google Privacy Policy. I understand I can choose to opt out later whenever I want via my settings."; readonly PREVIOUS_BUTTON: "Previous"; readonly DONE_BUTTON: "Done"; readonly TERMS_URL: "https://antigravity.google/terms"; readonly PRIVACY_URL: "https://policies.google.com/privacy"; readonly SECURITY_NOTE: "AI coding agents can run code on their own, leak data, follow injected prompts and pull in compromised packages, so their actions should be watched."; }; readonly MESSAGES: { readonly DECLINED_BY_OWNER: "You chose Don't agree"; readonly DECLINED_BY_DEADLINE: "No answer within 24 h, so the default (Don't agree) applied"; readonly PENDING: "Waiting for you to accept its Terms of Service (Slack card, or Settings → Runtimes → Accept terms…)"; readonly DECLINED_SUFFIX: "Terms not accepted"; }; }; /** * Timing for delivering the registration instruction to a freshly booted * runtime and re-delivering it once when no registration arrives. */ export declare const REGISTRATION_DELIVERY_CONSTANTS: { /** Give the runtime this long to reach an idle input prompt before we type the instruction anyway (ms) */ readonly RUNTIME_INPUT_READY_TIMEOUT_MS: 90000; /** Poll cadence while waiting for the idle prompt (ms) */ readonly RUNTIME_INPUT_READY_POLL_MS: 2000; /** Poll cadence while waiting for the agent to register after delivery (ms) */ readonly REGISTRATION_CHECK_INTERVAL_MS: 5000; /** Maximum number of re-deliveries after the first instruction */ readonly MAX_REDELIVERIES: 1; }; /** * Retrying messages the input guard held back (crewly#1014): see * services/messaging/input-blocked-retry.service.ts. */ export declare const INPUT_BLOCKED_RETRY_CONSTANTS: { /** Backoff between retries while an agent's input stays blocked (ms) */ readonly RETRY_DELAYS_MS: readonly number[]; /** Tell the owner/orchestrator once blocked this long (ms) */ readonly NOTIFY_AFTER_MS: number; /** …or after this many refusals, whichever comes first */ readonly NOTIFY_AFTER_REFUSALS: 5; /** * At most one input-blocked / busy-hold alert per agent in this window * (ms). The orchestrator is busy in long turns most of the day; one alert * per episode still meant an alert after every delivery (2026-10-05). */ readonly NOTIFY_COOLDOWN_MS: number; /** * Last step of the unreadable ladder (2026-10-08 Ella): an input box that * stays unreadable this long while messages are queued for the agent, and * the agent is not mid-turn, gets the session restarted (stop/start with * conversation resume) so the queue can drain (ms). */ readonly RESTART_AFTER_UNREADABLE_MS: number; /** At most one such restart per agent per this long (ms) */ readonly RESTART_COOLDOWN_MS: number; /** * Mid-turn for the restart check: a live spinner / busy bar on screen, or * agent output within this long (Crewly's own resize redraws excluded) (ms) */ readonly RESTART_QUIET_MS: number; }; /** * Agent-to-agent messages (orchestrator `send-message`, `agent/core/send-message`). * A sender never waits for a busy recipient: the message is queued and the * skill returns at once (2026-10-05: the orchestrator's turn sat in * `send-message` for minutes while the recipient was busy, so the owner's * messages to the orchestrator waited behind it). */ export declare const AGENT_MESSAGE_DELIVERY_CONSTANTS: { /** Longest a `queueIfBusy` delivery waits for the recipient to be ready before queuing (ms) */ readonly QUEUE_IF_BUSY_MAX_WAIT_MS: 10000; }; /** * Constants for sub-agent message queue. * Used by SubAgentMessageQueue to buffer messages for agents that haven't * completed initialization (status !== 'active') yet. */ export declare const SUB_AGENT_QUEUE_CONSTANTS: { /** Wait this long after an agent goes down before waking it for its queued messages (ms) */ readonly QUEUED_WAKE_DELAY_MS: 5000; /** After boot, pause before starting agents that were down with messages queued (lets the restore queue claim its own first; a start in between drains the queue and ends the retry) */ readonly BOOT_QUEUED_WAKE_DELAY_MS: 60000; /** At most one such wake per agent in this window, so a crashing agent is not relaunched in a loop (ms) */ readonly QUEUED_WAKE_COOLDOWN_MS: number; /** * Every this often, start agents that are down with messages queued for * them. A start deferred by the slot cap is retried for hours, but a retry * that ended (6 h passed, or the agent was down before the boot sweep) * left colleague messages stranded until the next restart (2026-10-19: * Kai and Sage for 9 h). One retry loop per agent, so a sweep never piles up. */ readonly QUEUED_WAKE_SWEEP_MS: number; /** Longest gap the sweep's per-agent backoff grows to, for a start that keeps failing */ readonly QUEUED_WAKE_SWEEP_MAX_BACKOFF_MS: number; /** Maximum messages per agent before dropping oldest */ readonly MAX_QUEUE_SIZE: 50; /** Failed delivery attempts (send failed or threw) before a queued message is reported undeliverable */ readonly MAX_DELIVERY_ATTEMPTS: 5; /** Rounds of attempts a message gets kept, not dropped, while its agent is down (each round waits for a start) */ readonly MAX_KEEPS_WHILE_DOWN: 3; /** Delay between flushed messages on registration (ms) */ readonly FLUSH_INTER_MESSAGE_DELAY: 2000; /** * Oldest a restored message may be before it is dropped on load. * * The queue survives a restart now, but a message from days ago is * worse than no message: the person has moved on, and delivering it * would have an agent answer something nobody is waiting for. */ readonly MAX_AGE_MS: number; }; /** * Owner messages handed to a busy Claude Code agent at its next tool * boundary (the agent-status PostToolUse hook's `additionalContext`) instead * of waiting for the end of a 10–30 minute turn. The message stays queued * until the agent posts in its conversation; otherwise the normal idle * delivery still sends it. */ export declare const OWNER_HOOK_MESSAGE_CONSTANTS: { /** Tag that opens the note (named in the communication prompt section) */ readonly TAG: "[OWNER MESSAGE]"; /** An unanswered surfaced message is shown once more after this long (ms) */ readonly RESURFACE_AFTER_MS: number; /** Times one message is shown at a tool boundary; after that only the idle delivery remains */ readonly MAX_SURFACES: 2; /** Ceiling on the whole additionalContext a hook call returns (chars) */ readonly MAX_CONTEXT_CHARS: 6000; /** Kill switch: set to this env var's OFF value to never surface at tool boundaries */ readonly KILL_SWITCH_ENV: "CREWLY_OWNER_MESSAGE_VIA_HOOK"; /** Value of the kill switch that turns the feature off */ readonly KILL_SWITCH_OFF_VALUE: "off"; /** * Directory under CREWLY_HOME holding owner messages too long for the * note: the note points to the file instead of cutting the owner's text * (message-routing audit 2026-10-18, R0). */ readonly OVERFLOW_DIR: "runtime/owner-messages"; /** Characters of an overflowing owner message quoted in the note before the pointer */ readonly OVERFLOW_PREVIEW_CHARS: 600; }; /** * Every message carries its destination (message-routing audit 2026-10-18): * the route token, the work-item field a delegation stamps, and the * placeholder conversation of agent status reports that name none. */ export declare const MESSAGE_ROUTE_CONSTANTS: { /** Opening of the route token in a delivered text (`{route: work-item }`) */ readonly TOKEN_OPEN: "{route: "; /** WorkItem metadata key: the route of the input the delegator was answering when it delegated */ readonly PARENT_ROUTE_METADATA_KEY: "parentRoute"; /** Hops followed from a delegated item to the route its delegator answers */ readonly MAX_ROUTE_HOPS: 4; /** * Conversation id of an agent status report that names no conversation: * it is internal, never "the globally current conversation". */ readonly AGENT_STATUS_CONVERSATION_ID: "system:agent-status"; }; /** * Constants for proactive system resource monitoring and alerting. * Used by SystemResourceAlertService to poll metrics, check thresholds, * and send user-facing notifications before resources are exhausted. */ export declare const SYSTEM_RESOURCE_ALERT_CONSTANTS: { /** Polling interval for resource checks (ms) */ readonly POLL_INTERVAL: 60000; /** Cooldown between repeated alerts for the same metric (ms) */ readonly ALERT_COOLDOWN: 600000; /** * Alert keys that also go to the owner over Slack (#991): critical disk * and memory, and idle agents auto-stopped under memory pressure. */ readonly OWNER_NOTICE_KEYS: readonly string[]; /** Owner Slack notice for the same alert key at most this often (6 hours). */ readonly OWNER_NOTICE_COOLDOWN: number; /** State file under CREWLY_HOME remembering when the owner was last told, per alert key. */ readonly OWNER_NOTICE_STATE_FILENAME: "system-alert-owner-notices.json"; /** Thresholds for triggering alerts */ readonly THRESHOLDS: { readonly DISK_WARNING: 85; readonly DISK_CRITICAL: 95; readonly MEMORY_WARNING: 85; readonly MEMORY_CRITICAL: 95; readonly CPU_WARNING: 80; readonly CPU_CRITICAL: 95; }; }; /** * Shared Slack API limits used by both image and file upload services. */ export declare const SLACK_API_LIMITS: { /** * Maximum file size for Slack transfers in either direction (default 1 GB, * which is what Slack itself allows). Files are streamed to/from disk, never * buffered. Override with CREWLY_SLACK_MAX_FILE_BYTES. */ readonly MAX_FILE_SIZE: number; /** Maximum number of retry attempts for Slack API 429 responses */ readonly UPLOAD_MAX_RETRIES: 3; /** Default backoff delay (ms) when no Retry-After header is present */ readonly UPLOAD_DEFAULT_BACKOFF_MS: 5000; }; /** * Constants for Slack image download and temporary storage. * Used by SlackImageService to validate, download, and manage * images sent by users in Slack messages. */ export declare const SLACK_IMAGE_CONSTANTS: { /** Temp directory for downloaded images (relative to ~/.crewly/) */ readonly TEMP_DIR: "tmp/slack-images"; /** Maximum allowed file size for image downloads (20 MB — images go to an LLM as vision input) */ readonly MAX_FILE_SIZE: number; /** Supported image MIME types for download (SVG excluded — not accepted by LLM vision APIs) */ readonly SUPPORTED_MIMES: readonly ["image/png", "image/jpeg", "image/gif", "image/webp"]; /** * Expected Content-Type prefixes for valid image responses from Slack. * Used to detect when Slack returns an HTML error page instead of an image. */ readonly VALID_RESPONSE_CONTENT_TYPES: readonly ["image/"]; /** * Magic byte signatures for supported image formats. * Used to verify that a downloaded file is actually an image * (Slack can return 200 OK with an HTML body when auth fails). */ readonly IMAGE_MAGIC_BYTES: { readonly PNG: readonly [137, 80, 78, 71]; readonly JPEG: readonly [255, 216, 255]; readonly GIF87: readonly [71, 73, 70, 56, 55]; readonly GIF89: readonly [71, 73, 70, 56, 57]; readonly WEBP_RIFF: readonly [82, 73, 70, 70]; }; /** Interval for cleaning up expired temp files (1 hour) */ readonly CLEANUP_INTERVAL: number; /** Maximum age for temp files before cleanup (24 hours) */ readonly FILE_TTL: number; /** Maximum concurrent image downloads per message */ readonly MAX_CONCURRENT_DOWNLOADS: 3; /** Warning threshold for temp directory total size (500 MB) */ readonly MAX_TEMP_DIR_SIZE: number; /** Maximum redirect hops to follow during file download */ readonly MAX_DOWNLOAD_REDIRECTS: 5; /** Maximum number of retry attempts for Slack API 429 responses */ readonly UPLOAD_MAX_RETRIES: 3; /** Default backoff delay (ms) when no Retry-After header is present */ readonly UPLOAD_DEFAULT_BACKOFF_MS: 5000; }; /** * Constants for generic file uploads to Slack channels. * Used by the upload-file endpoint and send-pdf-to-slack skill * to validate and upload arbitrary file types (PDF, images, docs, etc.). */ export declare const SLACK_FILE_UPLOAD_CONSTANTS: { /** Temp directory for generated PDFs (relative to ~/.crewly/) */ readonly TEMP_DIR: "tmp/slack-pdfs"; /** Maximum allowed file size for uploads (streamed; see SLACK_API_LIMITS.MAX_FILE_SIZE) */ readonly MAX_FILE_SIZE: number; /** File extensions accepted for upload */ readonly SUPPORTED_EXTENSIONS: readonly [".pdf", ".png", ".jpg", ".jpeg", ".gif", ".webp", ".svg", ".txt", ".csv", ".doc", ".docx", ".xls", ".xlsx", ".mp4", ".mov", ".avi", ".mkv", ".mp3", ".wav", ".zip"]; /** Maximum number of retry attempts for Slack API 429 responses */ readonly UPLOAD_MAX_RETRIES: 3; /** Waits before each try of posting a privately uploaded image under an agent's name (the file needs a moment) */ readonly PERSONA_IMAGE_WAITS_MS: readonly number[]; /** Default backoff delay (ms) when no Retry-After header is present */ readonly UPLOAD_DEFAULT_BACKOFF_MS: 5000; }; /** * Constants for downloading non-image file attachments from Slack messages. * Used by SlackOrchestratorBridge to download generic files (PDFs, docs, etc.) * sent by users so agents can access them via file-reading tools. */ /** * Message prefix `@slack/web-api` puts on every Slack platform error * (`platformErrorFromResult`), e.g. "An API error occurred: invalid_auth". * Used to recognise a rejection that came out of the Slack SDK rather than * from Crewly's own code. */ export declare const SLACK_PLATFORM_ERROR_PREFIX = "An API error occurred:"; /** * Substrings of unhandled-rejection messages that the backend logs but * does NOT shut down for. Each entry is a rejection a third-party * integration library can raise on its own promise chain (no app frame * to catch it) and which must never take the whole process down. * * Anything not matched here still triggers the graceful shutdown, so a * genuinely unknown rejection is not silently swallowed. */ export declare const NON_FATAL_UNHANDLED_REJECTION_PATTERNS: readonly ["Unhandled event", "socket hang up", "ECONNRESET", "An API error occurred:"]; /** * Slack Socket Mode reconnection constants. * Controls automatic reconnection when network drops cause the WebSocket * to die and Bolt's built-in reconnect fails to recover. */ /** * Outbound reachability health (#753). Posts failing because the connected * bot cannot see the channel usually mean the wrong Slack app is connected * (self-hosted vs Crewly Cloud are different bot users), which leaves the * socket healthy while every reply is lost. */ export declare const SLACK_DELIVERY_HEALTH_CONSTANTS: { /** Slack error codes meaning "this bot cannot see that channel" */ readonly UNREACHABLE_ERROR_CODES: readonly string[]; /** Consecutive unreachable posts (with no success in between) before status reports degraded */ readonly FAILURES_BEFORE_DEGRADED: 3; /** Distinct failing channel ids kept for the status payload */ readonly MAX_TRACKED_CHANNELS: 5; }; export declare const SLACK_RECONNECT_CONSTANTS: { /** Initial delay before first reconnection attempt (ms) */ readonly INITIAL_DELAY_MS: 2000; /** Maximum delay between reconnection attempts (ms) */ readonly MAX_DELAY_MS: 60000; /** Backoff multiplier applied after each failed attempt */ readonly BACKOFF_MULTIPLIER: 2; /** Maximum number of consecutive reconnection attempts before giving up (0 = unlimited) */ readonly MAX_ATTEMPTS: 50; /** Grace period after a disconnect before starting reconnection (ms). * Gives Bolt's built-in reconnect a chance to recover first. */ readonly GRACE_PERIOD_MS: 10000; /** Interval for periodic connection health checks (ms) */ readonly HEALTH_CHECK_INTERVAL_MS: 30000; /** Timeout for the auth.test API ping during health checks (ms) */ readonly PING_TIMEOUT_MS: 10000; /** Number of consecutive ping failures before forcing a reconnect */ readonly PING_FAILURES_BEFORE_RECONNECT: 2; }; /** * Fetching a Slack file by link or id (`slack-file get`, and files linked * in an inbound message). Local bot tokens first, then Crewly Cloud, which * holds every bot of the account (`POST /api/cloud/slack/files/fetch`). */ export declare const SLACK_FILE_FETCH_CONSTANTS: { /** Largest file fetched (bytes) — same cap as Cloud */ readonly MAX_BYTES: number; /** Cloud endpoint, appended to the cloud URL */ readonly CLOUD_FETCH_PATH: "/api/cloud/slack/files/fetch"; /** Header carrying the file metadata on Cloud's raw answer (base64url JSON) */ readonly CLOUD_META_HEADER: "x-crewly-slack-file"; /** Local bot tokens tried at most */ readonly MAX_LOCAL_CANDIDATES: 12; /** Slack API / Cloud call timeout (ms) */ readonly API_TIMEOUT_MS: 15000; /** Download timeout (ms) */ readonly DOWNLOAD_TIMEOUT_MS: 60000; /** Redirect hops followed on a download */ readonly MAX_REDIRECTS: 5; /** Linked files auto-fetched per inbound message */ readonly MAX_LINKED_PER_MESSAGE: 3; /** Linked files larger than this are not auto-fetched (the agent can still run `slack-file get`) */ readonly MAX_LINKED_BYTES: number; /** Lines of a text file shown inline by the skill */ readonly PREVIEW_LINES: 20; /** Characters of a text file shown inline by the skill */ readonly PREVIEW_CHARS: 2000; }; export declare const SLACK_FILE_DOWNLOAD_CONSTANTS: { /** Temp directory for downloaded files (relative to ~/.crewly/) */ readonly TEMP_DIR: "tmp/slack-files"; /** Maximum allowed file size for downloads (streamed to disk; see SLACK_API_LIMITS.MAX_FILE_SIZE) */ readonly MAX_FILE_SIZE: number; /** Maximum concurrent file downloads per message */ readonly MAX_CONCURRENT_DOWNLOADS: 3; /** Maximum redirect hops to follow during file download */ readonly MAX_DOWNLOAD_REDIRECTS: 5; /** Timeout for individual file download requests (ms) */ readonly DOWNLOAD_TIMEOUT_MS: 60000; /** Maximum extracted text length included inline in messages (characters) */ readonly MAX_EXTRACTED_TEXT_LENGTH: 8000; /** MIME types eligible for text extraction */ readonly EXTRACTABLE_MIMES: readonly string[]; }; /** * One responder per owner message in Slack rooms * (specs/2026-10-03-one-responder-per-message.md). */ export declare const ROOM_RESPONDER_CONSTANTS: { /** Longest the router waits for the decision path's outcome on a card-thread reply */ readonly DECISION_WAIT_MS: 8000; /** Longest the router waits for the Slack thread read before choosing a thread owner */ readonly THREAD_CONTEXT_WAIT_MS: 3000; /** Distinct Slack messages whose decision outcome is remembered (memo for the shared run) */ readonly DECISION_MEMO_MAX: 200; /** Thread rows the reply gate reads (newest) */ readonly GATE_THREAD_SCAN: 200; /** * An agent post into a room thread where the owner last wrote longer ago * than this (ms) is refused: the owner reads it with no context. It goes * in a new thread that says what it follows up on (2026-10-19: Ella's * week-3 Reddit summary landed in a 3-week-old, 80-message thread). */ readonly STALE_OWNER_THREAD_MS: number; /** Characters of the existing answer shown when a reply is held */ readonly GATE_EXCERPT_CHARS: 300; /** Pause before the one retry of a Slack thread read that failed */ readonly THREAD_READ_RETRY_BACKOFF_MS: 400; /** Longest the one retry waits in all, a Slack rate limit's retry-after included */ readonly THREAD_READ_RETRY_MAX_WAIT_MS: 2000; /** * Longest the 90 s fallback waits for each of its (two) Slack thread * reads. Nobody is waiting on it, so it gets more than the router's 3 s: * a busy backend regularly took longer, and every miss became a "nobody * was awake" note although agents were (2026-10-05, #book-publish). */ readonly FALLBACK_THREAD_READ_WAIT_MS: 8000; /** * A note-only watch (a machine that is not the room's watcher but cannot be * sure the watcher is live, or cannot tell who it is) waits this long, then * only tells the owner — it never hands the message to anyone. */ readonly NOTE_ONLY_WATCH_MS: number; /** * With colleagues on other machines in the room, this machine's chat log * is trusted to name a thread's last speaker only when its latest agent * turn there is this recent (Cloud never forwards other machines' bot * posts, so an older log may have missed them). */ readonly LOCAL_LOG_FRESH_MS: number; /** Huddle-row metadata: the responders the harness chose for an owner message (the reply gate reads it) */ readonly CHOSEN_RESPONDERS_METADATA_KEY: "roomResponders"; }; /** The context-only queue: room messages an agent hears on its next turn instead of now. */ export declare const ROOM_CONTEXT_CONSTANTS: { /** Entries kept per (agent, room); oldest dropped first */ readonly MAX_ENTRIES: 6; /** Characters kept of each message */ readonly PER_ENTRY_CHARS: 300; /** Entries older than this are dropped unread */ readonly TTL_MS: number; /** (agent, room) queues kept at most */ readonly MAX_QUEUES: 500; }; /** * Message source identifiers for the queue processor. * Determines delivery strategy (timeouts, retry behavior). */ /** * What makes a `sender_type='user'` chat-v2 row count as the OWNER speaking * (commitment-approval gate, issues #730 / 2026-06-02 incident). * * Every surface that carries the owner's words to the orchestrator records * them as a `user` turn (Chat UI, legacy chat, Slack DM/thread/team channel, * WhatsApp, Telegram, Google Chat, portal/mobile relay). A few paths also * store text an AGENT wrote as a `user` turn, so it reaches colleagues the way * a human's message does; those rows carry one of the markers below and are * never owner evidence. */ export declare const OWNER_EVIDENCE_METADATA: { /** Local agent session that authored the row (agent-session API caller, cross-machine Slack post). */ readonly AUTHOR_AGENT_SESSION: "authorAgentSession"; /** Colleague agent on another machine whose Slack post was recorded here (slack-team-channel). */ readonly REMOTE_AGENT_SESSION: "remoteAgentSession"; /** `metadata.source` values that only agent replies carry — never an owner turn. */ readonly AGENT_REPLY_SOURCES: readonly ["pty-runtime", "in-process-runtime", "reply-tool"]; }; export declare const MESSAGE_SOURCES: { readonly SLACK: "slack"; readonly WHATSAPP: "whatsapp"; readonly WEB_CHAT: "web_chat"; readonly SYSTEM_EVENT: "system_event"; readonly GOOGLE_CHAT: "google_chat"; readonly TELEGRAM: "telegram"; readonly CROSS_MACHINE: "cross-machine"; readonly REMOTE: "remote"; }; /** * Constants for Google Chat Pub/Sub integration. * Used by GoogleChatMessengerAdapter for pulling messages from a Pub/Sub subscription * and replying via the Chat API. */ export declare const GOOGLE_CHAT_PUBSUB_CONSTANTS: { /** Interval between Pub/Sub pull requests (ms) */ readonly PULL_INTERVAL_MS: 5000; /** Maximum messages to pull per request */ readonly MAX_MESSAGES_PER_PULL: 10; /** OAuth2 scope for Pub/Sub API access */ readonly PUBSUB_SCOPE: "https://www.googleapis.com/auth/pubsub"; /** OAuth2 scope for Google Chat API access */ readonly CHAT_SCOPE: "https://www.googleapis.com/auth/chat.bot"; /** Pub/Sub REST API base URL */ readonly PUBSUB_API_BASE: "https://pubsub.googleapis.com/v1"; /** Google Chat REST API base URL */ readonly CHAT_API_BASE: "https://chat.googleapis.com/v1"; /** Timeout for Pub/Sub API calls (ms) */ readonly FETCH_TIMEOUT_MS: 15000; /** Max consecutive pull failures before pausing */ readonly MAX_CONSECUTIVE_FAILURES: 5; /** Maximum message length for the Google Chat API (characters) */ readonly MAX_MESSAGE_LENGTH: 4096; }; /** * Constants for Telegram Bot API integration. * Used by TelegramService for polling incoming messages and sending replies. */ export declare const TELEGRAM_CONSTANTS: { /** Interval between getUpdates polling requests (ms) */ readonly POLL_INTERVAL_MS: 3000; /** Timeout for long-polling requests (seconds, sent to Telegram API) */ readonly LONG_POLL_TIMEOUT_S: 30; /** Timeout for fetch requests (ms) */ readonly FETCH_TIMEOUT_MS: 35000; /** Telegram Bot API base URL */ readonly API_BASE: "https://api.telegram.org/bot"; /** Maximum message length for Telegram API (characters) */ readonly MAX_MESSAGE_LENGTH: 4096; /** Max consecutive poll failures before pausing */ readonly MAX_CONSECUTIVE_FAILURES: 5; /** Credentials file name */ readonly CREDENTIALS_FILE: "telegram-credentials.json"; /** Chat routing prefix for incoming Telegram messages */ readonly CHAT_PREFIX: "TELEGRAM"; }; export declare const MARKETPLACE_CONSTANTS: { readonly PUBLIC_REGISTRY_URL: "https://raw.githubusercontent.com/stevehuang0115/crewly/main/config/skills/registry.json"; readonly PUBLIC_CDN_BASE: "https://raw.githubusercontent.com/stevehuang0115/crewly/main"; readonly PREMIUM_BASE_URL: "https://crewlyai.com"; readonly PREMIUM_REGISTRY_ENDPOINT: "/api/registry/skills"; readonly ASSETS_ENDPOINT: "/api/assets"; readonly CACHE_TTL: 3600000; readonly DIR_NAME: "marketplace"; readonly MANIFEST_FILE: "manifest.json"; readonly LOCAL_REGISTRY_FILE: "local-registry.json"; readonly SUBMISSIONS_DIR: "submissions"; readonly GITHUB_REPO: "stevehuang0115/crewly"; readonly SCHEMA_VERSION: 2; readonly DOWNLOAD_TIMEOUT: 30000; readonly GITHUB_FILE_TIMEOUT: 15000; readonly REGISTRY_FETCH_TIMEOUT: 10000; readonly CATEGORY_MAP: Record; }; export declare const TEMPLATE_MARKETPLACE_CONSTANTS: { readonly TEMPLATES_DIR: "templates"; readonly STORE_FILE: "templates.json"; readonly VERSIONS_DIR: "versions"; readonly VERSIONS_FILE: "versions.json"; readonly SCHEMA_VERSION: 1; readonly MAX_NAME_LENGTH: 100; readonly MAX_DESCRIPTION_LENGTH: 2000; readonly MAX_CHANGELOG_LENGTH: 5000; readonly MAX_TAGS: 20; readonly MAX_TAG_LENGTH: 50; }; /** Typed message source value */ export type MessageSource = (typeof MESSAGE_SOURCES)[keyof typeof MESSAGE_SOURCES]; /** * Constants for WhatsApp integration via Baileys. * Used by WhatsAppService, WhatsAppOrchestratorBridge, and WhatsApp controller * for connection management and message handling. */ export declare const WHATSAPP_CONSTANTS: { /** Directory name for auth state persistence (under ~/.crewly/) */ readonly AUTH_DIR: "whatsapp-auth"; /** Maximum text message length (WhatsApp limit) */ readonly MAX_MESSAGE_LENGTH: 4000; /** Maximum file size for sending documents (5 MB) */ readonly MAX_FILE_SIZE: number; /** Delay between reconnection attempts (ms) */ readonly RECONNECT_INTERVAL_MS: 5000; /** Timeout for QR code scanning before expiry (ms) */ readonly QR_TIMEOUT_MS: 60000; /** Maximum response length from orchestrator before truncation */ readonly MAX_RESPONSE_LENGTH: 3000; /** Buffer added to message queue timeout for response timeout (ms) */ readonly RESPONSE_TIMEOUT_BUFFER_MS: 5000; /** Regex pattern for WhatsApp JID suffix */ readonly JID_SUFFIX_PATTERN: RegExp; /** Regex pattern for phone number + prefix */ readonly PHONE_PREFIX_PATTERN: RegExp; /** Fallback timeout when MESSAGE_QUEUE_CONSTANTS is unavailable (ms) */ readonly DEFAULT_FALLBACK_TIMEOUT_MS: 120000; /** * Connection modes. `assistant` routes every incoming message to the * orchestrator and auto-replies (the original bridge). `inbox` reads the * owner's personal account, stores it locally and never sends anything * without the owner's explicit confirmation. */ readonly MODES: { readonly ASSISTANT: "assistant"; readonly INBOX: "inbox"; }; /** Mode used by `POST /api/whatsapp/connect` when the body names none (owner decision) */ readonly DEFAULT_CONNECT_MODE: "inbox"; /** * Mode for the legacy `WHATSAPP_ENABLED=true` env path when neither * `WHATSAPP_MODE` nor a persisted connection names one — that path has * always meant the orchestrator assistant. */ readonly LEGACY_ENV_MODE: "assistant"; /** Data directory under the Crewly home (`~/.crewly/whatsapp/`) */ readonly DATA_DIR: "whatsapp"; /** SQLite file holding the inbox (chats, messages, drafts) */ readonly INBOX_DB_FILE: "inbox.db"; /** Persisted connection config (mode, allowed contacts, auto-connect) */ readonly CONNECTION_CONFIG_FILE: "connection.json"; /** Group chat JID suffix */ readonly GROUP_JID_SUFFIX: "@g.us"; /** Phone-number user JID suffix */ readonly USER_JID_SUFFIX: "@s.whatsapp.net"; /** Linked-identity (LID) JID suffix used by newer WhatsApp addressing */ readonly LID_JID_SUFFIX: "@lid"; /** Pseudo-chat carrying status/stories — never part of the inbox */ readonly STATUS_BROADCAST_JID: "status@broadcast"; /** Baileys `messages.upsert` types: live (`notify`) and own/backfilled (`append`) */ readonly UPSERT_TYPES: { readonly NOTIFY: "notify"; readonly APPEND: "append"; }; /** Baileys `HistorySync.HistorySyncType.FULL` — the whole account history, skipped in inbox mode */ readonly HISTORY_SYNC_TYPE_FULL: 2; /** History-sync messages older than this are not stored (inbox seeds recent history only) */ readonly HISTORY_SYNC_MAX_AGE_MS: number; /** Stored message kinds */ readonly MESSAGE_KINDS: { readonly TEXT: "text"; readonly IMAGE: "image"; readonly DOCUMENT: "document"; readonly AUDIO: "audio"; readonly VIDEO: "video"; readonly STICKER: "sticker"; readonly OTHER: "other"; }; /** Chat-name provenance, higher wins: pushName < chat/group subject < address-book name */ readonly NAME_RANKS: { readonly NONE: 0; readonly PUSH_NAME: 1; readonly CHAT: 2; readonly CONTACT: 3; }; /** Row caps for the read API: default and hard maximum per endpoint */ readonly LIMITS: { readonly INBOX_DEFAULT: 20; readonly INBOX_MAX: 200; readonly CHATS_DEFAULT: 50; readonly CHATS_MAX: 500; readonly MESSAGES_DEFAULT: 50; readonly MESSAGES_MAX: 500; readonly SEARCH_DEFAULT: 20; readonly SEARCH_MAX: 200; readonly DRAFTS_DEFAULT: 100; readonly DRAFTS_MAX: 500; }; /** Prefix of the short human draft code (`W12`) */ readonly DRAFT_CODE_PREFIX: "W"; /** Draft lifecycle. `sending` is the claim held while the socket call is in flight */ readonly DRAFT_STATUSES: { readonly PENDING: "pending"; readonly SENDING: "sending"; readonly SENT: "sent"; readonly DISCARDED: "discarded"; }; /** * How long after a draft is written an agent may send it on the strength of * the owner's 「发 W12」 chat message. After this the owner must send it from * the dashboard, or the agent drafts again. */ readonly DRAFT_CONFIRM_WINDOW_MS: number; /** * Owner confirmation message: 「发 W12」 / 「发送 12」 / "send #W12" / 「确认发送 W12」. * Group 2 is the draft code (with or without the W). */ readonly DRAFT_CONFIRM_PATTERN: RegExp; /** Owner messages scanned for a confirmation (newest first) */ readonly OWNER_CONFIRM_SCAN_LIMIT: 200; /** Error codes returned by the inbox API */ readonly ERROR_CODES: { readonly NEEDS_OWNER_CONFIRMATION: "needs_owner_confirmation"; readonly AGENT_SEND_FORBIDDEN: "agent_send_forbidden_in_inbox_mode"; readonly DRAFT_NOT_FOUND: "draft_not_found"; readonly DRAFT_NOT_PENDING: "draft_not_pending"; readonly CHAT_NOT_FOUND: "chat_not_found"; readonly NOT_CONNECTED: "not_connected"; readonly SEND_FAILED: "send_failed"; readonly INVALID_MODE: "invalid_mode"; readonly INVALID_INPUT: "invalid_input"; }; }; /** WhatsApp connection mode (see {@link WHATSAPP_CONSTANTS.MODES}) */ export type WhatsAppMode = (typeof WHATSAPP_CONSTANTS.MODES)[keyof typeof WHATSAPP_CONSTANTS.MODES]; /** Google OAuth endpoint URLs and default scopes. */ export declare const GOOGLE_OAUTH_CONSTANTS: { readonly AUTH_BASE_URL: "https://accounts.google.com/o/oauth2/v2/auth"; readonly TOKEN_ENDPOINT: "https://oauth2.googleapis.com/token"; readonly USERINFO_ENDPOINT: "https://www.googleapis.com/oauth2/v2/userinfo"; /** * Default OAuth scopes requested by the standalone /api/oauth/google flow. * * `gmail.modify` is a Google-side functional superset that covers * `gmail.readonly`, `gmail.send`, and label/state mutations. Declaring * the single most-capable Gmail scope here means new credentials issued * via this flow can drive all 9 actions of the agent `gmail` skill on * one OAuth grant — no re-auth required when an agent moves from * read-only to send to label management. */ readonly DEFAULT_SCOPES: readonly ["openid", "email", "https://www.googleapis.com/auth/gmail.modify"]; /** Google Workspace scopes for agent-driven operations (Phase 1). */ readonly WORKSPACE_SCOPES: readonly ["https://www.googleapis.com/auth/gmail.readonly", "https://www.googleapis.com/auth/gmail.send", "https://www.googleapis.com/auth/gmail.compose", "https://www.googleapis.com/auth/drive.file", "https://www.googleapis.com/auth/calendar.events", "https://www.googleapis.com/auth/documents"]; }; /** * Google Workspace (Gmail + Calendar) via Crewly Cloud. * * Cloud holds the OAuth grant (`/api/cloud/google/workspace/*`); the OSS * instance fetches a short-lived access token from Cloud and talks to * Google directly so mail content never passes through Cloud. */ /** * Google products a Crewly install connects independently. * * Consent is per product so a user who wants Calendar is not asked for their * whole mailbox, and so an install that never touches mail or files stays out * of Google's *restricted* scope tier (which requires a CASA security * assessment to verify). `drive` covers Docs, Sheets and Slides — those APIs * read through `drive.readonly` and write through `drive.file`. */ export declare const GOOGLE_PRODUCTS: readonly ["gmail", "calendar", "drive"]; /** One of {@link GOOGLE_PRODUCTS}. */ export type GoogleProduct = (typeof GOOGLE_PRODUCTS)[number]; export declare const GOOGLE_WORKSPACE_CONSTANTS: { /** Cloud API prefix for the Workspace grant (appended to the cloud URL) */ readonly CLOUD_PATH: "/api/cloud/google/workspace"; /** Cloud sub-paths under CLOUD_PATH */ readonly CLOUD_ENDPOINTS: { /** GET → { connected, connections[], email, scopes, grantedAt } */ readonly STATUS: "/status"; /** GET ?email=&product= → { accessToken, expiresAt, scopes, email, products } */ readonly TOKEN: "/token"; /** GET ?token=&returnUrl=&products=&loginHint=&authorizedBy= → 302 to Google consent */ readonly START: "/start"; /** POST { email, authorizedBy?, sharing? } → { authorizedBy, sharing } — who owns a grant and who it is shared with (issue #968) */ readonly SHARING: "/sharing"; /** * POST { products, slackUserId, slackChannelId, slackThreadTs } → * `{ url, expiresAt }`. * * The URL behind a Slack card's button. Unlike {@link START} it * carries a single-use ticket rather than the Cloud session token, * because a message in a channel must not contain a credential. */ readonly CONNECT_TICKET: "/connect-ticket"; /** POST { email } → { updated }; choose the account used when none is named */ readonly DEFAULT: "/default"; /** DELETE CLOUD_PATH itself, optional ?email= → { removed } */ readonly DISCONNECT: ""; }; /** Re-fetch the access token this long before Cloud's `expiresAt` (ms) */ readonly TOKEN_REFRESH_MARGIN_MS: 60000; /** HTTP timeout for Cloud and Google calls (ms) */ readonly REQUEST_TIMEOUT_MS: 15000; /** Gmail REST base for the signed-in user */ readonly GMAIL_API_BASE: "https://gmail.googleapis.com/gmail/v1/users/me"; /** Calendar REST base */ readonly CALENDAR_API_BASE: "https://www.googleapis.com/calendar/v3"; /** Default / ceiling for Gmail search results per call */ readonly GMAIL_DEFAULT_MAX_RESULTS: 20; readonly GMAIL_MAX_RESULTS_CEILING: 100; /** Largest attachment the connector will hand to an agent (25 MB, Gmail's own send limit). */ readonly GMAIL_ATTACHMENT_MAX_BYTES: number; /** Default / ceiling for Calendar list results per call */ readonly CALENDAR_DEFAULT_MAX_RESULTS: 50; readonly CALENDAR_MAX_RESULTS_CEILING: 250; /** Calendar used when the caller names none */ readonly DEFAULT_CALENDAR_ID: "primary"; /** Drive REST base (metadata / search / export) */ readonly DRIVE_API_BASE: "https://www.googleapis.com/drive/v3"; /** Drive upload base (multipart create) */ readonly DRIVE_UPLOAD_BASE: "https://www.googleapis.com/upload/drive/v3"; /** Docs REST base */ readonly DOCS_API_BASE: "https://docs.googleapis.com/v1"; /** Sheets REST base */ readonly SHEETS_API_BASE: "https://sheets.googleapis.com/v4"; /** Slides REST base */ readonly SLIDES_API_BASE: "https://slides.googleapis.com/v1"; /** Default / ceiling for Drive search results per call */ readonly DRIVE_DEFAULT_MAX_RESULTS: 20; readonly DRIVE_MAX_RESULTS_CEILING: 100; /** Largest file body read into memory for `drive read` / `drive upload` (bytes) */ readonly DRIVE_MAX_CONTENT_BYTES: number; /** Google-native MIME types and what they export to as text */ readonly DRIVE_EXPORT_MIME: Record; /** * Full Drive scope. Needed to reply to, resolve or add comments on a * document Crewly did not create (`comments.create` / `replies.create` * accept only `drive` or `drive.file`). Optional on the Cloud grant. */ readonly DRIVE_FULL_SCOPE: "https://www.googleapis.com/auth/drive"; /** * Gmail compose scope. `drafts.create` / `drafts.send` need it; grants made * before 2026-10-08 carry only `gmail.readonly` + `gmail.send` and answer * drafts with 403 "insufficient authentication scopes". Optional on the * Cloud grant, like {@link DRIVE_FULL_SCOPE}. */ readonly GMAIL_COMPOSE_SCOPE: "https://www.googleapis.com/auth/gmail.compose"; /** * Reconnect cards the harness posts to the owner when a Google call fails * for want of a scope, or because the grant expired / was revoked. */ readonly REAUTH: { /** * A card that went out less than this long ago is not repeated for the * same product + Google account (concurrent failures share one card); * after it, a new failure posts a fresh card unless the owner has * reconnected (ms) */ readonly CARD_RESEND_AFTER_MS: number; /** First wait between looks at Cloud for the reconnect (ms) */ readonly POLL_INTERVAL_MS: 30000; /** The wait grows by this factor after each look... */ readonly POLL_BACKOFF_FACTOR: 1.5; /** ...up to this ceiling (ms) */ readonly POLL_MAX_INTERVAL_MS: number; /** The card link never expires, so keep watching for the reconnect this long (ms) */ readonly WATCH_MS: number; /** Give up posting the card after this long, so the skill call is not held (ms) */ readonly POST_TIMEOUT_MS: 8000; }; /** Comments fetched per Drive `comments.list` page (Google's max is 100) */ readonly COMMENTS_PAGE_SIZE: 100; /** Pages read per `docs-comment list` before stopping (caps a huge thread) */ readonly COMMENTS_MAX_PAGES: 5; /** Sheets range used when the caller names none */ readonly SHEETS_DEFAULT_RANGE: "A1:Z1000"; /** Cap on rows accepted per Sheets write */ readonly SHEETS_MAX_ROWS: 5000; /** Cap on slides per Slides create */ readonly SLIDES_MAX_SLIDES: 60; /** Dashboard path the Cloud consent flow returns to */ readonly SETTINGS_RETURN_PATH: "/connections?platform=google-workspace"; /** * Cloud portal page that starts Google consent. It mints the consent * ticket when the owner opens it, so a link to it never expires and * carries no credential. Query: `products=&account=&auto=1`. */ readonly PORTAL_CONNECT_PATH: "/portal/integrations/google"; /** Metadata headers requested on Gmail search hits */ readonly GMAIL_SEARCH_HEADERS: readonly ["From", "To", "Subject", "Date"]; /** How often watched Gmail threads are checked for replies (one history.list per tick); must stay well under the 60 s wake promise */ readonly GMAIL_WATCH_POLL_MS: 30000; /** A thread watch is dropped this long after its last reply (or creation) so the set cannot grow forever */ readonly GMAIL_WATCH_EXPIRY_MS: number; /** Consecutive "account not connected" ticks before its watches are removed */ readonly GMAIL_WATCH_DISCONNECT_MISSES: 3; /** RFC 2045 line width for base64 message bodies */ readonly MIME_LINE_WIDTH: 76; /** Error codes shared between the token service, controller and skills */ readonly ERROR_CODES: { /** Not signed in to Crewly Cloud at all */ readonly NOT_LOGGED_IN: "not_logged_in"; /** Cloud has no Workspace grant for this account (or it was revoked) */ readonly NOT_CONNECTED: "not_connected"; /** Cloud is not configured with a Google client */ readonly NOT_CONFIGURED: "not_configured"; /** Google answered Cloud (or us) with an error */ readonly GOOGLE_ERROR: "google_error"; /** Cloud or Google unreachable */ readonly NETWORK: "network"; /** Caller sent an invalid request */ readonly VALIDATION: "validation"; /** The grant lacks a scope this call needs; the owner must reconnect the product */ readonly REAUTH_REQUIRED: "reauth_required"; }; }; /** * Canva Connect on the owner's account — Cloud holds the grant (see * services/auth canva.service), this instance talks to api.canva.com. * Backs the canva-* skills. */ export declare const CANVA_CONSTANTS: { /** Cloud API prefix for the Canva grant (appended to the cloud URL) */ readonly CLOUD_PATH: "/api/cloud/canva"; readonly CLOUD_ENDPOINTS: { /** GET → { connected, canvaUserId, displayName, scopes, grantedAt } */ readonly STATUS: "/status"; /** GET → { accessToken, expiresAt, scopes, canvaUserId, canvaTeamId?, displayName? } */ readonly TOKEN: "/token"; /** GET ?token=&returnUrl= → 302 to Canva consent */ readonly START: "/start"; /** DELETE CLOUD_PATH itself → { removed } */ readonly DISCONNECT: ""; /** POST { authorizedBy?, sharing? } → { authorizedBy, sharing } — who owns the grant and who it is shared with (issue #968) */ readonly SHARING: "/sharing"; }; /** Canva Connect REST base */ readonly API_BASE: "https://api.canva.com/rest/v1"; readonly TOKEN_REFRESH_MARGIN_MS: 60000; readonly REQUEST_TIMEOUT_MS: 20000; /** Default / ceiling for design listing per call */ readonly DESIGNS_DEFAULT_LIMIT: 25; readonly DESIGNS_LIMIT_CEILING: 100; /** Export / upload jobs: poll cadence and ceiling */ readonly JOB_POLL_INTERVAL_MS: 1500; readonly JOB_POLL_TIMEOUT_MS: 120000; /** Largest asset accepted for upload (bytes) */ readonly ASSET_MAX_BYTES: number; /** Preset design types Canva accepts for `design_type.type = preset` */ readonly PRESET_DESIGN_TYPES: readonly string[]; /** Export formats */ readonly EXPORT_FORMATS: readonly string[]; /** Dashboard path the Cloud consent flow returns to */ readonly SETTINGS_RETURN_PATH: "/connections?platform=canva"; /** Error codes shared between the token service, controller and skills */ readonly ERROR_CODES: { readonly NOT_LOGGED_IN: "not_logged_in"; readonly NOT_CONNECTED: "not_connected"; readonly NOT_CONFIGURED: "not_configured"; readonly CANVA_ERROR: "canva_error"; readonly NETWORK: "network"; readonly VALIDATION: "validation"; }; }; /** * Crewly Apps (specs/2026-10-04-crewly-apps-p2.md): agents publish small web * apps to apps.crewlyai.com through this instance, and the owner's edits in * an app wake the agent that published it. */ export declare const CREWLY_APPS_CONSTANTS: { /** Agent API prefix on the Cloud URL (crewly-services apps/SPEC.md §3.1) */ readonly CLOUD_PATH: "/api/apps/v1"; /** Public origin apps are served from */ readonly APPS_ORIGIN: "https://apps.crewlyai.com"; /** Local registry of apps this instance published, under CREWLY_HOME */ readonly REGISTRY_DIR: "apps"; readonly REGISTRY_FILE: "registry.json"; /** Body limit for POST /api/apps/publish (25 MB of files as base64, plus JSON); parsed only after auth */ readonly PUBLISH_BODY_LIMIT: "36mb"; readonly PUBLISH_BODY_MAX_BYTES: number; /** A rejected publish declaring more than this closes the connection instead of draining the upload */ readonly PUBLISH_CLOSE_ABOVE_BYTES: number; /** The publish route, for the dedicated body parser registered before the global one */ readonly PUBLISH_ROUTE: "/api/apps/publish"; /** A file upload to an app (POST /api/apps/:appId/files): body cap here; Cloud applies its own, smaller one and its answer is passed through */ readonly UPLOAD_BODY_LIMIT: "10mb"; readonly UPLOAD_BODY_MAX_BYTES: number; /** Cloud request timeout (publish uploads can be large) */ readonly REQUEST_TIMEOUT_MS: 120000; /** Default entry file of a bundle */ readonly DEFAULT_ENTRY: "index.html"; /** Owner file uploads (request-file): how often the inbox is read, and the error backoff ceiling */ readonly FILE_REQUEST_POLL_MS: 15000; readonly FILE_REQUEST_MAX_BACKOFF_MS: number; /** One inbox request may take at most this long */ readonly FILE_REQUEST_TIMEOUT_MS: 20000; /** Change poller cadence and per-app error backoff ceiling */ readonly POLL_INTERVAL_MS: 30000; readonly POLL_MAX_BACKOFF_MS: number; /** One change-feed request may take at most this long (one slow app never stalls the rest) */ readonly POLL_REQUEST_TIMEOUT_MS: 20000; /** Apps polled at the same time */ readonly POLL_CONCURRENCY: 4; /** A batch keeps at most this many data changes (the newest); the count still covers all */ readonly MAX_BATCH_DATA_CHANGES: 200; /** A failed wake is retried after RETRY_BASE × 2^(n-1), up to RETRY_MAX */ readonly WAKE_RETRY_BASE_MS: 60000; readonly WAKE_RETRY_MAX_MS: number; /** After this many failed deliveries the orchestrator hears about it, once per batch */ readonly WAKE_FAILS_BEFORE_ORC_NOTICE: 3; /** Failed this many times (about 2 h with the backoff): the batch is dropped — the comments stay in the app — and the orchestrator is told once more (2026-10-22: 48 retries over 11 h for an agent that no longer existed) */ readonly WAKE_MAX_FAILURES: 12; /** * One delivery may take at most this long (it can wait behind other * deliveries to a busy agent). Past it the attempt counts as failed and the * batch is retried; a late success still settles the batch. */ readonly WAKE_DELIVER_TIMEOUT_MS: number; /** A batch older than this that has not reached its agent is logged and the orchestrator told (once) */ readonly WAKE_STUCK_NOTICE_MS: number; /** A new batch for an agent whose previous one is still being delivered waits this long, then checks again */ readonly WAKE_INFLIGHT_RECHECK_MS: 30000; /** One app's poll (every page plus the cursor write) may take at most this long */ readonly POLL_APP_DEADLINE_MS: number; /** A poll pass that has not finished after this long is abandoned and a fresh one starts */ readonly POLL_PASS_STALL_MS: number; /** Delivered seqs above the cursor kept for de-duplication after a restart */ readonly MAX_DELIVERED_SEQS: 2000; /** Pages of changes read per app per tick (P1 page = 200 changes) */ readonly POLL_MAX_PAGES: 10; /** P1's changes page size: a full page means more may follow */ readonly CHANGES_PAGE: 200; /** First wake-worthy change opens a window; everything in it is one message */ readonly BATCH_WINDOW_MS: 90000; /** No second wake for the same (app, agent) within this time after one */ readonly COOLDOWN_MS: number; /** Untrusted app text: per message character cap and messages per wake */ readonly MAX_EVENT_CHARS: 500; readonly MAX_EVENTS_PER_WAKE: 10; /** Data changes listed one by one in a wake before "… and N more" */ readonly MAX_DATA_CHANGES_LISTED: 15; /** * Signed open-links (specs/2026-10-04-crewly-apps-p3.md §1): Cloud mints * `https://apps.crewlyai.com/?k=`; the token is never shown * to an agent or written to a log. */ readonly OPEN_LINK: { /** Lifetime Cloud gives a link when none is asked for */ readonly DEFAULT_TTL_DAYS: 7; readonly MIN_TTL_DAYS: 1; readonly MAX_TTL_DAYS: 30; /** The query parameter that carries the token */ readonly TOKEN_PARAM: "k"; /** Shape of a link id (management calls) */ readonly LINK_ID_PATTERN: RegExp; /** What replaces a token in anything an agent or a log sees */ readonly REDACTED: "[redacted]"; }; /** * App thumbnails: a headless-Chrome screenshot of the published app, * uploaded to Cloud for the portal's Apps list. */ readonly THUMBNAIL: { readonly WIDTH: 390; readonly HEIGHT: 844; readonly VIRTUAL_TIME_BUDGET_MS: 6000; /** Time the app gets after `load` to read its data and render (CDP runner) */ readonly SETTLE_MS: 4000; /** The whole browser run is killed after this */ readonly TIMEOUT_MS: 60000; /** Cloud's limit for one thumbnail */ readonly MAX_BYTES: number; /** Width after shrinking (px) and JPEG quality */ readonly OUTPUT_WIDTH: 320; readonly JPEG_QUALITY: 80; /** Cloud's shortest link lifetime; it is revoked right after the capture */ readonly LINK_TTL_DAYS: 1; readonly MAC_BROWSERS: readonly ["/Applications/Google Chrome.app/Contents/MacOS/Google Chrome", "/Applications/Chromium.app/Contents/MacOS/Chromium"]; readonly LINUX_BROWSERS: readonly ["google-chrome", "google-chrome-stable", "chromium", "chromium-browser"]; readonly LINUX_FIXED_PATHS: readonly ["/usr/bin/google-chrome", "/usr/bin/chromium", "/usr/bin/chromium-browser", "/snap/bin/chromium"]; }; /** Public-app requests (P3 §2): collections per list, and the note's length */ readonly PUBLIC_REQUEST: { readonly MAX_COLLECTIONS: 20; readonly MAX_NOTE_CHARS: 500; /** * Words Cloud refuses in a public app's name (and the owner's display * name) at approval, 400 validation (crewly-services #33): they make a * phishing page look official. Single words match as case-insensitive * substrings; `sign in` / `log in` match as whole words with a space, * hyphen or underscore (or none) between them. */ readonly BLOCKED_NAME_WORDS: readonly ["crewly", "sign in", "sign-in", "login", "log in", "password", "verify", "account", "security", "support"]; }; /** * Visitor-triggered wakes (P3 §3): a stopped publisher is never started * for them, and each app wakes its agent for visitors at most this many * times per UTC day. Past the cap, submissions are counted, not delivered; * the next message delivered for the app says how many were skipped. */ readonly VISITOR_WAKE: { readonly MAX_PER_DAY: 20; /** A visitor-only batch for a stopped publisher waits; checked again this often */ readonly PENDING_RECHECK_MS: 60000; }; /** * App data returned to an agent (`/api/apps/:id/data…`) is sanitised for * display (P3 §4): every string value and key is stripped of control / * ANSI / bidi characters and its harness markers are disarmed. A single * string longer than MAX_STRING_CHARS (well above anything a form field * holds; P1 documents are ≤ 256 KB) is cut there with a visible note. * Nesting deeper than MAX_DEPTH (P1 caps it lower) is replaced by a note. */ readonly DATA_SANITIZE: { readonly MAX_STRING_CHARS: number; readonly MAX_DEPTH: 32; }; /** * Comments (crewly#1056): the owner taps an element in the app and writes * a comment; it reaches the publishing agent in `[APP CHANGES]` with the * element's anchor. Agents list, reply and resolve with `app-comments`. */ readonly COMMENTS: { /** One reply, in characters (Cloud's cap) */ readonly MAX_BODY_CHARS: 2000; /** Comment ids Cloud issues */ readonly ID_PATTERN: RegExp; /** Comment events listed one by one in a wake before "… and N more" */ readonly MAX_PER_WAKE: 10; /** Owner text shown per comment in a wake (the agent reads the rest with app-comments) */ readonly MAX_WAKE_BODY_CHARS: 800; /** Anchor fields shown in a wake */ readonly MAX_WAKE_SELECTOR_CHARS: 200; readonly MAX_WAKE_TEXT_CHARS: 120; /** * A comment for an app owned by a team or channel (crewly-services * apps/SPEC.md §15) is posted into that room after this short window (no * cooldown: each comment is its own thread there, and the room's rules * decide who is woken). */ readonly ROOM_BATCH_WINDOW_MS: 5000; /** Owner text shown in the room's (and its Slack channel's) thread */ readonly MAX_ROOM_BODY_CHARS: 2000; /** Sender label of the owner's comment rows in a room */ readonly ROOM_SENDER: "Owner (app comment)"; /** chat-v2 `metadata.source` of rows written for app comments in a room */ readonly ROOM_SOURCE: "app-comment"; }; /** * Voice comments (crewly-services apps/SPEC.md §16): the owner records audio * in the app's comment box; this instance downloads it when the comment is * delivered and tells the agent the local path to transcribe. */ readonly VOICE: { /** Where recordings are saved (relative to the Crewly home), next to Slack's downloaded files */ readonly DIR: "tmp/app-comment-audio"; /** Largest recording accepted (Cloud's cap is 5 MB; a little slack) */ readonly MAX_BYTES: number; /** One download */ readonly DOWNLOAD_TIMEOUT_MS: 30000; /** Recordings fetched for one wake (the rest are named with the command to fetch them) */ readonly MAX_PER_WAKE: 10; /** Recording ids Cloud issues */ readonly BLOB_ID_PATTERN: RegExp; /** File extension per audio type */ readonly EXT: Readonly>; }; /** * @mentions in comments (crewly-services apps/SPEC.md §12.1): this * instance pushes its agents to Cloud (`PUT /roster`) so the owner can * pick them after "@", and reads its mention inbox (`GET /mentions`) every * poll tick to wake the mentioned agents, wherever the app was published. */ readonly ROSTER: { /** Cloud's caps */ readonly MAX_AGENTS: 200; readonly MAX_NAME_CHARS: 60; readonly MAX_TEAM_CHARS: 80; /** Teams / channels / members per channel pushed with the roster (Cloud's caps; app owners, SPEC §15) */ readonly MAX_TEAMS: 100; readonly MAX_CHANNELS: 100; readonly MAX_CHANNEL_MEMBERS: 100; /** An unchanged roster is re-pushed this often (Cloud drops one silent for 14 days) */ readonly REFRESH_MS: number; }; readonly MENTIONS: { /** Cloud's mention inbox page size: a full page means more may follow */ readonly PAGE: 100; /** Pages read per tick */ readonly MAX_PAGES: 5; }; /** * Marketplace app templates (crewly-services apps/SPEC.md §17, * specs/2026-10-08-app-templates.md): an agent asks, the owner taps Yes on * a decision card, Cloud lists it. Agents find and use templates before * building a new app. */ readonly TEMPLATES: { /** Shape of a template id */ readonly ID_PATTERN: RegExp; /** How long the owner has to answer the card (nothing is listed without a yes) */ readonly DECISION_DEADLINE_MS: number; /** Cloud's caps on what the agent sends */ readonly MAX_DESCRIPTION_CHARS: 500; readonly MAX_TAGS: 8; readonly MAX_AUTHOR_CHARS: 60; /** Results find-app-template returns by default / at most */ readonly FIND_DEFAULT_LIMIT: 5; readonly FIND_MAX_LIMIT: 20; /** The Marketplace page a template's card links to (`template=` is appended) */ readonly MARKETPLACE_URL: "https://crewlyai.com/portal/marketplace?tab=apps"; }; /** Error codes the controller and skills share */ readonly ERROR_CODES: { /** A template's bundle holds something that looks like a secret or personal data (Cloud's scan) */ readonly UNSAFE_CONTENT: "unsafe_content"; readonly NOT_YOUR_APP: "not_your_app"; readonly NOT_LOGGED_IN: "not_logged_in"; readonly NO_INSTANCE: "instance_unknown"; readonly VALIDATION: "validation"; readonly NETWORK: "network"; }; }; /** * Microsoft To Do on the owner's account — Cloud holds the grant (see * services/auth microsoft.service; the grant is keyed `microsoft` so Outlook * mail / calendar can reuse it later), this instance talks to Microsoft * Graph directly. Backs the todo-* skills. */ export declare const MICROSOFT_TODO_CONSTANTS: { /** Connector id (role allowlist key, frontend catalog id) */ readonly CONNECTOR_ID: "microsoft-todo"; /** Cloud API prefix for the Microsoft grant (appended to the cloud URL) */ readonly CLOUD_PATH: "/api/cloud/microsoft"; readonly CLOUD_ENDPOINTS: { /** GET → { connected, microsoftUserId, displayName, email, scopes, grantedAt } */ readonly STATUS: "/status"; /** GET → { accessToken, expiresAt, scopes, microsoftUserId, displayName?, email? } */ readonly TOKEN: "/token"; /** GET ?token=&returnUrl= → 302 to Microsoft consent */ readonly START: "/start"; /** DELETE CLOUD_PATH itself → { removed } */ readonly DISCONNECT: ""; /** POST { authorizedBy?, sharing? } → { authorizedBy, sharing } — who owns the grant and who it is shared with (issue #968) */ readonly SHARING: "/sharing"; }; /** Microsoft Graph v1.0 base */ readonly GRAPH_BASE: "https://graph.microsoft.com/v1.0"; readonly TOKEN_REFRESH_MARGIN_MS: 60000; readonly REQUEST_TIMEOUT_MS: 20000; /** Default / ceiling for tasks listed per call (`$top`) */ readonly TASKS_DEFAULT_LIMIT: 50; readonly TASKS_LIMIT_CEILING: 100; /** Longest `Retry-After` (s) a 429 is waited out in-process before it is returned */ readonly RETRY_AFTER_MAX_WAIT_S: 10; /** Task / list title cap (characters) */ readonly TITLE_MAX_LENGTH: 255; /** Task note cap (characters) */ readonly NOTE_MAX_LENGTH: 4000; /** Note text shown per task in list output (characters) */ readonly NOTE_PREVIEW_LENGTH: 200; /** Accepted `importance` values */ readonly IMPORTANCE_VALUES: readonly string[]; /** Most steps (Graph `checklistItems`) one call may add to a task (#835) */ readonly STEPS_MAX_PER_CALL: 50; /** * Time zone written with a due date. To Do stores due *dates*; midnight * in UTC is what Microsoft's own samples send and reads back unchanged. */ readonly DUE_TIME_ZONE: "UTC"; /** `wellknownListName` of the list that answers when no list is named */ readonly DEFAULT_LIST_WELLKNOWN: "defaultList"; /** Dashboard path the Cloud consent flow returns to */ readonly SETTINGS_RETURN_PATH: "/connections?platform=microsoft-todo"; /** Error codes shared between the token service, controller and skills */ readonly ERROR_CODES: { readonly NOT_LOGGED_IN: "not_logged_in"; readonly NOT_CONNECTED: "not_connected"; readonly NOT_CONFIGURED: "not_configured"; /** Graph rejected the token even after a fresh one */ readonly UNAUTHORIZED: "unauthorized"; /** Graph 403 — no access (e.g. account without an Exchange Online mailbox) */ readonly FORBIDDEN: "forbidden"; /** List or task not found */ readonly NOT_FOUND: "not_found"; /** Graph 429 with a `Retry-After` beyond what we wait out */ readonly RATE_LIMITED: "rate_limited"; readonly MICROSOFT_ERROR: "microsoft_error"; readonly NETWORK: "network"; readonly VALIDATION: "validation"; }; }; /** * Constants for CrewlyAI Cloud integration. * Used by CloudClientService and CloudAuthMiddleware to connect * the open-source Crewly instance to CrewlyAI Cloud for premium features. */ export declare const CLOUD_CONSTANTS: { /** Default CrewlyAI Cloud API base URL (env: CREWLY_CLOUD_URL) */ readonly DEFAULT_CLOUD_URL: string; /** API version prefix for all cloud endpoints */ readonly API_VERSION: "/v1"; /** * Ask Cloud for a successor refresh token (`rotate: true`) once the one we * hold expires within this. Refresh tokens live 30 days and were never * renewed, so every machine went deaf 30 days after login (steamfun-ops, * 2026-10-19). Rotating only near the end keeps copies of the same token * (portal pairing) working until then — they would die at expiry anyway (ms). */ readonly REFRESH_ROTATE_WITHIN_MS: number; /** How many rotated-away refresh tokens the client remembers so a stale copy can never be put back */ readonly RETIRED_REFRESH_TOKENS_KEPT: 5; /** Cloud API endpoints */ readonly ENDPOINTS: { /** Authentication and token verification (matches crewly-auth /api/cloud/validate) */ readonly AUTH_TOKEN: "/api/cloud/validate"; /** Sync local config with cloud subscription status */ readonly SYNC: "/v1/cloud/sync"; /** List premium templates */ readonly TEMPLATES: "/v1/templates/premium"; /** Get template detail by ID */ readonly TEMPLATE_DETAIL: "/v1/templates/premium/:id"; }; /** HTTP request timeouts (ms) */ readonly TIMEOUTS: { /** Timeout for connect/auth requests */ readonly CONNECT: 10000; /** Timeout for fetching template lists */ readonly FETCH_TEMPLATES: 15000; /** Timeout for fetching template detail */ readonly FETCH_TEMPLATE_DETAIL: 10000; /** Timeout for status/sync requests */ readonly STATUS: 5000; }; /** Subscription tiers */ readonly TIERS: { readonly FREE: "free"; readonly PRO: "pro"; readonly ENTERPRISE: "enterprise"; }; /** Connection statuses */ readonly CONNECTION_STATUS: { readonly DISCONNECTED: "disconnected"; readonly CONNECTED: "connected"; readonly ERROR: "error"; /** Token expired or revoked — cloud API returned 401/403 */ readonly TOKEN_EXPIRED: "token_expired"; }; /** Cloud Relay API endpoints (relative to cloudUrl) */ readonly RELAY_ENDPOINTS: { /** List all devices registered to the authenticated user */ readonly DEVICES: "/api/v1/relay/devices"; }; }; /** * Constants for the Cloud Sync system. * Replaces the WebSocket Relay pairing model with heartbeat-based device * discovery and HTTP-polled messaging. * * @see docs/cloud-sync-design.md */ export declare const CLOUD_SYNC_CONSTANTS: { /** Interval between heartbeat uploads (ms) */ readonly HEARTBEAT_INTERVAL_MS: 30000; /** * Idle interval between message poll requests (ms). Used as the gap when * long-poll is disabled (`MESSAGE_LONGPOLL_WAIT_MS = 0`), after a poll * error, or as the auto-degrade fallback when the relay returns a held * long-poll near-instantly (i.e. it doesn't support `wait=`). */ readonly MESSAGE_POLL_INTERVAL_MS: 5000; /** * A message-poll cycle that hasn't finished for this long is stuck; the * heartbeat restarts the loop (see CloudSyncService.checkMessagePollAlive). * Comfortably above MESSAGE_LONGPOLL_TIMEOUT_MS. */ readonly MESSAGE_POLL_STALL_MS: 90000; /** * Long-poll hold time (ms) sent to the relay as `?wait=`. The relay holds * `/queue/poll` open until a message arrives (or this deadline), so the OSS * picks up Portal `chat_request`s near-instantly instead of on the old 5s * tick. Set to 0 to disable and fall back to fixed-interval polling. Kept * under the relay's own 25s ceiling. */ readonly MESSAGE_LONGPOLL_WAIT_MS: 20000; /** * HTTP request timeout (ms) for the long-poll message fetch. MUST exceed * {@link MESSAGE_LONGPOLL_WAIT_MS} so the client doesn't abort the held * connection before the relay's deadline returns it. */ readonly MESSAGE_LONGPOLL_TIMEOUT_MS: 30000; /** Gap (ms) before re-opening the next long-poll after one returns. */ readonly MESSAGE_LONGPOLL_GAP_MS: 100; /** Interval between device list poll requests (ms) */ readonly DEVICE_POLL_INTERVAL_MS: 30000; /** * Interval between queue/register re-registrations (ms). * * The relay's auto-pair only matches devices in `pairingWait`. If a * Portal that was paired with this OSS closes uncleanly (closed tab, * hard crash), the relay leaves OSS wedged `state: paired` against * the dead session — and every fresh Portal that registers afterward * joins pairingWait alone with no peer to match. The relay's * register-time stale-pair eviction frees us, but only on the next * register. We re-register on this interval so recovery is bounded. */ readonly REGISTER_INTERVAL_MS: 60000; /** * Retry gaps (ms) after a failed queue registration: 30 s, 1 min, 2 min, * 5 min, then the last value forever. Without a queue this machine * receives no Cloud or Slack messages, so it never gives up — but it * backs off so a relay that is refusing (quota, outage) is not hammered. */ readonly REGISTER_RETRY_BACKOFF_MS: readonly number[]; /** * Upper bound (ms) on honouring a relay 429 `Retry-After`. A daily-limit * 429 can ask for hours; we re-check at most this often so a limit that was * raised or reset is noticed without a restart. */ readonly RETRY_AFTER_MAX_MS: number; /** * A machine with no queue must stay without one this long before the owner * is told (ms). One 429 at restart usually clears on the next retry. */ readonly QUEUE_OUTAGE_NOTIFY_AFTER_MS: number; /** Device considered offline after this threshold (ms) */ readonly OFFLINE_THRESHOLD_MS: 60000; /** HTTP request timeout for sync API calls (ms) */ readonly REQUEST_TIMEOUT_MS: 15000; /** Maximum consecutive failures before entering error state */ readonly MAX_CONSECUTIVE_FAILURES: 10; /** Interval between error recovery attempts after entering error state (ms). * Periodically retries a heartbeat to check if Cloud is reachable again. */ readonly ERROR_RECOVERY_INTERVAL_MS: 60000; /** Maximum error recovery attempts before entering auth_expired terminal state */ readonly MAX_ERROR_RECOVERY_ATTEMPTS: 5; /** Cloud Sync API endpoints (relative to cloudUrl) — must match web project routes at /api/v1/relay/* */ readonly ENDPOINTS: { /** Device heartbeat/handshake — POST to Cloud Relay (registers device + updates metadata) */ readonly HEARTBEAT: "/api/v1/relay/handshake"; /** List all devices for account (auto-registers caller on GET) */ readonly DEVICES: "/api/v1/relay/devices"; /** Send a message to another device via Cloud message queue */ readonly MESSAGES: "/api/v1/relay/queue/send"; /** Poll for incoming messages (GET with ?queueId= or uses JWT deviceId) */ readonly MESSAGES_POLL: "/api/v1/relay/queue/poll"; /** Acknowledge processed messages */ readonly MESSAGES_ACK: "/api/v1/relay/queue/ack"; }; }; /** Subscription tier type */ export type CloudTier = (typeof CLOUD_CONSTANTS.TIERS)[keyof typeof CLOUD_CONSTANTS.TIERS]; /** Cloud connection status type */ export type CloudConnectionStatus = (typeof CLOUD_CONSTANTS.CONNECTION_STATUS)[keyof typeof CLOUD_CONSTANTS.CONNECTION_STATUS]; /** * Constants for CrewlyAI Cloud account authentication and licensing. * Used by AuthService and JwtAuthMiddleware for user registration, * login, JWT management, and plan-based feature gating. */ export declare const AUTH_CONSTANTS: { /** JWT configuration */ readonly JWT: { /** JWT secret (env: CREWLY_JWT_SECRET, falls back to dev default) */ readonly DEFAULT_SECRET: string; /** Access token expiry in seconds (1 hour) */ readonly ACCESS_TOKEN_EXPIRY_S: 3600; /** Refresh token expiry in seconds (30 days) */ readonly REFRESH_TOKEN_EXPIRY_S: 2592000; /** JWT algorithm identifier */ readonly ALGORITHM: "HS256"; /** JWT issuer claim */ readonly ISSUER: "crewly-cloud"; }; /** User plans */ readonly PLANS: { readonly FREE: "free"; readonly PRO: "pro"; readonly ENTERPRISE: "enterprise"; }; /** Storage paths relative to ~/.crewly/ */ readonly STORAGE: { /** Directory for cloud user data */ readonly CLOUD_DIR: "cloud"; /** Directory for user accounts */ readonly USERS_DIR: "cloud/users"; /** File storing user index (email → id mapping) */ readonly USER_INDEX_FILE: "cloud/users/index.json"; }; /** Pro features list */ readonly PRO_FEATURES: readonly ["template-marketplace", "premium-templates", "priority-support"]; }; /** User plan type */ export type UserPlan = (typeof AUTH_CONSTANTS.PLANS)[keyof typeof AUTH_CONSTANTS.PLANS]; /** Device heartbeat constants for dual-machine connectivity */ export declare const DEVICE_CONSTANTS: { /** Time-to-live for device heartbeat (ms) */ readonly HEARTBEAT_TTL_MS: 60000; /** Storage directory for device state (relative to ~/.crewly/) */ readonly DEVICES_DIR: "cloud/devices"; /** Maximum teams per heartbeat payload */ readonly MAX_TEAMS_PER_HEARTBEAT: 50; }; /** * Constants for Supabase-backed Cloud Auth. * Used by CloudAuthService for user registration, login, * session management, and license verification via Supabase. * * Supabase credentials are read from env vars (CREWLY_SUPABASE_URL, * CREWLY_SUPABASE_ANON_KEY) with dev-project defaults as fallback. */ export declare const CLOUD_AUTH_CONSTANTS: { /** Google OAuth configuration for Cloud Console login */ readonly GOOGLE: { /** Google OAuth Client ID (env: GOOGLE_CLIENT_ID) */ readonly CLIENT_ID: string; }; /** MongoDB collections */ readonly COLLECTIONS: { readonly USERS: "users"; readonly SESSIONS: "sessions"; }; /** License statuses */ readonly LICENSE_STATUS: { readonly ACTIVE: "active"; readonly EXPIRED: "expired"; readonly CANCELLED: "cancelled"; }; }; /** * Constants for the Auditor Scheduler Service. * Controls periodic, event-driven, and API audit triggers. * * The auditor operates in always-active mode: initialized at server start, * remains alive between audits, and only shuts down when the service stops. */ export declare const AUDITOR_SCHEDULER_CONSTANTS: { /** Periodic full-sweep audit interval (15 minutes) */ readonly AUDIT_INTERVAL_MS: number; /** Debounce window for event-driven triggers (30 seconds) */ readonly EVENT_DEBOUNCE_MS: 30000; /** Maximum time a single audit run can take before timeout (10 minutes). * Note: timeout does NOT shutdown the runtime (always-active mode). */ readonly AUDIT_TIMEOUT_MS: number; /** Session name for the auditor agent */ readonly AUDITOR_SESSION_NAME: "crewly-auditor"; /** Event types that trigger an audit run */ readonly TRIGGER_EVENT_TYPES: readonly string[]; /** Command message sent to the auditor agent to start an audit cycle */ readonly AUDIT_COMMAND: "Run a full audit cycle: check team status, review agent logs for errors, verify task alignment with goals, and write findings to the audit report."; /** Slack auditor prefix regex pattern (used in SlackOrchestratorBridge) */ readonly SLACK_PREFIX_PATTERN: RegExp; /** Maximum session creation retries before entering cooldown */ readonly MAX_SESSION_RETRIES: 3; /** Cooldown period after max retries exhausted (5 minutes) */ readonly SESSION_RETRY_COOLDOWN_MS: number; /** Initial backoff between session creation retries (10 seconds) */ readonly SESSION_RETRY_INITIAL_BACKOFF_MS: 10000; /** Backoff multiplier for exponential retry */ readonly SESSION_RETRY_BACKOFF_MULTIPLIER: 2; /** Delay before re-triggering after auditor's own session goes inactive (30 seconds). * This bridges the gap between the auditor exiting and the next L1 periodic trigger * (up to 15 min away), ensuring the auditor recovers quickly after context exhaustion. */ readonly SESSION_RECOVERY_DELAY_MS: 30000; }; /** Log rotation service constants for managing session log file sizes */ export declare const LOG_ROTATION_CONSTANTS: { /** Maximum size for active session logs before truncation (20MB) */ readonly MAX_LOG_SIZE_BYTES: number; /** Maximum size for orphan logs (no active session) before truncation (5MB) */ readonly ORPHAN_LOG_MAX_SIZE_BYTES: number; /** Days to retain archived log files before deletion */ readonly ARCHIVE_RETENTION_DAYS: 7; /** Interval between rotation checks in milliseconds (1 hour) */ readonly LOG_ROTATION_INTERVAL_MS: number; /** Whether to archive old log content before truncation */ readonly LOG_ROTATION_ARCHIVE_ENABLED: true; /** Directory name for session logs under ~/.crewly/logs/ */ readonly SESSIONS_LOG_DIR: "sessions"; /** Directory name for archived logs under ~/.crewly/logs/ */ readonly ARCHIVE_DIR: "archive"; /** Base log directory under ~/.crewly/ */ readonly LOGS_DIR: "logs"; }; /** OpenTelemetry tracing configuration constants */ export declare const TRACING_CONSTANTS: { /** Default OTLP exporter endpoint */ readonly DEFAULT_EXPORTER_ENDPOINT: "http://localhost:4318/v1/traces"; /** Default service name reported in traces */ readonly DEFAULT_SERVICE_NAME: "crewly-backend"; /** Environment variable names */ readonly ENV_VARS: { readonly ENABLED: "OTEL_ENABLED"; readonly ENDPOINT: "OTEL_EXPORTER_ENDPOINT"; readonly SERVICE_NAME: "OTEL_SERVICE_NAME"; }; /** Span names for key operations */ readonly SPANS: { readonly HTTP_REQUEST: "http.request"; readonly AGENT_CREATE_SESSION: "agent.createSession"; readonly AGENT_SEND_MESSAGE: "agent.sendMessage"; readonly AGENT_REGISTER: "agent.register"; readonly TASK_CREATE: "task.create"; readonly TASK_UPDATE: "task.update"; readonly TASK_ASSIGN: "task.assign"; readonly AUDIT_TRIGGER: "audit.trigger"; readonly SCHEDULER_RUN: "scheduler.run"; readonly MEMORY_RECALL: "memory.recall"; readonly MEMORY_STORE: "memory.store"; readonly TASK_COMPLETE: "task.complete"; readonly AGENT_RUN: "agent.run"; }; }; /** * Agent self-improvement wiring (attention / self-model / prediction * calibration / memory consolidation). The four services under * `services/ai/self-improvement/` were built and tested but never consumed; * these constants govern the API, the daily consolidation cadence, and the * bounded prompt injection that finally puts them to work. */ export declare const SELF_IMPROVEMENT_CONSTANTS: { /** State file (under CREWLY_HOME) recording the last consolidation sweep */ readonly STATE_FILE: "self-improvement-state.json"; /** How often the consolidation sweep runs (24 h) */ readonly CONSOLIDATION_INTERVAL_MS: number; /** Env override for the consolidation interval (positive integer ms) */ readonly CONSOLIDATION_INTERVAL_ENV: "CREWLY_SELF_IMPROVEMENT_INTERVAL_MS"; /** Delay before the boot-time catch-up run so startup I/O settles first */ readonly BOOT_RUN_DELAY_MS: number; /** Prompt injection caps — keep the self-model card small and stable */ readonly PROMPT: { /** Max focus items rendered in the prompt */ readonly MAX_FOCUS_ITEMS: 5; /** Max suppressed topics rendered in the prompt */ readonly MAX_SUPPRESSED_ITEMS: 5; /** Max consolidation insights rendered in the prompt */ readonly MAX_INSIGHTS: 3; /** Hard character ceiling for the whole self-model section */ readonly MAX_CHARS: 600; /** Calibration below this → "you are overconfident" guidance */ readonly LOW_CALIBRATION: 0.5; /** Calibration at/above this → "well calibrated" guidance */ readonly HIGH_CALIBRATION: 0.8; }; /** Outcome keywords that resolve a prediction without an explicit `accurate` flag */ readonly OUTCOME_KEYWORDS: { readonly ACCURATE: readonly ["correct", "accurate", "true", "yes", "confirmed", "right"]; readonly INACCURATE: readonly ["incorrect", "inaccurate", "false", "no", "wrong", "missed"]; }; }; /** * Daily owner receipt (#828): one phone-readable message every evening with * every ask of the window, its outcome and link, what waits on the owner, and * what it cost. See specs/owner-receipt.md. */ export declare const OWNER_RECEIPT_CONSTANTS: { /** Default local send time (HH:MM, 24h) */ readonly DEFAULT_TIME: "21:00"; /** Default owner time zone (same default as team-health off-hours) */ readonly DEFAULT_TIMEZONE: "America/New_York"; /** How often the scheduler checks whether it is time (ms) */ readonly TICK_INTERVAL_MS: number; /** Settings + last-sent state, under ~/.crewly */ readonly STATE_FILENAME: "owner-receipt.json"; /** An ask is shortened to this many characters (CJK count double) */ readonly MAX_ASK_WEIGHTED_LENGTH: 56; /** A waiting-on-you question is shortened to this many characters */ readonly MAX_QUESTION_WEIGHTED_LENGTH: 90; /** Ask lines in the Slack message before "另有 N 件" (phone length) */ readonly MAX_ASK_LINES: 30; /** Waiting-on-you lines before "另有 N 件" */ readonly MAX_WAITING_LINES: 12; /** 「可能漏记」 lines: appended messages that still read like a request (#828 coverage) */ readonly MAX_POSSIBLY_MISSED: 5; /** Team label for a ticket nobody is assigned to */ readonly UNASSIGNED_TEAM: "Unassigned"; /** * The redesigned receipt (owner, 2026-09-28: 14 asks + 17 「等你拍板」 with * ticket numbers was overwhelming): at most this many 「今天做完的」 lines… */ readonly MAX_HIGHLIGHTS: 3; /** …and this many 「需要你决定的」 lines; the rest is 「另有 N 件,在看板上」 */ readonly MAX_DECISIONS: 3; /** A deliverable older than this is no longer put in front of the owner (ms) */ readonly DECISION_MAX_AGE_MS: number; /** One receipt line is cut (at a phrase boundary) to this weighted length */ readonly MAX_LINE_WEIGHTED_LENGTH: 64; /** A summary line shorter than this (weighted) says nothing; the next line is used */ readonly MIN_SUMMARY_WEIGHTED_LENGTH: 12; /** How the orchestrator is named on the receipt (it is in no team) */ readonly ORCHESTRATOR_LABEL: "Orc"; /** * The format ask (#856 follow-up): while the nightly receipt is off, the * owner is shown one real sample of it on a decision card and chooses. * Nothing is turned on without his "Turn on" answer. */ readonly FORMAT_ASK: { /** Decision kind whose handler applies the answer */ readonly DECISION_KIND: "owner_receipt_format"; /** `system.key` of the decision */ readonly SYSTEM_KEY: "nightly-format"; /** The sample covers this much time before the ask (ms) */ readonly SAMPLE_WINDOW_MS: number; /** Unanswered by then: the default (stay off) is applied (ms) */ readonly DEADLINE_MS: number; readonly TITLE: "Nightly receipt · try this format?"; readonly QUESTION: "This is what tonight's receipt would look like. Send it to you every night?"; readonly OPTION_ON: "Turn on nightly — this format"; readonly OPTION_PER_ASK: "Keep per-ask format — every ask, one line each"; }; /** * The weekly "how often agents pulled the owner in" line (#1387, first * slice). Specs: specs/owner-pulls-weekly.md. */ readonly PULLS_WEEKLY: { /** Local weekday it goes out with the receipt (0 = Sunday) */ readonly REPORT_WEEKDAY: 0; /** The week counted: the 7 days before the report; compared with the 7 before that (ms) */ readonly WINDOW_MS: number; /** Teams named on the line before "+N more" */ readonly MAX_TEAMS: 3; /** Traces read for the owner-touch count (most recent first) */ readonly MAX_TRACES: 500; /** Log of agent-started posts in the owner's DM (JSON lines, in the Requests dir) */ readonly POST_LOG_FILENAME: "owner-pulls-posts.jsonl"; /** The post log is rewritten with only the last two weeks once it grows past this (bytes) */ readonly POST_LOG_MAX_BYTES: number; /** A post this close to a card the same agent opened is that card's post, counted once (ms) */ readonly CARD_POST_WINDOW_MS: number; /** Agents named on the "Top sources" line */ readonly MAX_SOURCES: 3; /** Owner activity is counted in buckets of this size: each bucket with a message counts as this many active minutes (ms) */ readonly ACTIVE_BUCKET_MS: number; /** Owner messages read (timestamps only) for the active-minutes estimate */ readonly MAX_OWNER_MESSAGES: 50000; }; /** * Daily spot-check of verified WorkItems (#1315, phase 1): re-check that the * file artifacts recorded as evidence still exist. Never changes a WorkItem. */ readonly VERIFIED_SPOT_CHECK: { /** WorkItems sampled per day */ readonly SAMPLE_SIZE: 5; /** Only items completed this recently are eligible (ms) */ readonly WINDOW_MS: number; /** Discrepancies named on the owner line before "+N more" */ readonly MAX_LISTED: 3; /** * Directory markers of agent worktrees the disk janitor removes after merge * (the segment after a marker is the worktree root). A missing file inside * a worktree that is gone is "worktree cleaned", not a discrepancy. */ readonly WORKTREE_MARKERS: readonly string[]; }; }; /** How each pull kind reads on the owner's weekly line (#1387). */ export declare const PULLS_WEEKLY_KIND_LABELS: { readonly decision: "decision needed"; readonly review: "review or approval"; readonly fyi: "FYI that did not need an answer"; readonly stuck: "agent stuck or error"; }; /** * Hours a ticket waits in 待验收 with no objection before silence accepts it * (default 24). */ export declare const REVIEW_AUTO_ACCEPT_HOURS = 24; /** * Ticket loop (specs/ticket-loop.md, Phase 1): owner messages become tickets * (a `Request` with a TKT number), receipts go back where the owner spoke, * and WorkItems link back to the ticket. */ export declare const TICKET_CONSTANTS: { /** Display prefix: `TKT-042` */ readonly NUMBER_PREFIX: "TKT-"; /** Minimum digits in a displayed ticket number (zero-padded) */ readonly NUMBER_PAD: 3; /** Counter file inside the requests dir (no `.json`, so listAll never reads it) */ readonly COUNTER_FILENAME: ".ticket-counter"; /** * #828 coverage: one JSON line per owner message intake handled * (created / appended / ignored + reason), next to the Request files. The * first line records when counting started, so a window that begins * earlier is reported as unknown (不详), never as 0. */ readonly INTAKE_LOG_FILENAME: ".intake-outcomes.jsonl"; /** Tag every ticket carries */ readonly TAG: "ticket"; /** Tag added when the owner said "don't track" */ readonly DISMISSED_TAG: "dismissed"; /** * Weighted minimum length a message needs to be a ticket. CJK characters * count double: "fix the build" (13) passes, and so does "把首页改成蓝色" * (7 CJK = 14), while "好的收到" (8) does not. */ readonly MIN_WEIGHTED_TEXT_LENGTH: 12; /** Tag on a ticket an agent split out of another (#827) */ readonly SPLIT_TAG: "split"; /** A bare "don't track" reply dismisses the ticket it answers */ readonly DISMISS_PATTERN: RegExp; /** * Ask classifier (#827): is an owner message a new ask, or a follow-up on * the thread's current deliverable? Deterministic, text only. Every * pattern below was taken from real owner messages of 2026-09-25/26 — see * ticket-ask-classifier.test.ts for the labelled set. Scoring and the * tie rule live in ticket-ask-classifier.ts. */ readonly ASK: { /** A request verb: something to go and do (+2 ask) */ readonly REQUEST_VERB: RegExp; /** * An explicit request construction (+3 ask instead of +2). Survives the * long-discussion damping: 「你能不能帮我…总结成一个MD文档」 inside a * voice transcript is still an ask. */ readonly STRONG_REQUEST: RegExp; /** A question that asks for information (+1 ask; on its own, a question ticket) */ readonly INFO_QUESTION: RegExp; /** The message points at something to look at: a link, an image, a file path */ readonly REFERENCE: RegExp; /** A bare 「看看」 (look) — an ask only when it has a reference to look at */ readonly LOOK: RegExp; /** Opens a new topic, near the start (+1 ask) */ readonly NEW_TOPIC: RegExp; /** Suggests something new to do (+1 ask) */ readonly IDEA: RegExp; /** First line is a numbered reply to the agent's list (+3 follow) */ readonly NUMBERED_REPLY: RegExp; /** * How the CURRENT deliverable should be delivered (+3 follow). Needs a * delivery verb: a bare `00-plan.md` path is not an instruction. */ readonly DELIVERY_FORMAT: RegExp; /** Clarifies or corrects the work in progress (+2 follow) */ readonly CLARIFY: RegExp; /** Feedback on the current draft (+3 follow) */ readonly FEEDBACK: RegExp; /** Asks the agent about its own work or plan (+2 follow) */ readonly ABOUT_AGENT_WORK: RegExp; /** * Opens by correcting or choosing (+3 follow): 「不对 我要你研究的是…」, * 「不是前面…」, 「方案A」 — a reply to the agent, never a new ask. */ readonly CORRECTION_OR_CHOICE: RegExp; /** 「…给我看看吗」: show me the current thing (+3 follow) */ readonly SHOW_ME_TAIL: RegExp; /** Retry / continue the current work (+3 follow): 「你再看看」「按你说的继续挖」 */ readonly RETRY_CONTINUE: RegExp; /** Ends as a suggestion about the current work (+3 follow): 「…添加一些截图吧?」 */ readonly SUGGEST_TAIL: RegExp; /** * What to do with the thing just discussed (+3 follow, in a thread): * park it, keep it, remind me — 「A 论文那个 开个Issue吧 放到backlog」 * 「加到flopost的backlog」「好的 存下来」「提醒我明天做这件事」. * The owner deciding about the agent's output, not a new unit of work * (owner, 2026-09-28: these had become tickets of their own). */ readonly DISPOSITION: RegExp; /** Asks where things stand (+3 follow; top level: not an ask) */ readonly STATUS_PING: RegExp; /** * A line that approves what the agent proposed: starts with an ack and * ends in approval ("好的 开issue可以的", "好的 部署吧") (+3 follow) */ readonly APPROVAL_LINE: RegExp; /** A line that is only an acknowledgement (ignored when scoring the rest) */ readonly ACK_ONLY_LINE: RegExp; /** * Weighted length above which a message reads as spoken discussion (a * voice transcript): a follow-up unless it opens a new topic (+2 follow) */ readonly LONG_DISCUSSION_WEIGHTED_LENGTH: 300; /** Ends like a question (a verb-less question in a thread needs one) */ readonly QUESTION_MARK: RegExp; /** Quoted text is content, not a request: 接住“能不能帮我做” asks nothing */ readonly QUOTED: RegExp; /** Minimum ask score for a new ask; it must also beat the follow score (ties append) */ readonly MIN_ASK_SCORE: 2; }; /** * A top-level "不用记" (no thread) dismisses the latest open ticket from the * same conversation if it was opened this recently (ms). */ readonly DISMISS_LOOKBACK_MS: number; /** How long a caller waits for intake before delivering without a marker (ms) */ readonly INTAKE_TIMEOUT_MS: 3000; /** Slack Block Kit action id of the receipt's dismiss button */ readonly SLACK_DISMISS_ACTION_ID: "ticket_dismiss"; /** Receipt texts */ readonly RECEIPT: { readonly RECORDED: (tkt: string) => string; readonly DISMISSED: (tkt: string) => string; /** chat-v2 receipt once the ticket is accepted */ readonly DONE: (tkt: string) => string; /** Slack receipt: this reaction on the owner's message (no reply, no notification) */ readonly REACTION: "ticket"; /** Slack: the 🎫 becomes this once the ticket is accepted */ readonly DONE_REACTION: "white_check_mark"; /** * Receipts are off (owner, 2026-09-24): tickets are Crewly's own record * of the work — no 🎫 / ✅ / "Recorded as TKT-…" in the owner's conversations. */ readonly ENABLED: false; }; /** Done / cancelled tickets leave the board after this long (Phase 3) */ readonly ARCHIVE: { readonly AFTER_MS: number; /** Subdirectory of the requests dir the files move to (never deleted) */ readonly DIRNAME: "archive"; }; /** * Review (Phase 2): an agent's answer puts a ticket in 待验收; the owner * accepts (验过了) or sends it back (打回 + reason). Silence accepts. */ readonly REVIEW: { /** Origins whose tickets close without the owner (cron, missions) */ readonly NO_REVIEW_ORIGINS: readonly string[]; /** * Intent categories whose answer is the whole deliverable — a shared * link, a note, "按你说的来". They close when answered instead of * waiting for an OK (owner, 2026-09-25: 19 tickets sat in 待验收, most * of them "[Message] 看看这个 " already answered). */ readonly NO_REVIEW_CATEGORIES: readonly string[]; /** Tag on a ticket that closed because nobody objected in time */ readonly AUTO_ACCEPTED_TAG: "auto_accepted"; /** * 待验收 this long after the answer with no word from the owner → * accepted (ms). A hard deadline from `submittedAt`, whatever the nudges * did (owner, 2026-09-28: 43 tickets sat in 待验收; the old 24h → nudge → * 24h → nudge → 24h chain took 72h+ and restarted on every re-answer). */ readonly AUTO_ACCEPT_MS: number; /** Why a ticket was accepted without the owner's word (ticket discussion note + log) */ readonly AUTO_ACCEPT_REASON: "auto-accepted: no objection"; /** Author of that note */ readonly AUTO_ACCEPT_NOTE_AUTHOR: "crewly"; /** * The agent that answered asks the owner itself (owner, 2026-09-24): * with no word from the owner this long after the answer, the agent is * nudged once to follow up in the thread, before the auto-accept. */ readonly NUDGE_AFTER_MS: number; /** Nudges before silence counts as acceptance */ readonly MAX_NUDGES: 1; /** Tag on a ticket closed as soon as it was answered (nothing to review) */ readonly ANSWERED_TAG: "answered"; /** * The owner's ask names a deliverable someone has to look at before it * counts: a document, an email or a reply to send, a draft, a form, code, * a deploy, money, anything sent out in his name. Only these (or an * answer that asks him something) wait in 待验收; a plain answer closes. */ readonly DELIVERABLE_ASK: RegExp; /** Intent categories that always need a look (a change was made) */ readonly DELIVERABLE_CATEGORIES: readonly string[]; /** * The answer asks the owner something (a decision or an OK) — then the * ticket is waiting on him even without a deliverable. */ readonly OWNER_QUESTION: RegExp; /** * A plain acknowledgement from the owner in a ticket's thread while the * agent is waiting for their OK counts as the OK (「好的」「可以」「行」). */ readonly ACK_PATTERN: RegExp; /** An agent reply must be this old, with the agent idle, before it counts as the answer (ms) */ readonly SUBMIT_SETTLE_MS: number; /** Sweep for settled answers and auto-accepts (ms) */ readonly SWEEP_INTERVAL_MS: number; /** Longest excerpt of the answer kept on the ticket */ readonly REPLY_EXCERPT_MAX: 600; /** 「验过了」 and friends — a bare accept in the ticket's thread */ readonly VERIFY_PATTERN: RegExp; /** 「打回 」 — send it back; group 2 is the reason */ readonly REJECT_PATTERN: RegExp; /** Reason recorded when 打回 came without one */ readonly REJECT_NO_REASON: "(no reason given)"; /** WorkItem title for a rework sent from the board */ readonly REWORK_TITLE: (tkt: string) => string; }; /** * Follow-ups vs new tickets (owner, 2026-09-28): an answer to an agent's * question — 「发了」「是绿卡」「A 论文那个 开个Issue吧」 — belongs to the * conversation it answers, never to a ticket of its own. */ readonly FOLLOW_UP: { /** * A thread whose ticket finished (answered, accepted) this recently * still takes follow-ups into that ticket instead of opening a new one. */ readonly RECENT_TICKET_MS: number; /** A chase matches only tickets with activity in this window */ readonly CHASE_MATCH_WINDOW_MS: number; /** * In a DM (every message is top level) a reply this soon after the * conversation's latest ticket activity is a follow-up to that ticket. */ readonly DM_WINDOW_MS: number; /** Origins where every message is top level (DM-like conversations) */ readonly DM_ORIGINS: readonly string[]; /** * A reply this short (weighted, CJK double) is an answer, not a new * ask, unless it says 「帮我…」 outright: 「可以改到10:30吗」. */ readonly SHORT_REPLY_WEIGHTED_LENGTH: 24; }; /** * Tickets nobody touched for this long are closed as stale (owner, * 2026-09-28): open / running with no activity; the agent reopens one by * answering in its thread. */ readonly STALE: { readonly AFTER_MS: number; /** Tag on a ticket closed as stale */ readonly TAG: "stale"; /** Statuses that go stale */ readonly STATUSES: readonly string[]; /** Discussion note left on a ticket closed as stale */ readonly NOTE: "Closed automatically after 3 days with no activity. The responsible agent can reopen it by replying in the original conversation."; /** Author of that note */ readonly NOTE_AUTHOR: "crewly"; }; /** * WorkItem statuses that no longer hold a ticket open once the ticket is * accepted: a verify that was rejected and superseded by its retry, a * failed attempt. The #467 gate treats them as open, which kept accepted * tickets out of done for good (TKT-017: two `rejected` WorkItems). */ readonly DEAD_WORK_ITEM_STATUSES: readonly string[]; /** Metadata key the dispatcher reads the delivered-message marker from */ readonly MESSAGE_MARKER_METADATA_KEY: "ticketMarker"; /** Metadata key on a chat-v2 receipt row */ readonly RECEIPT_METADATA_KEY: "ticketReceipt"; /** Header a client may send to say it is the mobile app */ readonly CLIENT_HEADER: "x-crewly-client"; /** {@link CLIENT_HEADER} value sent by the mobile app / mobile relay */ readonly MOBILE_CLIENT: "mobile"; }; /** * One-time task-pool archive run by the ticket loop (specs/ticket-loop.md §4). */ export declare const POOL_ARCHIVE_CONSTANTS: { /** Marker file in the task-pool dir; present = migration already ran */ readonly MARKER_FILENAME: ".archived-2026-09-ticket-loop"; /** Archive dir inside the task-pool dir */ readonly ARCHIVE_DIRNAME: "archive"; /** Archive file name prefix; the date (YYYY-MM-DD) and `.json` follow */ readonly ARCHIVE_FILE_PREFIX: "pool-archive-"; /** Items older than this are eligible (ms) */ readonly MIN_AGE_MS: number; /** Terminal statuses that are archived once old enough */ readonly TERMINAL_STATUSES: readonly ["verified", "done", "failed", "cancelled"]; }; /** * Ongoing pruning of the task pool (crewly#1108). The pool file is rewritten * whole on every write, and finished items and ended claims were never * removed (13 MB, 5,600 claims): writes queued behind each other. Old * finished items and ended claims move to the dated archive file. */ export declare const POOL_PRUNE_CONSTANTS: { /** A finished item (and the ended claims on it) is kept in the live pool this long (ms) */ readonly RETENTION_MS: number; /** How often the prune runs after boot (ms) */ readonly INTERVAL_MS: number; }; /** * Harness onboarding (specs/onboarding-harness-login.md): detect, install and * log in to the agent CLIs ("harnesses") Crewly drives — Claude Code, Codex * and Gemini CLI. Shared by the backend REST API and the `crewly` CLI. */ export declare const HARNESS_CONSTANTS: { /** Harness ids; equal to the matching RUNTIME_TYPES values */ readonly IDS: { readonly CLAUDE_CODE: "claude-code"; readonly CODEX_CLI: "codex-cli"; readonly ANTIGRAVITY_CLI: "antigravity-cli"; readonly GEMINI_CLI: "gemini-cli"; }; /** * Harnesses kept working for existing and enterprise users but no longer * offered to new users: Gemini CLI stopped serving individual accounts on * 2026-06-18 (Antigravity CLI replaces it). Setup lists them only when * already in use. */ readonly RETIRED_IDS: readonly string[]; /** Appended to a retired harness's name wherever it is still listed */ readonly RETIRED_LABEL_SUFFIX: " (enterprise only)"; /** Harness the orchestrator uses when the owner does not choose */ readonly DEFAULT_ORC_HARNESS: "claude-code"; /** Short names accepted by `crewly login ` / `--harness ` */ readonly CLI_ALIASES: { readonly claude: "claude-code"; readonly codex: "codex-cli"; readonly antigravity: "antigravity-cli"; readonly agy: "antigravity-cli"; readonly gemini: "gemini-cli"; }; /** Credentials file under the Crewly home dir */ readonly CREDENTIALS_FILE: "harness-credentials.json"; /** File mode for the credentials file (owner read/write only) */ readonly CREDENTIALS_FILE_MODE: 384; /** User-owned npm prefix under the Crewly home dir, used when `npm install -g` hits EACCES */ readonly USER_NPM_PREFIX_DIR: "npm-global"; /** Extra bin dir where Claude Code's native installer puts `claude` (relative to $HOME) */ readonly NATIVE_INSTALLER_BIN_DIR: ".local/bin"; /** How long `npm view version` results are cached */ readonly LATEST_VERSION_CACHE_TTL_MS: number; /** How long a failed `npm view` is cached (so an offline machine is not re-probed per request) */ readonly LATEST_VERSION_FAILURE_TTL_MS: number; /** Timeout for short probe commands (which, --version, login status) */ readonly PROBE_TIMEOUT_MS: 15000; /** * Guard against probe pile-ups (500 hung `codex` on one Mac). Concurrent * timeout-bounded runs allowed per executable; beyond it `runCommand` * fails fast instead of spawning. */ readonly MAX_INFLIGHT_PER_EXECUTABLE: 4; /** Minimum gap between "executable is stuck" warnings, per executable (ms) */ readonly STUCK_WARN_INTERVAL_MS: number; /** Timeout for `npm view` */ readonly NPM_VIEW_TIMEOUT_MS: 20000; /** Timeout for one `npm install -g` attempt */ readonly INSTALL_TIMEOUT_MS: number; /** Install log kept per job (tail) */ readonly INSTALL_LOG_MAX_CHARS: 64000; /** Finished install jobs are forgotten after this long */ readonly INSTALL_JOB_RETENTION_MS: number; /** npm output that means "no permission to write the global prefix" */ readonly PERMISSION_ERROR_PATTERNS: readonly string[]; /** Version-like token in `--version` output */ readonly VERSION_PATTERN: RegExp; /** Length bounds for a pasted API key */ readonly API_KEY_MIN_LENGTH: 20; readonly API_KEY_MAX_LENGTH: 512; /** Login broker (PTY running the harness's own login command) */ readonly LOGIN: { /** A login session that has not finished by then is timed out */ readonly TIMEOUT_MS: number; /** Very wide PTY so login URLs are not wrapped at the terminal width */ readonly PTY_COLS: 1000; readonly PTY_ROWS: 50; /** Tail of normalized screen text exposed to front ends */ readonly SCREEN_MAX_CHARS: 2000; /** Raw PTY output kept per session (tail) */ readonly RAW_BUFFER_MAX_CHARS: 200000; /** Finished sessions stay readable for this long */ readonly SESSION_RETENTION_MS: number; /** Timeout for the post-login verification command */ readonly VERIFY_TIMEOUT_MS: 30000; /** After a failure line, a self-exiting harness (Codex) gets this long to exit before the session fails */ readonly FAILURE_EXIT_GRACE_MS: 10000; /** Longest (redacted) failure message written to the log */ readonly LOG_MESSAGE_MAX_CHARS: 300; /** Value for BROWSER in the broker env: a no-op command, so harnesses do not open a browser */ readonly BROWSER_SUPPRESS_VALUE: "true"; /** Replaces anything that looks like a token or key in exposed screen text */ readonly REDACTED: "[redacted]"; /** A line that is a piece of a captured secret is redacted when at least this long */ readonly REDACT_MIN_FRAGMENT: 8; /** A URL line at least this long that ends at end-of-line may continue on the next line */ readonly WRAPPED_LINE_MIN_LENGTH: 40; /** Keys typed after user input */ readonly ENTER: "\r"; /** * Pause between typing the user's input and pressing Enter (ms). Ink TUIs * (Claude's `auth login`) read one write of "code + \r" as a paste and * swallow the Enter, leaving the code sitting at the prompt * (iriss-air.lan, 2026-10-01). */ readonly SUBMIT_DELAY_MS: 300; /** If the login printed nothing this long after Enter, press Enter once more (ms) */ readonly SUBMIT_RETRY_MS: 4000; }; /** * Re-login over Slack (Phase 2): an expired harness login is noticed in * agent output or by a periodic status check, a broker login is started * and the owner finishes it from their phone. */ readonly RELOGIN: { /** At most one owner reminder per harness in this window while the owner has not acted */ readonly REMIND_INTERVAL_MS: number; /** How often the orchestrator's harness login state is checked */ readonly STATUS_CHECK_INTERVAL_MS: number; /** A broker screen with no URL, code or prompt after this long is sent to the owner as-is */ readonly UNRECOGNISED_SCREEN_MS: 20000; /** Expiry reports for a harness are ignored this long after its login succeeded (resumed transcripts repeat old errors) */ readonly POST_SUCCESS_QUIET_MS: number; /** After a reported "expiry" turned out to be a logged-in harness, ignore reports this long */ readonly NOT_EXPIRED_QUIET_MS: number; /** A second expiry within this window after a silent API-key recovery falls back to the phone login */ readonly SILENT_KEY_RETRY_WINDOW_MS: number; /** Bounds for an owner reply that is taken as Claude's authorization code */ readonly CODE_MIN_LENGTH: 16; readonly CODE_MAX_LENGTH: 512; /** Longest reply typed into an unrecognised login screen */ readonly SCREEN_REPLY_MAX_LENGTH: 512; /** Owner replies that start the login over (compared trimmed, case-insensitive) */ readonly RETRY_KEYWORDS: readonly string[]; /** * Bare owner replies that start the sign-in of every signed-out harness * (compared trimmed, case-insensitive, trailing punctuation dropped). * Only taken while a harness is known to be signed out. */ readonly LOGIN_KEYWORDS: readonly string[]; /** First re-reminder for a harness that stays signed out; doubles each time */ readonly REMIND_BACKOFF_BASE_MS: number; /** Longest gap between re-reminders */ readonly REMIND_BACKOFF_MAX_MS: number; /** A live sign-in probe result is reused this long */ readonly PROBE_CACHE_MS: number; /** A harness in use that looks signed in is probed for real this often */ readonly PROBE_INTERVAL_MS: number; /** Longest a sign-in probe may run */ readonly PROBE_TIMEOUT_MS: 90000; /** Persisted notice / backoff state under CREWLY_HOME */ readonly STATE_FILENAME: "harness-relogin-state.json"; /** Tail of the (redacted) login screen included in a DM */ readonly DM_SCREEN_MAX_CHARS: 1500; /** Longest broker message quoted in a DM */ readonly DM_MESSAGE_MAX_CHARS: 300; /** Waiting agents named in a DM; the rest are counted */ readonly DM_MAX_LISTED_AGENTS: 8; }; /** * Owner-requested logins (「重新登录 claude」 in the orc DM, or the orc's * `harness-login` skill). specs/onboarding-harness-login.md "Owner-triggered login". */ readonly OWNER_LOGIN: { /** Longest DM (normalised) the deterministic trigger considers; longer text goes to the orc */ readonly MAX_TRIGGER_LENGTH: 60; /** How far back an owner message may ask for the login the orc's skill starts */ readonly EVIDENCE_LOOKBACK_MS: number; /** Owner messages read for that evidence */ readonly EVIDENCE_MAX_MESSAGES: 50; }; /** Claude Code facts */ readonly CLAUDE: { readonly OAUTH_TOKEN_ENV: "CLAUDE_CODE_OAUTH_TOKEN"; readonly API_KEY_ENV: "ANTHROPIC_API_KEY"; readonly CONFIG_DIR_ENV: "CLAUDE_CONFIG_DIR"; /** `~/.claude.json` (or `$CLAUDE_CONFIG_DIR/.claude.json`) */ readonly CONFIG_FILE: ".claude.json"; readonly DATA_DIR: ".claude"; readonly CREDENTIALS_FILE: ".credentials.json"; /** macOS keychain item Claude Code stores its login under */ readonly KEYCHAIN_SERVICE: "Claude Code-credentials"; /** Claude Code approves a custom API key by its last N characters */ readonly API_KEY_APPROVAL_SUFFIX_LENGTH: 20; readonly API_KEY_PREFIX: "sk-ant-"; /** Endpoint used to check a pasted Anthropic API key */ readonly API_KEY_CHECK_URL: "https://api.anthropic.com/v1/models"; readonly API_VERSION: "2023-06-01"; readonly API_KEY_CHECK_TIMEOUT_MS: 10000; /** * Live sign-in probe: one tiny print-mode turn. `claude auth status` * only reports whether a credential is *stored* (an expired or revoked * login still says loggedIn), so it cannot see an expiry; a real turn * answers "Not logged in · Please run /login" / "OAuth token revoked" * when the login is gone. No session file is written. */ readonly PROBE_ARGS: readonly string[]; /** * More of the owner's own Claude Code accounts on this machine, each * with its own config dir (`CLAUDE_CONFIG_DIR`) and login, used as * runtime fallbacks (`claude-code@`). Issue #942. */ /** * Retiring the Claude sign-in tokens older Crewly versions saved * (claude-token-migration.service.ts). */ readonly TOKEN_MIGRATION: { /** Bookkeeping under CREWLY_HOME (no credential in it) */ readonly STATE_FILE: "claude-token-migration.json"; /** First run after the backend starts */ readonly START_DELAY_MS: 60000; /** How often saved tokens are re-checked (a login done by hand is noticed) */ readonly RUN_INTERVAL_MS: number; }; readonly ACCOUNTS: { /** Directory under CREWLY_HOME holding one config dir per account */ readonly DIR: "claude-accounts"; /** Separator of a fallback chain entry: `claude-code@work` */ readonly TARGET_SEPARATOR: "@"; /** Account names: lower-case letters, digits, `-`, `_` */ readonly NAME_PATTERN: RegExp; /** Words that cannot be account names (they read as part of "claude code account …") */ readonly RESERVED_NAMES: readonly string[]; /** Claude's user settings file, copied from the default login when an account is created */ readonly SETTINGS_FILE: "settings.json"; /** `.claude.json` keys copied from the default login (the owner's own earlier answers) */ readonly COPIED_CONFIG_KEYS: readonly string[]; }; }; /** Codex CLI facts */ readonly CODEX: { readonly HOME_ENV: "CODEX_HOME"; readonly HOME_DIR: ".codex"; readonly AUTH_FILE: "auth.json"; /** * Launch flag that keeps a Codex TUI off the shared background * `codex app-server` daemon (0.157+). Without it every Codex agent on a * machine ran its shell commands inside the one daemon the first agent * started, so they all carried that agent's CREWLY_SESSION_NAME. */ readonly NO_DAEMON_FLAG: "--no-daemon"; /** Flags that already pick the app server themselves (leave the command alone) */ readonly APP_SERVER_SELECT_FLAGS: readonly string[]; /** Timeout for `codex --help` when checking that the flag exists */ readonly HELP_PROBE_TIMEOUT_MS: 10000; /** A Codex without the flag is re-checked after this long (it may be upgraded) */ readonly NO_DAEMON_PROBE_RETRY_MS: number; }; /** Antigravity CLI facts (API key only; see ANTIGRAVITY_CONSTANTS) */ readonly ANTIGRAVITY: { /** Login source when Crewly holds the key */ readonly STORED_KEY_SOURCE: "crewly-api-key"; /** Longest installer script accepted (the official one is ~8 KB) */ readonly INSTALL_SCRIPT_MAX_BYTES: number; /** Timeout for downloading the installer script */ readonly INSTALL_SCRIPT_FETCH_TIMEOUT_MS: 30000; /** Shell the official installer is written for */ readonly INSTALL_SHELL: "bash"; }; /** Gemini CLI facts (detect only) */ readonly GEMINI: { readonly OAUTH_CREDS_FILE: ".gemini/oauth_creds.json"; readonly KEY_ENV: readonly string[]; }; /** System tools Crewly needs (tmux is not one: sessions use node-pty) */ readonly SYSTEM_TOOLS: { readonly JQ: { readonly ID: "jq"; readonly INSTALL_HINT_MACOS: "brew install jq"; readonly INSTALL_HINT_LINUX: "sudo apt-get install -y jq (Fedora: sudo dnf install -y jq)"; }; }; /** Orchestrator config under the Crewly home dir (same file StorageService uses) */ readonly ORCHESTRATOR_CONFIG_SEGMENTS: readonly string[]; /** Web setup page opened by `crewly onboard --web` */ readonly WEB_SETUP_PATH: "/setup"; }; /** * First-run checklist and starter teams (specs/onboarding-harness-login.md, * Phase 3): harness → first team → first task → Cloud → Slack. */ export declare const ONBOARDING_CONSTANTS: { /** Checklist state (dismissed flag, blank choice, first-task record) under the Crewly home dir */ readonly STATE_FILE: "onboarding.json"; /** Checklist step ids, in display order */ readonly STEP_IDS: { readonly HARNESS: "harness"; readonly TEAM: "team"; readonly FIRST_TASK: "first_task"; readonly CLOUD: "cloud"; readonly SLACK: "slack"; }; readonly STEP_ORDER: readonly string[]; /** Starter id of "Blank": the orchestrator only, no team */ readonly BLANK_STARTER_ID: "blank"; /** Display copy for the Blank starter (templates carry their own) */ readonly BLANK_STARTER: { readonly NAME: "Blank"; readonly LABEL: "Blank"; readonly TAGLINE: "Just the Orc (the orchestrator), no team yet. Ask the Orc to put a team together when you need one."; readonly SUGGESTIONS: readonly string[]; }; /** Longest first task accepted */ readonly FIRST_TASK_MAX_LENGTH: 4000; /** First line of the message the orchestrator receives for a first task */ readonly FIRST_TASK_HEADER: "[Initial setup · first task]"; /** Chat metadata `source` of a first task sent from setup */ readonly FIRST_TASK_SOURCE: "onboarding_first_task"; /** Query parameter that opens `/setup` at a step (e.g. `/setup?step=cloud`) */ readonly WEB_STEP_QUERY: "step"; /** Crewly Cloud sign-in that works from a phone */ readonly CLOUD: { /** Cloud auth route that starts Google sign-in and redirects back with ?token=&refreshToken= */ readonly GOOGLE_START_PATH: "/api/cloud/google/start"; /** Portal page that shows the token + refresh token for copy-paste */ readonly CLI_TOKEN_PATH: "/cloud/cli-token"; /** The web app's callback page that hands the token to this backend */ readonly WEB_CALLBACK_PATH: "/auth/callback"; /** Portal base URL (env: CLOUD_CONSOLE_URL) */ readonly CONSOLE_URL: string; }; }; /** * Solution bundles: sellable team templates that deploy a whole working * setup in one step (specs/solution-bundles.md). */ export declare const BUNDLE_CONSTANTS: { /** The only manifest schema version this engine understands */ readonly SCHEMA_VERSION: 1; /** Deployment state per template, under the Crewly home dir */ readonly STATE_DIR: "bundles"; /** Key of the team built from the template's own `roles` */ readonly MAIN_TEAM_KEY: "main"; /** Joins template id and team key into the id of an extra team */ readonly TEAM_ID_SEPARATOR: "--"; /** Extra template directories (path-delimited), e.g. Crewly Pro's config/templates */ readonly TEMPLATE_DIRS_ENV: "CREWLY_TEMPLATE_DIRS"; /** Bundle status that may be deployed without `allowDraft` */ readonly STATUS: { readonly READY: "ready"; readonly DRAFT: "draft"; }; /** Apply steps, in the order they run */ readonly STEP_IDS: { readonly TEAM: "team"; readonly NORMS: "norms"; readonly SKILLS: "skills"; readonly CONNECTORS: "connectors"; readonly SLACK: "slack"; readonly SCHEDULES: "schedules"; readonly FIRST_WEEK: "first_week"; }; readonly STEP_ORDER: readonly string[]; /** Owner-facing step labels */ readonly STEP_LABELS: Readonly>; /** Why a step is waiting (status `pending`) */ readonly PENDING_REASONS: { readonly SLACK_NOT_CONNECTED: "slack_not_connected"; readonly BACKEND_NOT_RUNNING: "backend_not_running"; readonly CONNECTORS_MISSING: "connectors_missing"; }; /** Why a step did not run (status `skipped`) */ readonly SKIP_REASONS: { readonly TEAM_FAILED: "team_failed"; readonly NOTHING_TO_DO: "nothing_to_do"; }; /** Timezone of schedules and first-week tasks when the bundle names none */ readonly DEFAULT_TIMEZONE: "Asia/Shanghai"; /** Time of day a later first-week task is handed over when it names none */ readonly DEFAULT_FIRST_WEEK_TIME: "09:00"; /** Last day (0-based) a first-week task may be scheduled on */ readonly MAX_FIRST_WEEK_DAY: 6; /** Chat metadata `source` of a first-week task */ readonly FIRST_WEEK_SOURCE: "bundle_first_week"; /** First line of a first-week task the orchestrator receives */ readonly FIRST_WEEK_HEADER: "[Solution bundle · week one]"; /** How often the backend delivers due first-week tasks and resumes waiting deployments */ readonly TICK_INTERVAL_MS: number; /** How often `crewly deploy-bundle` polls a running backend's job */ readonly CLI_POLL_INTERVAL_MS: 1500; /** How long `crewly deploy-bundle` waits for a running backend's job */ readonly CLI_JOB_TIMEOUT_MS: number; /** Owner-facing connections page; `?platform=` opens a connector */ readonly CONNECTIONS_PATH: "/connections"; /** Connector ids a bundle may require (the /connections cards) */ readonly CONNECTOR_IDS: readonly string[]; /** Google products a bundle may name on `google-workspace` */ readonly GOOGLE_PRODUCTS: readonly string[]; /** Hosted server tiers (ops/marketing/2026-09-template-deploy/00-plan.md §3) */ readonly SERVER_TIERS: readonly string[]; /** Placeholders the engine fills without a question */ readonly BUILTIN_PLACEHOLDERS: readonly string[]; /** Longest accepted answer */ readonly MAX_ANSWER_LENGTH: 2000; /** Joins a multi-select answer when it fills a placeholder */ readonly MULTISELECT_JOINER: "、"; /** Norm written from the bundle's review points */ readonly REVIEW_POINTS_NORM_ID: "owner-review-points"; /** Norms / SOP folders inside a team directory (see get-team-norms, get-sops) */ readonly NORMS_DIR: "norms"; readonly SOPS_DIR: "sops"; /** Default SOP category folder */ readonly DEFAULT_SOP_CATEGORY: "team"; }; /** License status type */ export type CloudLicenseStatus = (typeof CLOUD_AUTH_CONSTANTS.LICENSE_STATUS)[keyof typeof CLOUD_AUTH_CONSTANTS.LICENSE_STATUS]; /** * On-demand skill setup (specs/skill-auto-install.md). * * A skill's `skill.json` may declare a `setup` block (system commands, files * such as models, Python packages). The setup runner performs it idempotently; * `find-skill` / `install-skill` let an agent find and install an official * skill, with its dependencies, as a background job. */ export declare const SKILL_SETUP_CONSTANTS: { /** Directory under the Crewly home for setup state (locks) */ readonly STATE_DIR: "skill-setup"; /** Subdirectory of STATE_DIR holding one lock file per skill */ readonly LOCKS_SUBDIR: "locks"; /** Directory under the Crewly home for per-skill setup logs */ readonly LOG_DIR: "logs/skill-setup"; /** Directory under the Crewly home for Crewly-managed binaries (e.g. whisper-cli on Linux) */ readonly BIN_DIR: "bin"; /** Directory under the Crewly home for per-skill Python virtualenvs (`venv/`) */ readonly VENV_DIR: "venv"; /** Directories always searched for commands, after the process PATH */ readonly EXTRA_COMMAND_DIRS: readonly ["/opt/homebrew/bin", "/usr/local/bin", "/usr/bin", "/bin", "/usr/sbin", "/sbin"]; /** Where Homebrew lives when it is not on PATH */ readonly BREW_CANDIDATES: readonly ["/opt/homebrew/bin/brew", "/usr/local/bin/brew", "/home/linuxbrew/.linuxbrew/bin/brew"]; /** File whose presence marks a Debian-family Linux (apt-get available) */ readonly DEBIAN_MARKER_FILE: "/etc/debian_version"; /** Timeout for a check command (`bash -c`, venv import probe) */ readonly CHECK_TIMEOUT_MS: 30000; /** Timeout for one package-manager install (brew / apt-get / pip) */ readonly PACKAGE_INSTALL_TIMEOUT_MS: number; /** Timeout for a skill-provided install script (may build from source) */ readonly SCRIPT_INSTALL_TIMEOUT_MS: number; /** Abort a download that receives no bytes for this long */ readonly DOWNLOAD_STALL_TIMEOUT_MS: number; /** Log download progress every this many percent */ readonly DOWNLOAD_PROGRESS_STEP_PERCENT: 10; /** Extra free space required beyond a file's size before downloading it (fraction) */ readonly DOWNLOAD_FREE_SPACE_MARGIN: 0.1; /** How often a waiting setup re-checks another process's lock */ readonly LOCK_POLL_MS: 2000; /** How long a setup waits for another process's lock before giving up */ readonly LOCK_WAIT_MS: number; /** A lock older than this is treated as abandoned even if its pid is alive (pid reuse) */ readonly LOCK_STALE_MS: number; /** Keep this many characters of output per install job (tail) */ readonly JOB_LOG_MAX_CHARS: 20000; /** Forget finished install jobs after this long */ readonly JOB_RETENTION_MS: number; /** Minutes quoted to the user when a skill declares setup but no estimate */ readonly DEFAULT_ESTIMATED_MINUTES: 3; /** Maximum candidates `find-skill` returns */ readonly FIND_MAX_RESULTS: 8; /** Candidates whose setup state is probed (checks only) per `find-skill` call */ readonly FIND_PROBE_LIMIT: 3; /** Registry authors whose entries in an official registry count as official */ readonly OFFICIAL_AUTHORS: readonly ["Crewly Team", "crewly", "Crewly"]; /** How far back an owner "yes" counts for `--approved-by-owner` (ms) */ readonly OWNER_APPROVAL_LOOKBACK_MS: number; /** Chat conversation id used for install-completion system events */ readonly COMPLETION_CONVERSATION_ID: "system"; /** Header lines that open the completion message the requesting agent receives */ readonly COMPLETION_HEADERS: { readonly SUCCEEDED: "[SKILL INSTALLED]"; readonly FAILED: "[SKILL INSTALL FAILED]"; }; /** * Words that map a user's phrasing onto the vocabulary skills are tagged * with, for `find-skill` ranking ("voice message" should find a * transcription skill even though no skill is tagged "voice"). */ readonly QUERY_SYNONYMS: Readonly>; }; /** * Unified conversation log (specs/unified-conversations-cloud-store.md §A). * Column vocabularies for the first-class copies of what used to live only * in `chat_messages.metadata`. */ export declare const CHAT_SEARCH_CONSTANTS: { /** Default number of hits */ readonly DEFAULT_LIMIT: 10; /** Most hits one search returns */ readonly MAX_LIMIT: 30; /** Characters of a message kept in a hit */ readonly SNIPPET_CHARS: 300; /** Most keywords used (extra words are ignored) */ readonly MAX_TERMS: 6; }; export declare const CONVERSATION_LOG_CONSTANTS: { /** `chat_messages.source` values — which surface a message belongs to. */ readonly SOURCES: readonly ["slack", "crewly-chat", "cloud-talk", "google-chat", "telegram", "whatsapp", "system", "runtime"]; /** `chat_messages.direction` values: to an agent, from an agent, or neither. */ readonly DIRECTIONS: readonly ["in", "out", "internal"]; /** `chat_messages.sender_kind` values. */ readonly SENDER_KINDS: readonly ["owner", "agent", "human", "system"]; /** Channel id prefixes of messenger conversations (legacy rows carry no reliable source tag). */ readonly CHANNEL_PREFIXES: { readonly SLACK: "slack-"; readonly WHATSAPP: "whatsapp-"; readonly TELEGRAM: "telegram-"; readonly GOOGLE_CHAT: "gchat-"; }; /** Rows rewritten per transaction when backfilling legacy rows. */ readonly BACKFILL_BATCH_SIZE: 2000; /** Default / maximum page size of the per-agent timeline. */ readonly TIMELINE_DEFAULT_LIMIT: 50; readonly TIMELINE_MAX_LIMIT: 200; }; /** * Machine → Cloud conversation upload (specs/unified-conversations-cloud-store.md §B). * The wire contract itself lives in `services/cloud/conversation-ingest.contract.ts`. */ export declare const CONVERSATION_SYNC_CONSTANTS: { /** Kill switch: `CREWLY_CONVERSATION_SYNC=0` (or `off` / `false`) stops uploading. */ readonly ENV_SWITCH: "CREWLY_CONVERSATION_SYNC"; /** The spec's name for the same switch (`CREWLY_CLOUD_CONVERSATIONS=off`); honoured too. */ readonly ENV_SWITCH_ALT: "CREWLY_CLOUD_CONVERSATIONS"; /** Flush when this many messages are waiting. */ readonly BATCH_MAX_MESSAGES: 50; /** Flush when the batch reaches this many bytes of JSON (before gzip). */ readonly BATCH_MAX_BYTES: number; /** Flush a partial batch after it has waited this long (ms). */ readonly BATCH_MAX_WAIT_MS: 2000; /** Periodic wake-up even without new messages (ms). */ readonly TICK_INTERVAL_MS: 10000; /** First retry delay after a failure (ms); doubles up to the max. */ readonly BACKOFF_INITIAL_MS: 1000; /** Longest retry delay after repeated failures (ms) — 5 min. */ readonly BACKOFF_MAX_MS: number; /** Retry interval while Cloud has no ingest endpoint (404) or refused sync (403) — 1 h. */ readonly UNAVAILABLE_RETRY_MS: number; /** Rows read from chat_messages per backfill page. */ readonly BACKFILL_PAGE_SIZE: 500; /** Largest backfill request (bytes of JSON before gzip); Cloud accepts 8 MB inflated. */ readonly BACKFILL_BATCH_MAX_BYTES: number; /** At most one backfill request per this many ms (live batches go first). */ readonly BACKFILL_MIN_INTERVAL_MS: 1000; /** Retention assumed until Cloud reports one (days) — the free plan's window. */ readonly DEFAULT_RETENTION_DAYS: 7; /** Largest outbox kept while disconnected; older rows are dropped with a gap marker. */ readonly OUTBOX_MAX_ROWS: 200000; /** Outbox rows older than this are dropped (ms) — the longest plan window, 90 days. */ readonly OUTBOX_MAX_AGE_MS: number; /** Timeout of one ingest request (ms). */ readonly REQUEST_TIMEOUT_MS: 30000; /** `cloud_sync_state` keys. */ readonly STATE_KEYS: { readonly ACCOUNT_ID: "accountId"; readonly BACKFILL_DONE_AT: "backfillDoneAt"; readonly BACKFILL_CURSOR: "backfillCursor"; readonly BACKFILL_RETENTION_DAYS: "backfillRetentionDays"; readonly RETENTION_DAYS: "retentionDays"; readonly NOTICE_SENT_AT: "noticeSentAt"; readonly GAP_AT: "gapAt"; readonly LAST_INGEST_AT: "lastIngestAt"; }; /** One-time owner DM (O1) when history starts syncing. `{device}` is replaced. */ readonly NOTICE_TEXT: string; }; /** * Cloud Talk (specs/unified-conversations-cloud-store.md §D.3, Phase 3): the * owner talks to an agent from the Crewly Cloud portal; Cloud pushes a * `talk_message` through the relay and this machine records it in the * agent's DM (source `cloud-talk`) and wakes the agent. */ export declare const CLOUD_TALK_CONSTANTS: { /** Relay message `type` Cloud pushes for a Talk message. */ readonly RELAY_MESSAGE_TYPE: "talk_message"; /** Capability advertised to Cloud (heartbeat + uploads) once the handler runs. */ readonly CAPABILITY: "talk_message"; /** `GET /:messageId?instanceId=` fetches the text; `POST /:messageId/failed` refuses it. */ readonly MESSAGE_PATH: "/api/cloud/conversations/talk"; /** Delays before retrying a failed fetch (ms); Cloud re-pushes after 5 min anyway. */ readonly FETCH_RETRY_DELAYS_MS: readonly [1000, 5000, 15000]; /** Timeout of one Cloud call (ms). */ readonly REQUEST_TIMEOUT_MS: 15000; /** How often the uploader re-sends this machine's agent roster (ms). */ readonly ROSTER_INTERVAL_MS: number; /** Display name of the orchestrator in the roster (same as its Slack app). */ readonly ORCHESTRATOR_DISPLAY_NAME: "Crewly Orc"; /** Ticket-intake origin for a Talk message (the portal). */ readonly INTAKE_ORIGIN: "portal"; }; /** * Talk voice transcription (specs/2026-10-04-talk-whisper-transcribe.md, #1074): * the portal Talk page records a short clip, sends it over the relay to the * machine of the agent being talked to, and this machine transcribes it with * whisper.cpp — the same engine, binary and model the `transcribe-audio` skill * uses. Nothing is sent to the agent here: the text goes back to the phone for * the owner to check. */ export declare const TALK_TRANSCRIBE_CONSTANTS: { /** Skill whose setup block installs ffmpeg + whisper-cli + the model. */ readonly SKILL_ID: "transcribe-audio"; /** Capability advertised to Cloud (heartbeat) while whisper is ready. */ readonly CAPABILITY: "talk_whisper"; /** Model file (large-v3-turbo q5_0), same as the skill. */ readonly MODEL_FILENAME: "ggml-large-v3-turbo-q5_0.bin"; /** Model directories, in the skill's lookup order (after FLOPOST_WHISPER_MODEL). Relative to the home dir. */ readonly MODEL_DIRS: readonly [".flopost/whisper", ".cache/whisper-models"]; /** whisper-cli locations before PATH, in the skill's order (after FLOPOST_WHISPER_BIN). Relative to the home dir. */ readonly HOME_BIN_CANDIDATES: readonly [".flopost/whisper/whisper-cli"]; /** whisper.cpp CLI binary name. */ readonly WHISPER_BINARY: "whisper-cli"; /** ffmpeg binary name. */ readonly FFMPEG_BINARY: "ffmpeg"; /** * Largest clip accepted, in bytes of encoded audio. The relay refuses a * send body over 1 MiB and the clip travels base64 (4/3) inside it, so * 640 KiB of audio leaves room for the envelope. */ readonly MAX_AUDIO_BYTES: number; /** Longest clip accepted (s). The phone stops recording here too. */ readonly MAX_DURATION_SEC: 90; /** Whole request deadline, including the wait for the one-at-a-time slot (ms). */ readonly REQUEST_TIMEOUT_MS: 45000; /** Requests allowed to wait behind the running one; more are refused as `busy`. */ readonly MAX_QUEUE: 3; /** Sample rate whisper.cpp wants. */ readonly SAMPLE_RATE: 16000; /** Bytes per second of 16 kHz mono 16-bit PCM (for the duration of the WAV). */ readonly WAV_BYTES_PER_SEC: 32000; /** WAV header size (bytes) left off when measuring the duration. */ readonly WAV_HEADER_BYTES: 44; /** Threads for whisper-cli: CPU count minus this, never below MIN_THREADS. */ readonly THREADS_RESERVED: 2; readonly MIN_THREADS: 4; /** First sentence of the initial prompt: biases whisper to Simplified Chinese punctuation and script. */ readonly PROMPT_PREFIX: "以下是普通话的句子。"; /** Owner glossary always in the vocabulary prompt. */ readonly GLOSSARY: readonly ["Crewly", "CE", "Crewly Cloud", "Slack"]; /** Upper bound on the vocabulary part of the prompt (characters); whisper keeps ~224 prompt tokens. */ readonly MAX_VOCAB_CHARS: 400; /** Language hints the caller may pass (`auto` = whisper detects; zh/en mixed works). */ readonly LANGUAGES: readonly ["auto", "zh", "en"]; /** Audio container types accepted, mapped to the temp file extension ffmpeg sees. */ readonly MIME_EXTENSIONS: Readonly>; /** Temp dir prefix (under the OS temp dir); removed after every request. */ readonly TEMP_PREFIX: "crewly-talk-stt-"; /** Error codes the phone reacts to (any of them = fall back to on-device recognition). */ readonly CODES: { readonly WHISPER_UNAVAILABLE: "whisper_unavailable"; readonly TOO_LONG: "too_long"; readonly TIMEOUT: "timeout"; readonly FAILED: "failed"; readonly BUSY: "busy"; readonly INVALID_AUDIO: "invalid_audio"; readonly OWNER_ONLY: "owner_only"; }; }; /** * "Waiting on you" synced to Crewly Cloud (specs/unified-conversations-cloud-store.md §F, Phase 5): * tickets in 待验收 are uploaded as text snapshots; the owner's accept / * send-back from the portal comes back as a `waiting_action` relay push. */ export declare const WAITING_SYNC_CONSTANTS: { /** Machine → Cloud snapshot upload. */ readonly INGEST_PATH: "/api/cloud/conversations/waiting/ingest"; /** `GET /:actionId?instanceId=` fetches an action; `POST /:actionId/result` reports it. */ readonly ACTIONS_PATH: "/api/cloud/conversations/waiting/actions"; /** Relay message `type` Cloud pushes for an owner action. */ readonly RELAY_MESSAGE_TYPE: "waiting_action"; /** Capability advertised to Cloud (heartbeat + uploads) once the action handler runs. */ readonly CAPABILITY: "waiting_actions"; /** Gather ticket changes this long before uploading (ms). */ readonly DEBOUNCE_MS: 2000; /** Recompute the set this often and upload when it changed (catches WorkItem-driven changes) (ms). */ readonly CHECK_INTERVAL_MS: 30000; /** Full snapshot (Cloud drops anything not in it) at least this often (ms). */ readonly FULL_SYNC_INTERVAL_MS: number; /** Backoff after a failed upload: first delay and cap (ms). */ readonly BACKOFF_INITIAL_MS: 5000; readonly BACKOFF_MAX_MS: number; /** 404 / 503 / 400 from Cloud: try again after this long (ms). */ readonly UNAVAILABLE_RETRY_MS: number; /** Timeout of one Cloud call (ms). */ readonly REQUEST_TIMEOUT_MS: 15000; /** Longest title / excerpt sent (chars; Cloud truncates beyond its own limits too). */ readonly MAX_TITLE_CHARS: 500; readonly MAX_EXCERPT_CHARS: 2000; /** Delays before retrying a failed action fetch (ms); Cloud re-pushes after 5 min anyway. */ readonly FETCH_RETRY_DELAYS_MS: readonly [1000, 5000, 15000]; /** Actions already carried out are remembered this long, so a re-push only re-reports (ms). */ readonly DONE_ACTIONS_TTL_MS: number; /** Most remembered actions. */ readonly DONE_ACTIONS_MAX: 500; }; export type AgentStatus = (typeof CREWLY_CONSTANTS.AGENT_STATUSES)[keyof typeof CREWLY_CONSTANTS.AGENT_STATUSES]; export type WorkingStatus = (typeof CREWLY_CONSTANTS.WORKING_STATUSES)[keyof typeof CREWLY_CONSTANTS.WORKING_STATUSES]; export type RuntimeType = (typeof RUNTIME_TYPES)[keyof typeof RUNTIME_TYPES]; export type AgentId = string; /** * Secret redaction for persisted terminal output (session logs, scrub of old * logs and shell history). See utils/secret-redactor. */ export declare const SECRET_REDACTION_CONSTANTS: { /** * Secret variable names masked in `NAME=value` even if the generic suffix * rule (utils/secret-env isSecretEnvKey) ever changed. Upper-case. */ readonly KNOWN_SECRET_ENV_NAMES: readonly string[]; /** Name families masked in `NAME=value` (tested against the upper-cased name). */ readonly SECRET_NAME_PATTERNS: readonly RegExp[]; /** * Longest whitespace-free tail the streaming redactor holds back between * chunks before it forces a cut. Far above any credential's length. */ readonly MAX_CARRY_CHARS: 4096; /** Tail kept after a forced cut — above any credential's length (≈250 chars). */ readonly MIN_FORCED_CARRY_CHARS: 512; }; /** * Shell history is kept off in agent PTYs: whatever an agent shell runs * (and anything Crewly types into it) must never land in ~/.bash_history or * ~/.zsh_history. Two layers: * * - SPAWN_ENV is merged into every PTY's spawn environment. It works for a * shell whose rc files leave these alone. * - DISABLE_COMMAND is typed (space-prefixed) as the first line of the * runtime init sequence, after the rc files ran — macOS /etc/zshrc * unconditionally sets HISTFILE, which beats the spawn env. `unset HISTFILE` * stops bash and zsh from ever writing a history file; bash additionally * turns history recording off. * * HISTSIZE / HISTFILESIZE are deliberately NOT set to 0: when a user's * ~/.bashrc re-points HISTFILE at ~/.bash_history but leaves HISTFILESIZE * alone, bash truncates that file to HISTFILESIZE (defaulting to HISTSIZE) * lines — an inherited 0 would wipe the user's own history. */ export declare const SHELL_HISTORY_CONSTANTS: { /** Env merged into every PTY spawn (callers' own env still wins). */ readonly SPAWN_ENV: Readonly>; /** POSIX-shell line typed first into an agent shell (bash, zsh, sh, dash, ksh). */ readonly DISABLE_COMMAND: "unset HISTFILE; if [ -n \"$ZSH_VERSION\" ]; then setopt HIST_IGNORE_SPACE; SAVEHIST=0; elif [ -n \"$BASH_VERSION\" ]; then set +o history; fi"; /** fish: an empty fish_history keeps history in memory only. */ readonly FISH_DISABLE_COMMAND: "set -g fish_history ''"; /** Shells that understand DISABLE_COMMAND (basename of $SHELL). */ readonly POSIX_SHELLS: readonly string[]; /** Prefix for every line Crewly types into a shell (kept out of history by ignorespace / HIST_IGNORE_SPACE). */ readonly TYPED_LINE_PREFIX: " "; }; /** One-time scrub of secrets already on disk (`crewly security scrub-logs`). */ export declare const SECRET_SCRUB_CONSTANTS: { /** Shell history files (relative to the user's home) that are scrubbed */ readonly SHELL_HISTORY_FILES: readonly string[]; /** settings.json under CREWLY_HOME, whose apiKeys are masked by exact value */ readonly SETTINGS_FILE: "settings.json"; }; /** * Owner-facing Upgrade / Restart controls in the dashboard * (specs/2026-10-01-upgrade-restart-controls.md). */ export declare const SYSTEM_CONTROL_CONSTANTS: { /** Progress / outcome of the last upgrade or restart, under CREWLY_HOME */ readonly STATE_FILE: "system-action.json"; /** Log file under `/logs/` written by the detached replacement launcher */ readonly REPLACEMENT_LOG_FILE: "restart-replacement.log"; /** "When idle": longest wait for agents to finish their turns before going ahead anyway (ms) — 30 min */ readonly IDLE_WAIT_CAP_MS: number; /** "When idle": how often the busy check runs while waiting (ms) */ readonly IDLE_POLL_MS: number; /** Pause before shutting down so the HTTP answer reaches the dashboard (ms) */ readonly RESPONSE_FLUSH_MS: 500; /** A registry answer older than this is re-fetched when the owner asks to upgrade (ms) */ readonly REGISTRY_MAX_AGE_MS: number; /** Registry answer age accepted for the status read (ms) — matches auto-update */ readonly STATUS_REGISTRY_MAX_AGE_MS: number; /** Replacement launcher: how often it checks whether the old process is gone (ms) */ readonly REPLACEMENT_PID_POLL_MS: 500; /** Replacement launcher: give up waiting for the old process after this long (ms) — drain is ≤ 2 min */ readonly REPLACEMENT_MAX_WAIT_MS: number; /** Replacement launcher: grace before checking the port when nothing is expected to relaunch us (ms) */ readonly REPLACEMENT_PORT_GRACE_MS: number; /** Replacement launcher: grace when an unknown supervisor might relaunch us first (ms) */ readonly REPLACEMENT_PORT_GRACE_UNKNOWN_MS: number; /** Login item script that keeps `crewly start` running on the owner's Mac */ readonly LOGIN_WRAPPER_SCRIPT: "crewly-start.command"; /** systemd unit `crewly service install` writes */ readonly SYSTEMD_UNIT: "crewly.service"; /** Accepted values of the `when` body field */ readonly WHEN_VALUES: readonly ["idle", "now"]; /** Refusal codes returned with 4xx/5xx answers */ readonly CODES: { readonly OWNER_ONLY: "owner-only"; readonly DEV_CHECKOUT: "dev-checkout"; readonly NOT_NPM_GLOBAL: "not-npm-global"; readonly UP_TO_DATE: "up-to-date"; readonly IN_PROGRESS: "in-progress"; readonly RESTART_IN_PROGRESS: "restart-in-progress"; readonly REGISTRY_UNREACHABLE: "registry-unreachable"; readonly UNAVAILABLE: "unavailable"; readonly BAD_REQUEST: "bad-request"; readonly NOT_WINDING_DOWN: "not-winding-down"; }; /** Owner-facing messages (the dashboard shows these as-is; English only) */ readonly MESSAGES: { readonly OWNER_ONLY: "Only the owner can upgrade, restart or shut down Crewly. Agents cannot trigger this."; readonly DEV_CHECKOUT: "This machine runs Crewly from a source checkout — update it with git (git pull, npm run build), then restart."; readonly NOT_NPM_GLOBAL: "This copy of Crewly is not a global npm install, so it cannot upgrade itself. Update it the way it was installed."; readonly UNAVAILABLE: "Upgrade and restart controls are not ready yet — Crewly is still starting. Try again in a minute."; readonly REGISTRY_UNREACHABLE: "Could not reach the npm registry to find the latest version. Try again later."; readonly RESTART_IN_PROGRESS: "Crewly is already shutting down or restarting."; }; }; /** * Owner-facing wording of decision cards (crewly#1367, crewly#1369 §3): the * question in plain words in the owner's language, internal references only * in a short details line, context on merge cards. */ export declare const DECISION_WORDING_CONSTANTS: { /** Max characters of one card context field (who asked, what the owner said, what changes) */ readonly CONTEXT_FIELD_MAX_CHARS: 200; /** Max characters of the context link */ readonly LINK_MAX_CHARS: 300; /** Max references shown in the card's details line */ readonly DETAILS_MAX_REFS: 3; /** Max characters of the question quoted in a one-line reminder */ readonly REMINDER_QUESTION_MAX_CHARS: 60; /** Min letters (any script) left in a question once internal references are stripped */ readonly QUESTION_MIN_LETTERS: 4; }; /** * Decision cards (specs/2026-10-01-decision-cards.md): structured owner * questions posted by the responsible agent's own Slack bot as a Block Kit * card, answered with a button, a reaction or a thread reply. */ export declare const DECISION_CONSTANTS: { /** * Layout revision of the card blocks. Bump whenever the card layout changes * (buttons added/removed): open cards drawn with an older revision are * redrawn once at startup so they get the new controls (e.g. Skip, 1.20.185). */ readonly CARD_RENDER_REV: 4; /** Delay after startup before stale open cards are redrawn (ms) */ readonly STALE_CARD_REFRESH_DELAY_MS: 15000; /** Gap between two card redraws, to stay under Slack's chat.update rate limit (ms) */ readonly STALE_CARD_REFRESH_GAP_MS: 1200; /** Persisted decisions under CREWLY_HOME */ readonly STORE_FILENAME: "owner-decisions.json"; /** Persisted ticket → Slack thread map under CREWLY_HOME */ readonly TICKET_THREADS_FILENAME: "ticket-slack-threads.json"; /** Id prefix of a decision (`D-`) */ readonly ID_PREFIX: "D-"; /** Max characters of the question (one line) */ readonly QUESTION_MAX_CHARS: 280; /** Min characters of the question */ readonly QUESTION_MIN_CHARS: 8; /** Allowed option count */ readonly MIN_OPTIONS: 2; readonly MAX_OPTIONS: 5; /** Two yes/no asks on one ticket this close together that read as either/or are rejected (ms) */ readonly EITHER_OR_WINDOW_MS: number; /** A ticket's question shorter than this (characters) gets the ticket title prepended so the card says what is decided */ readonly CONTEXT_FREE_QUESTION_CHARS: 20; /** * A new card for a ticket withdraws the ticket's older open cards (one open card per ticket; TKT-357 had * 11 in 105 min). Cards made less than this long before the new one belong to the same reply and stay (ms) */ readonly SAME_REPLY_WINDOW_MS: number; /** Max characters of an option label (Slack button text max is 75) */ readonly OPTION_LABEL_MAX_CHARS: 40; /** Max characters of an option's optional detail line */ readonly OPTION_DETAIL_MAX_CHARS: 150; /** The `default` value meaning "do nothing until the owner answers" */ readonly WAIT_DEFAULT: "wait"; /** Local hour of the default deadline: the next day at this hour */ readonly DEFAULT_DEADLINE_HOUR_LOCAL: 12; /** Local hour the "Remind me tomorrow" reminder is posted */ readonly REMIND_HOUR_LOCAL: 9; /** After "Remind me tomorrow" the deadline is at least this long after the reminder (ms) */ readonly REMIND_GRACE_MS: number; /** Sensitive asks: re-asked once this long after the ask (or at the deadline, whichever is later) (ms) */ readonly SENSITIVE_REASK_AFTER_MS: number; /** Sensitive asks: parked this long after the re-ask (ms) */ readonly SENSITIVE_PARK_AFTER_REASK_MS: number; /** Deadline / reminder evaluation cadence (ms) */ readonly TICK_MS: number; /** A card Slack refused to post is retried at most this often (ms) */ readonly POST_RETRY_MS: number; /** Resolved decisions are kept this long, then pruned (ms) */ readonly RESOLVED_KEEP_MS: number; /** Sensitive categories (never auto-applied) */ readonly SENSITIVE_KINDS: readonly string[]; /** Block Kit action ids: `decision:` and the remind button */ readonly ACTION_PREFIX: "decision:"; readonly REMIND_ACTION_ID: "decision:remind"; /** The "Skip" button (`decision:skip`, button value option `skip`) */ readonly SKIP_ACTION_ID: "decision:skip"; readonly SKIP_OPTION: "skip"; /** Reactions: ✅ = default (or first) option, ❌ = a "no" option, ⏰ = remind tomorrow */ readonly REACTION_ACCEPT: readonly string[]; readonly REACTION_REJECT: readonly string[]; /** Reactions that skip a card: 🚫 and ⏭️ */ readonly REACTION_SKIP: readonly string[]; readonly REACTION_REMIND: readonly string[]; /** Option labels that read as "no" (lower-cased, matched on the whole label or its first word) */ readonly NO_WORDS: readonly string[]; /** Free-text replies that accept the default (or first) option */ readonly YES_WORDS: readonly string[]; /** Free-text replies that skip a card (checked before NO_WORDS) */ readonly SKIP_WORDS: readonly string[]; /** A skipped question is not asked again in the same request / ticket for this long (ms) */ readonly SKIP_DEDUPE_MS: number; /** Two questions at least this similar (character bigrams) in the same scope are the same skipped question */ readonly SKIP_SAME_QUESTION_SIMILARITY: 0.8; /** A `reply_question` card created this long after the agent asked is a backfilled card (legacy cards without `source`) (ms) */ readonly BACKFILL_CARD_MIN_LAG_MS: number; /** Free-text replies that snooze to tomorrow */ readonly REMIND_WORDS: readonly string[]; /** Questions too vague to put in front of the owner (whole question, lower-cased, trailing ?! stripped) */ readonly VAGUE_QUESTIONS: readonly string[]; /** Max decisions listed by the API at once */ readonly MAX_LISTED: 200; /** * `wait` default: the one "Still waiting on you" reminder is posted this long after * the asker was told the deadline passed — time for it to withdraw a moot card first * (specs/2026-10-02-decision-card-thread-answers.md §2) (ms) */ readonly WAIT_REMINDER_DELAY_MS: number; /** * `wait` default: a card still unanswered this long after its one reminder * (a lifted reply question: after the deadline notice, it never gets a * reminder) expires instead of staying open for days (#1243) (ms) */ readonly WAIT_EXPIRE_AFTER_REMINDER_MS: number; /** Max characters of a withdrawn card's reason ("Closed — ") */ readonly CLOSED_REASON_MAX_CHARS: 120; /** Withdraw reasons the harness itself uses (cancelWhere notes) */ readonly CLOSED_REASONS: { readonly TICKET_DONE: "ticket done"; readonly TICKET_CANCELLED: "ticket cancelled"; readonly HANDLED_IN_THREAD: "already handled in this thread"; /** `answered in D-7`: the same question, answered on another card */ readonly ANSWERED_IN_PREFIX: "answered in "; /** `answered by your reply to D-7`: the owner's words on another card moved the thread on */ readonly ANSWERED_BY_REPLY_PREFIX: "answered by your reply to "; /** The open item the card tracked expired (nothing happened for 7 days) */ readonly STALE: "no longer needed"; }; /** * Two questions with at least this character-bigram overlap, from the same * asker in the same thread / ticket / request, are one question: a new ask * reuses the open card, and an answer closes the other open ones. */ readonly SAME_OPEN_QUESTION_SIMILARITY: 0.6; /** A settled card Slack refused to update (buttons still live) is retried this often (ms) */ readonly CARD_SYNC_RETRY_MS: number; /** Open cards are checked this often for what they track having closed (then withdrawn silently) (ms) */ readonly MOOT_CHECK_MS: number; }; /** * Open items: commitments and questions agents put in their replies to the * owner (specs/2026-10-01-reply-open-items.md). */ export declare const OPEN_ITEMS_CONSTANTS: { /** Local hour a commitment that only says "tomorrow" is due */ readonly DEFAULT_DUE_HOUR_LOCAL: 12; /** A commitment with no time is due this long after it was made (ms) */ readonly DEFAULT_DUE_MS: number; /** * A commitment with no time made live in the owner's conversation (the * owner is waiting for the reply) is due this long after it was made (ms). * Backfilled and gate-opened promises keep DEFAULT_DUE_MS (crewly#1360). */ readonly CHAT_PROMISE_DEFAULT_DUE_MS: number; /** * An estimate the same agent states in the same thread this long after an * undated promise ("预计一小时左右" a minute after "改完发你") dates that * promise (ms) (crewly#1360) */ readonly ESTIMATE_WINDOW_MS: number; /** The same words by the same agent this close together are one item (a reply recorded twice) (ms) */ readonly DUPLICATE_WINDOW_MS: number; /** The same agent's two promises this close together, about the same deliverable, are one (the newer stands) (ms) */ readonly PROMISE_DUPLICATE_WINDOW_MS: number; /** Max commitments and max questions taken from one reply */ readonly MAX_ITEMS_PER_REPLY: 3; /** Max characters stored for an item's text */ readonly TEXT_MAX_CHARS: 300; /** Max characters of a question (the card's one line) */ readonly QUESTION_MAX_CHARS: 280; /** * A yes/no decision asked of the owner in prose ("should I close PR #12?", * "要不要合并?") is on file as an owner question due this long after it * was asked; with no answer by then the stall watcher follows up (ms) * (crewly#1369 gap 2). */ readonly OWNER_QUESTION_DUE_MS: number; /** * An open commitment to the owner keeps its agent from an idle stop for at * most this long after it was made (ms). Under memory pressure the agent is * stopped anyway and the stall watcher's promise ladder wakes it to report * (crewly#1386). Older promises must not pin memory. */ readonly IDLE_HOLD_MAX_AGE_MS: number; /** Child WorkItems created this long before the promise still count as what it waits on (ms) */ readonly CHILD_LOOKBACK_MS: number; /** ...and this long after it — the agent promises, then delegates (ms). Later work is a new topic. */ readonly CHILD_LOOKAHEAD_MS: number; /** With no child work, a later reply by the agent counts as the delivery only after this (ms) */ readonly MIN_DELIVERY_GAP_MS: number; /** A plain promise is delivered only by a post at least this long (characters) — not an ack */ readonly MIN_DELIVERY_CHARS: 12; /** After the overdue nudge, the owner is told when it is still undelivered this long later (ms) */ readonly OWNER_NOTE_AFTER_NUDGE_MS: number; /** An item still active this long after it was made is expired (ms) */ readonly EXPIRE_AFTER_MS: number; /** * A commitment older than this is "stale": before it is resurfaced (nudge, ready wake, restart * reminder) Crewly checks whether the owner already answered or the ticket is done, and the agent * is told to verify it is still needed before contacting the owner (ms) */ readonly STALE_VERIFY_AFTER_MS: number; /** Requests looked at by the sweep and the backfill: updated within (ms) */ readonly LOOKBACK_MS: number; /** The backfill only looks at promises and questions made within this long before it runs (ms) */ readonly BACKFILL_MAX_AGE_MS: number; /** * A post made up to this long before the child work's recorded finish still delivers it: a verify * pass overwrites `completedAt` after the agent has already posted (ms) */ readonly DELIVERY_FINISH_GRACE_MS: number; /** Sweep cadence (ms) */ readonly SWEEP_INTERVAL_MS: number; /** A conditional promise (waiting on the owner) with no gate anyone can answer is surfaced to the owner after this long (ms) */ readonly GATE_SURFACE_AFTER_MS: number; /** An ask-owner decision this close in time to the reply, by the same agent, is the same question (ms) */ readonly ASK_OWNER_DEDUPE_WINDOW_MS: number; /** Two questions with at least this character-bigram overlap are the same question */ readonly SAME_QUESTION_SIMILARITY: 0.5; /** Window (ms) for counting an agent's lifted-question cards (#1334) */ readonly REPLY_CARD_WINDOW_MS: number; /** At most this many lifted-question cards per agent per window; later questions are tracked without a card (#1334) */ readonly REPLY_CARDS_PER_AGENT_PER_DAY: 3; /** Option labels of a derived card */ readonly YES_LABEL: "Yes"; readonly NO_LABEL: "No"; readonly REPLY_LABEL: "Reply in thread"; /** A lifted question shorter than this (characters) gets its subject prepended (#1242) */ readonly QUESTION_MIN_CHARS: 20; /** Still shorter than this after the subject was added (characters): too vague for a card, tracked without one */ readonly QUESTION_VAGUE_CHARS: 12; /** Suffix of the option the agent recommended */ readonly RECOMMENDED_SUFFIX: " (recommended)"; /** A sentence that recommends something ("我推荐 A", "I recommend B") */ readonly RECOMMENDATION_PATTERN: RegExp; /** A recommendation that argues against doing it ("不建议", "don't recommend") */ readonly RECOMMENDATION_NEGATIVE_PATTERN: RegExp; /** A project ticket id named in a promise ("CE-16"); the work done under it fulfils the promise wherever it ran (crewly#1037) */ readonly PROJECT_TICKET_REF_PATTERN: RegExp; /** `WorkItem.metadata` key of a follow-up WorkItem */ readonly FOLLOW_UP_METADATA_KEY: "openItemFollowUp"; /** * `WorkItem.metadata` key naming the agent session that cancelled an item * through the cancel API (absent: the owner, or a caller with no session). * A promise whose follow-up another agent cancelled is told to the owner * (crewly#1015 §10). */ readonly CANCELLED_BY_METADATA_KEY: "cancelledBy"; /** The one line every agent prompt carries */ readonly PROMPT_LINE: string; /** * Max characters of the agent's own words quoted on a question card (the * paragraph(s) before the question). Every auto-extracted question card * carries context: a question lifted out of a message must stand on its own. */ readonly CONTEXT_EXCERPT_MAX_CHARS: 500; /** Max characters of a quoted / fenced block (a draft) the question refers to */ readonly CONTEXT_BLOCK_MAX_CHARS: 1200; /** Max characters of the "About:" line (the owner's original ask) */ readonly CONTEXT_ABOUT_MAX_CHARS: 200; /** * Words that make a question point back at earlier text ("这样安排行不行?", "按这个草稿回?", * "Does this plan work?"): its card prefers the text right before it and * quotes the block (draft) it points at (specs/2026-10-02-decision-card-thread-answers.md §5) */ readonly REFERS_BACK_PATTERNS: readonly RegExp[]; /** Words of a question about a draft: its card quotes the fenced / quoted block of the message */ readonly DRAFT_WORD_PATTERN: RegExp; }; /** * Where an agent's answer goes, by the work item it is doing * (specs/2026-10-01-decision-cards.md §6). */ export declare const WORK_ITEM_DESTINATION_CONSTANTS: { /** `WorkItem.metadata` key holding the work item's origin */ readonly METADATA_KEY: "origin"; /** An owner turn origin newer than this (ms) still counts as the current work when no work item is running */ readonly OWNER_ORIGIN_FRESH_MS: number; /** * An owner origin whose request is older than this (ms) is never inherited by * new work, explicit or not (crewly#1357: a delegation carried an owner DM * from nine days earlier). A verbatim `metadata.origin` in the add body is * not an inheritance and is kept as given. */ readonly ORIGIN_INHERIT_MAX_AGE_MS: number; /** Tag that opens the message a delegating agent gets with a result for work it handed out */ readonly AGENT_RESULT_TAG: "[RESULT]"; /** Max characters of the topic line of a new top-level post */ readonly TOPIC_MAX_CHARS: 120; /** The one line every prompt carries about where to answer */ readonly PROMPT_LINE: "Answer where you were asked; a new topic goes in a new thread."; }; /** * Team work the orchestrator delegated on the owner's behalf is reported to * the owner by the team's lead, in the team's Slack channel, as the lead * (crewly#1371). See `services/orc/team-lead-report.ts`. */ export declare const TEAM_LEAD_REPORT_CONSTANTS: { /** `WorkItem.metadata` key holding who reports the finished work and where */ readonly METADATA_KEY: "leadReport"; /** Answer key prefix of a `reply --work-item ` report (owner completion report store) */ readonly WORK_ITEM_ANSWER_PREFIX: "wi:"; }; /** * Finished owner-requested work is reported back where the owner asked * (2026-10-07: CREW-305/309/310 were all verified and nobody told the owner). * See `services/orc/owner-completion-report.service.ts`. */ export declare const OWNER_COMPLETION_REPORT_CONSTANTS: { /** How often the pool is scanned for finished owner chains (ms) */ readonly TICK_MS: 60000; /** A chain must stay finished this long before the report is asked for — the lead may still be handing out the next step (ms) */ readonly SETTLE_MS: number; /** * After the one nudge, the agent has this long to post its own report before * the harness posts one line for it (ms) (crewly#1357) */ readonly NUDGE_GRACE_MS: number; /** * A post of the responsible agent itself in the owner's place counts as its * report when it came this long after the work started (earlier ones are * its "on it" ack). Owen delivered and explained, closed his item two hours * later, and the owner then got an empty "Owen 把你要的事做完了" (2026-10-19 audit) (ms) */ readonly OWN_POST_AS_REPORT_AFTER_MS: number; /** Chains whose first item is older than this are never reported (ms) */ readonly MAX_CHAIN_AGE_MS: number; /** Finished chain records are forgotten after this long (ms) */ readonly RECORD_TTL_MS: number; /** Store file under CREWLY_HOME */ readonly STORE_FILE: "owner-completion-reports.json"; /** Titles named in a message (the rest are counted) */ readonly MAX_TITLES: 5; /** Max characters of one title in a message */ readonly TITLE_MAX_CHARS: 90; /** Max characters of one deliverable named in the fallback line */ readonly DELIVERABLE_MAX_CHARS: 90; /** Share of CJK characters in the owner's text above which the fallback line is written in Chinese */ readonly OWNER_LANGUAGE_CJK_MIN_RATIO: 0.2; /** How far back the owner's recent messages are read to pick the fallback line's language (ms) */ readonly OWNER_LANGUAGE_LOOKBACK_MS: number; /** How many recent owner messages are read for that */ readonly OWNER_LANGUAGE_SAMPLE: 30; /** An app comment / relayed owner request stays the agent's current request this long (ms) */ readonly CONTEXT_FRESH_MS: number; /** Max sessions the owner-request context registry remembers */ readonly MAX_CONTEXT_SESSIONS: 500; }; /** * Team leads delegate instead of doing the work (crewly#1083, * specs/2026-10-04-tl-delegation.md): owner-thread hand-over, the execution * nudge, lead share of team tokens, and "no member fits" records. */ export declare const TL_DELEGATION_CONSTANTS: { /** WorkItem.metadata / add-body field: the owner's Slack thread key a delegation answers */ readonly OWNER_THREAD_FIELD: "ownerThread"; /** WorkItem.metadata / add-body field: the owner's chat conversation a delegation answers (crewly#1357) */ readonly OWNER_CONVERSATION_FIELD: "ownerConversation"; /** A done item still lets its agent post in the owner's thread this long (ms) */ readonly DONE_THREAD_GRACE_MS: number; /** Claude Code tools that count as hands-on file edits */ readonly EDIT_TOOLS: readonly string[]; /** Edits in the window that trigger a nudge */ readonly EDIT_THRESHOLD: 6; /** Sliding window for counting edits (ms) */ readonly WINDOW_MS: number; /** At most one nudge per lead per this long (ms) */ readonly NUDGE_COOLDOWN_MS: number; /** A delegation this soon after a nudge counts as following it (ms) */ readonly FOLLOW_WINDOW_MS: number; /** Members named in a nudge */ readonly NUDGE_MAX_MEMBERS: 3; /** Tag that opens the nudge text */ readonly NUDGE_TAG: "[CREWLY-NUDGE]"; /** Note tag on a WorkItem the lead kept because no member fits */ readonly NO_MEMBER_FITS_TAG: "[NO-MEMBER-FITS]"; /** A lead share above this is flagged */ readonly FLAG_SHARE: 0.5; /** Below this many team tokens in the period a share is not flagged (too little to judge) */ readonly MIN_TEAM_TOKENS: 1000000; /** "This week" = the last N days */ readonly WEEK_DAYS: 7; /** State file in CREWLY_HOME (nudge counts, kept-work records) */ readonly STATE_FILENAME: "tl-delegation.json"; /** Kept-work records retained */ readonly MAX_RECORDS: 500; /** Max characters of a recorded reason / work line */ readonly MAX_TEXT_CHARS: 300; /** Accepted tool_name from the hook */ readonly TOOL_NAME_PATTERN: RegExp; }; /** * Agent-to-agent coordination in Slack rooms (crewly#1340): local * coordination stays inside Crewly, cross-machine coordination goes in a * thread, ticket ids never reach Slack, near-identical repeats are dropped. */ export declare const AGENT_COORDINATION_CONSTANTS: { /** chat-v2 message metadata: this agent post was delivered inside Crewly only (value: the reason) */ readonly INTERNAL_METADATA_KEY: "agentCoordinationInternal"; /** A repeat to the same recipients in the same room within this window is dropped (ms) */ readonly DUPLICATE_WINDOW_MS: number; /** Character-bigram similarity at which two messages count as the same message */ readonly DUPLICATE_SIMILARITY: 0.85; /** Messages remembered per sender/recipients/room */ readonly DUPLICATE_HISTORY: 10; /** Sender/recipients/room keys remembered at most */ readonly DUPLICATE_MAX_KEYS: 500; /** Request ticket prefix — always internal (`TKT-187`) */ readonly REQUEST_TICKET_PREFIX: "TKT"; /** Ticket prefixes are re-read at most this often (ms) */ readonly TICKET_PREFIX_CACHE_MS: number; /** Who asked a helper is remembered this long, to hand back a reply it cannot post (ms) */ readonly ASKED_BY_TTL_MS: number; /** Entries of the asked-by map at most */ readonly ASKED_BY_MAX: 500; /** Longest wait for the directory when telling agents elsewhere from people (ms) */ readonly DIRECTORY_LOOKUP_MS: 2000; /** Coordination threads (one per room per UTC day) remembered at most */ readonly COORDINATION_THREADS_MAX: 200; /** * Rules for every agent prompted from a Slack room. 2026-10-16: three * agents on one machine flooded #content-team with top-level coordination, * repeated one conclusion three times, quoted ticket ids, and promised to * watch a log "every 3h". */ readonly PROMPT_RULES: string; /** Added when the message came from a colleague inside Crewly (never in Slack). */ readonly INTERNAL_PROMPT_LINE: "This message came from a colleague inside Crewly and is not in Slack: reply with the command above — your answer goes to them, not to the room."; }; /** * Harness-owned reply routing (specs/2026-10-02-harness-owned-routing.md). */ export declare const REPLY_ROUTING_CONSTANTS: { /** A `[FOLLOW-UP]` / `[DECISION]` prompt reference steers a bare `reply` for this long (ms) */ readonly PROMPT_REFERENCE_FRESH_MS: number; /** chat-v2 message metadata: the harness posted this as the delivery of a ticket follow-up (`reply --ticket`) */ readonly DELIVERS_TICKET_METADATA_KEY: "deliversTicket"; /** The Slack DM mirror keeps reply affinity to another surface only while the owner's turn there is this recent (ms) */ readonly DM_AFFINITY_FRESH_MS: number; /** * `reply --new-thread` while the agent still owes an answer to the owner's * own new channel post this recent (ms): the answer goes under that post * instead of opening a second thread (2026-10-18, NECINA request). */ readonly OWNER_ROOT_POST_ANSWER_WINDOW_MS: number; /** Request ticket ids (`TKT-187`) */ readonly REQUEST_TICKET_PATTERN: RegExp; /** Project ticket ids (`CE-7`, `APP-12`) */ readonly PROJECT_TICKET_PATTERN: RegExp; /** Decision ids (`D-12`) */ readonly DECISION_PATTERN: RegExp; }; /** * "Triggered by" provenance on agent messages (agent-provenance audit * 2026-10-14): what started the turn an agent posted from, stored on the chat * row and shown as one muted line when the post is not a direct answer to the * owner's message in the same conversation. */ /** * Internal decision / ticket ids taken out of agent posts the owner reads * (crewly#1427, utils/owner-text-ids.utils.ts). */ export declare const OWNER_TEXT_IDS_CONSTANTS: { /** One internal id: decision card, request ticket, project ticket */ readonly ID_PATTERN: RegExp; /** A bracket holding only ids: "(D-897)", "(D-829 / TKT-537)" */ readonly PAREN_ONLY_IDS_PATTERN: RegExp; /** A card/ticket noun followed by its id: "决策卡 D-919", "card D-7" — the noun stays */ readonly NOUN_THEN_ID_PATTERN: RegExp; /** A post addressed to an agent keeps its ids */ readonly AGENT_MENTION_PATTERN: RegExp; /** Code spans and links are never changed */ readonly PROTECTED_PATTERN: RegExp; /** Chinese text gets the Chinese fallback */ readonly CJK_PATTERN: RegExp; readonly FALLBACK_ZH: "那张卡"; readonly FALLBACK_EN: "that card"; }; export declare const TURN_TRIGGER_CONSTANTS: { /** chat-v2 message metadata key holding the provenance record */ readonly METADATA_KEY: "triggeredBy"; /** A turn's trigger is used for posts this long after the delivery (ms); older posts get no line */ readonly FRESH_MS: number; /** An agent-to-agent send is matched to its delivery for this long (queued messages arrive later) (ms) */ readonly SENDER_MATCH_MS: number; /** A post into a conversation where the owner wrote to the agent this recently is an answer: no line (ms) */ readonly OWNER_ANSWER_WINDOW_MS: number; /** * An agent message inherits its sender's trigger (root, hand-off chain) only * when the sender's turn started this recently: an older turn is likely * about something else (message audit 2026-10-20) (ms) */ readonly CHAIN_LINK_MS: number; /** Agent-to-agent sends remembered per target */ readonly MAX_SENDERS_PER_TARGET: 20; /** Characters of a sent text used to recognise it in the delivered prompt */ readonly SENDER_MATCH_CHARS: 160; /** A sender counts as an agent only with a session-shaped name (harness services send `WorkItemDispatch` etc.) */ readonly AGENT_SESSION_PATTERN: RegExp; /** Agents listed in a hand-off chain */ readonly MAX_VIA_AGENTS: 4; /** Characters of a work-item title quoted in the line */ readonly MAX_TITLE_CHARS: 60; /** Internal ids dropped from a quoted title: decisions (`D-228`) and tickets (`CE-397`; short `GPT-4`-like names kept) */ readonly INTERNAL_ID_PATTERN: RegExp; /** A leading `[tag]` on a title (`[crewly-daily-metrics] …`), dropped */ readonly LEADING_TAG_PATTERN: RegExp; /** Opening of the rendered line (Slack mrkdwn / chat markdown italics) */ readonly LINE_PREFIX: "_↳ Triggered by "; /** Closing of the rendered line */ readonly LINE_SUFFIX: "_"; }; /** * Usage ledger backfill (`POST /api/system/usage/backfill`, owner only). * specs/2026-10-03-usage-ledger-durability.md §Backfill */ export declare const USAGE_BACKFILL_CONSTANTS: { /** Longest range one request may rebuild, in days */ readonly MAX_DAYS: 120; /** Most ledger backup files one request may read */ readonly MAX_LEDGER_FILES: 10; /** Largest ledger backup file read (the live ledger was 2.8 MB) */ readonly MAX_LEDGER_FILE_BYTES: number; /** Names a ledger backup may have: `*.json`, `*.corrupt-*`, `*.corrupt.*`, `*.bak-*` */ readonly LEDGER_FILE_NAME_PATTERN: RegExp; /** The one error a rejected ledger file gets: never file contents, never "missing" vs "unreadable" */ readonly LEDGER_FILE_ERROR: "not a usable ledger file"; }; /** * Daily token caps with a hard stop, team caps and temporary boosts * (specs/2026-10-02-spend-cap.md). The unit is TOKENS, not dollars (owner, * 2026-10-02): caps compare cost-weighted budget tokens (cache reads x0.1, * crewly#1090); the raw total is input (fresh + cached) + output — see * `eventTokens` in token-usage.service. Caps are OFF until the owner sets * one. All owner-facing text is English. */ export declare const SPEND_CAP_CONSTANTS: { /** Caps + boosts + today's bookkeeping, under CREWLY_HOME (tokens) */ readonly STORE_FILE: "usage-caps.json"; /** The pre-token (USD) store; migrated once with USAGE_CONSTANTS.TOKENS_PER_USD */ readonly LEGACY_USD_STORE_FILE: "spend-caps.json"; /** Default / max window of GET /api/system/usage and /api/system/spend */ readonly DEFAULT_DAYS: 7; readonly MAX_DAYS: 31; /** Fraction of a cap that sends the one heads-up */ readonly WARN_FRACTION: 0.8; /** Enforcement tick */ readonly TICK_MS: 60000; /** How long the all-agents total is cached for the delivery gate */ readonly TOTAL_CACHE_MS: 15000; /** How often the orc's held message queue re-checks the cap */ readonly QUEUE_RECHECK_MS: 60000; /** The "Boost +X today" card offers the cap times this (rounded to a whole million) */ readonly BOOST_FACTOR: 1; /** Target key of the all-agents total cap / an everyone boost */ readonly TOTAL_TARGET: "*"; /** Prefix of a team target (`team:`) */ readonly TEAM_TARGET_PREFIX: "team:"; /** Error code of a refused wake */ readonly ERROR_CODE: "SPEND_CAP_REACHED"; /** Queued-delivery marker in sendMessageToAgent results */ readonly QUEUED_MARKER: "[SPEND_CAP]"; /** Decision kind of the "cap reached" card */ readonly DECISION_KIND: "spend_cap"; /** * Why an open "cap reached" card was withdrawn when its stop lifted * without it (cap removed or raised, boost from elsewhere) — shown on the * card as "Closed — " (#939) */ readonly CARD_WITHDRAWN_NOTE: "no longer needed: the cap was removed, raised or boosted, so the stop has lifted"; /** Why an open "cap reached" card was withdrawn when a newer card for the same target (a changed cap) replaced it */ readonly CARD_SUPERSEDED_NOTE: "replaced by a newer card: the cap changed"; readonly OPTIONS: { readonly KEEP: "Keep stopped"; readonly UNLIMITED: "Unlimited today"; }; }; /** * Run traces: one id from a request / goal / owner message through tickets, * work items, agent turns, skill calls, messages and usage * (specs/2026-10-03-run-traces.md, issue #983). */ export declare const TRACE_CONSTANTS: { /** Folder under CREWLY_HOME holding `.jsonl` and the index */ readonly DIR_NAME: "traces"; /** Index file name inside {@link TRACE_CONSTANTS.DIR_NAME} */ readonly INDEX_FILE: "index.json"; /** Index file format version */ readonly INDEX_VERSION: 1; /** Trace id prefix (`tr-YYYYMMDD-xxxxxxxx`) */ readonly ID_PREFIX: "tr-"; /** Random hex chars at the end of a trace id */ readonly ID_RANDOM_HEX: 8; /** Most events one trace keeps; the next one becomes a `trace.truncated` marker */ readonly MAX_EVENTS_PER_TRACE: 5000; /** Most bytes one trace file takes (same marker) */ readonly MAX_BYTES_PER_TRACE: number; /** Longest summary written (chars); message bodies are cut to this */ readonly SUMMARY_MAX_CHARS: 200; /** Longest string value kept in an event's `data` map */ readonly DATA_VALUE_MAX_CHARS: 120; /** Traces whose last event is older than this are deleted (90 days) */ readonly RETENTION_MS: number; /** Minimum gap between two retention sweeps (one day) */ readonly SWEEP_INTERVAL_MS: number; /** Delay before the in-memory index is written after a change */ readonly INDEX_FLUSH_DELAY_MS: 1000; /** Per-session `(since, traceId)` spans kept for timestamp lookups */ readonly SESSION_HISTORY_SPANS: 50; /** * A session with no activity (deliveries, API calls, hooks) for this long * stops being on its trace. Override with CREWLY_TRACE_IDLE_CLEAR_MINUTES. */ readonly IDLE_CLEAR_MS: number; /** * Most bytes the whole traces folder may take; the oldest traces are * pruned past it (daily sweep and the disk janitor). Override with * CREWLY_TRACES_MAX_TOTAL_MB. */ readonly MAX_TOTAL_BYTES: number; /** A pending owner-message root older than this is forgotten */ readonly PENDING_ROOT_TTL_MS: number; /** Default and maximum page size of `GET /api/traces/:id` */ readonly DEFAULT_PAGE_SIZE: 200; readonly MAX_PAGE_SIZE: 1000; /** Default and maximum rows of `GET /api/traces` */ readonly DEFAULT_LIST_LIMIT: 100; readonly MAX_LIST_LIMIT: 1000; /** * API paths (relative to /api) whose agent calls are not recorded as skill * calls: Claude Code hooks fire on every tool use, heartbeats are polling, * and reading a trace must not write to it. */ readonly SKIPPED_SKILL_PATH_PREFIXES: readonly ["/agent-hooks", "/traces", "/heartbeat", "/health"]; /** HTTP statuses recorded as `guard.block` instead of `error` */ readonly GUARD_BLOCK_STATUSES: readonly [403, 409, 423, 429]; /** A gap with no progress longer than this is a stall. Override: CREWLY_TRACE_STALL_MINUTES, `?stallMinutes=` */ readonly STALL_MINUTES: 30; /** Bounds of a caller-given stall threshold (minutes) */ readonly STALL_MINUTES_MIN: 1; readonly STALL_MINUTES_MAX: number; /** Events this long before a stall still explain it (quota, delivery failure) */ readonly STALL_CAUSE_LOOKBACK_MS: number; /** Without turn events, a session's activity points at most this far apart form one busy period */ readonly INFERRED_TURN_GAP_MS: number; /** The owner message this close to the root is the ask, not a touch */ readonly ROOT_GRACE_MS: number; /** The same owner message delivered to two agents within this window is one touch */ readonly OWNER_MESSAGE_DEDUPE_MS: number; /** A dashboard write this close to an owner touch is that touch, not a manual intervention */ readonly OWNER_ACTION_DEDUPE_MS: number; /** How long computed metrics are reused (an ongoing stall keeps growing) */ readonly METRICS_CACHE_TTL_MS: number; /** Most traces whose metrics are cached (≥ MAX_LIST_LIMIT, so one list call never evicts its own rows) */ readonly METRICS_CACHE_MAX: 1000; /** Most rows of `GET /api/traces` when metrics are embedded (each row may read a whole trace file) */ readonly METRICS_LIST_MAX: 100; /** Default and largest size of the `trace-read` summary (chars) */ readonly READ_DEFAULT_CHARS: 4000; readonly READ_MIN_CHARS: 600; readonly READ_MAX_CHARS: 16000; /** Longest event summary shown in a key-event line of the text summary */ readonly SUMMARY_EVENT_CHARS: 140; /** Stalls listed in the text summary (the longest first) */ readonly SUMMARY_MAX_STALLS: 5; /** Agents / models listed in the text summary (most expensive first) */ readonly SUMMARY_MAX_BREAKDOWN: 4; /** Stalls kept in a metrics object (the longest; counts cover all) */ readonly METRICS_MAX_STALL_ITEMS: 50; /** Hook event the subagent guard posts to /api/agent-hooks when it sends a subagent back */ readonly SUBAGENT_SENDBACK_HOOK_EVENT: "SubagentSendBack"; /** Most ticket labels kept in a trace's tags (specs/2026-10-03-autopilot-experiments.md) */ readonly MAX_TAG_LABELS: 20; }; /** * Stall watcher (crewly#1363, crewly#1362, specs/2026-10-17-stall-watcher.md): * acts on in-flight work that stopped moving — promises to the owner, * claimed WorkItems, and background runs an agent said it started — with * one ladder: the agent once, then its lead once, then one plain line to the * owner. The owner is never the retry loop. */ /** * Daily spot check of verified work (crewly#1315): a few verified WorkItems * from the last day are re-checked for their artifacts still existing. */ export declare const VERIFIED_SPOT_CHECK_CONSTANTS: { /** `=0` switches the sampler off */ readonly ENV_SWITCH: "CREWLY_VERIFIED_SPOT_CHECK"; /** Verified items completed within this window are candidates */ readonly WINDOW_MS: number; /** Most WorkItems re-checked per run */ readonly SAMPLE_SIZE: 10; /** How often the service asks whether today's run is still owed */ readonly CHECK_INTERVAL_MS: number; /** Delay before the first check after boot */ readonly BOOT_DELAY_MS: number; /** State file under CREWLY_HOME: the last run's day and result */ readonly STATE_FILE: "verified-spot-check.json"; /** Title of the owner alert when a sampled artifact is gone */ readonly OWNER_ALERT_TITLE: "Verified work spot check"; /** Discrepancies listed under the digest line (the rest are counted) */ readonly MAX_LISTED_DISCREPANCIES: 5; /** Longest WorkItem title quoted in a discrepancy row */ readonly TITLE_MAX_CHARS: 60; }; export declare const STALL_WATCHER_CONSTANTS: { /** `=false` switches the watcher off entirely */ readonly ENV_KILL_SWITCH: "CREWLY_STALL_WATCHER"; /** `=true` / `=false` forces dry run on / off (default: dry run until armed for ARM_DELAY_MS) */ readonly ENV_DRY_RUN: "CREWLY_STALL_WATCHER_DRY_RUN"; /** State file under CREWLY_HOME: in-flight runs, ladder stamps, pending claim checks, the armed time */ readonly STATE_FILE: "in-flight-runs.json"; /** State file format version */ readonly STATE_VERSION: 1; /** How often the watcher looks (ms) */ readonly INTERVAL_MS: number; /** First pass this long after boot, after the boot-pressure gate (ms) */ readonly BOOT_DELAY_MS: number; /** * Like the janitor's retention deletes: the first pass records when it ran * and only logs; the watcher sends nothing until this long after that (ms). */ readonly ARM_DELAY_MS: number; /** The agent is re-briefed at this multiple of its own estimate past the promise / run start */ readonly AGENT_STEP_MULTIPLIER: 1.5; /** ... but never sooner than this after the promise (ms) */ readonly MIN_AGENT_STEP_MS: number; /** The lead is told at this multiple of the estimate */ readonly LEAD_STEP_MULTIPLIER: 3; /** The owner hears one line when the lead has not answered in the thread this long after being told (ms) */ readonly LEAD_GRACE_MS: number; /** At most one watcher nudge per agent per this long (ms) */ readonly NUDGE_COOLDOWN_MS: number; /** At most one owner line per stalled item per this long (ms) */ readonly OWNER_LINE_INTERVAL_MS: number; /** A background run with no duration in the text is expected back after this long (ms) */ readonly RUN_DEFAULT_ESTIMATE_MS: number; /** An agent with a run in flight is not idle-stopped or evicted until expectedBy + this (ms) */ readonly RUN_PROTECTION_GRACE_MS: number; /** A run record older than this is dropped whatever happened (ms) */ readonly RUN_MAX_AGE_MS: number; /** Ladder stamps of an item not seen for this long are forgotten (ms) */ readonly LADDER_TTL_MS: number; /** Most in-flight runs kept */ readonly MAX_RUNS: 200; /** Characters of a run's sentence kept in its record and in nudges */ readonly RUN_HEAD_MAX_CHARS: 160; /** Characters of the context message quoted in a promise re-brief */ readonly CONTEXT_MAX_CHARS: 300; /** Characters of a promise / title quoted in a nudge or an owner line */ readonly QUOTE_MAX_CHARS: 120; /** * A claimed WorkItem whose trace shows no progress (no tool calls, posts * or worktree changes) for this long is stalled; the same again after the * nudge releases the claim (ms). Shares the autonomy-metrics threshold. */ readonly CLAIM_STALL_MS: number; /** The claim-check window around "X is doing it": a WorkItem / ticket for X created this long before or after (ms) (crewly#1362) */ readonly CLAIM_CHECK_WINDOW_MS: number; /** Most pending claim checks kept */ readonly MAX_PENDING_CLAIMS: 200; /** Opens every watcher line to an agent */ readonly NUDGE_TAG: "[STALL]"; /** Opens a claim-check line to an agent */ readonly CLAIM_CHECK_TAG: "[CLAIM-CHECK]"; /** Title of the owner's Slack notice */ readonly OWNER_ALERT_TITLE: "Stalled work"; /** * An agent saying it started something that runs on its own (a render, a * background process, a sub-agent). Registered only together with a * duration the open-items parser understands ("预计 1 小时左右", "about 40 min"). */ readonly BACKGROUND_RUN_PATTERN: RegExp; /** * An agent saying it submitted jobs to an outside generator that runs them on * its own (Flow / Veo renders, a generation batch): registered even with no * duration, expected back after RUN_DEFAULT_ESTIMATE_MS (crewly#1386). Needs * ASYNC_JOB_NOUN_PATTERN in the same sentence ("提交了 PR" is not a run). */ readonly SUBMITTED_JOB_PATTERN: RegExp; /** What a submitted async job is: a render or generation on an outside tool */ readonly ASYNC_JOB_NOUN_PATTERN: RegExp; /** "等它跑完" / "让它们跑完" / "once they are done": waiting for a run to finish, not a report that it did */ readonly RUN_AWAITED_PATTERN: RegExp; /** A later post by the same agent in the thread that reports the run finished or failed */ readonly RUN_FINISHED_PATTERN: RegExp; /** A link or a file in a post counts as a deliverable */ readonly DELIVERABLE_PATTERN: RegExp; /** Chinese verbs that make " …" a claim that the named teammate is doing the work (crewly#1362) */ readonly CLAIM_ZH_AFTER_NAME: "(?:已经)?(?:在做|在弄|在处理|在搞|在跟|在推进|在准备|在出|在写|在剪|在录|在改|负责|去做|来做|接了|接手|正在)"; /** Chinese verbs that make "… " a hand-off to the named teammate */ readonly CLAIM_ZH_BEFORE_NAME: "(?:交给|让|叫|由|派给|丢给|转给|安排)"; /** English verbs after the name: "Pia is (already) doing / on / making / working on it" */ readonly CLAIM_EN_AFTER_NAME: "(?:is|'s|has been|will be|was|are|'re)\\s+(?:already\\s+)?(?:doing|on|making|working on|handling|building|taking|taking care of|writing|producing|rendering|editing|cutting|preparing|drafting|looking into|picking (?:it|this) up)"; /** English hand-off before the name: "handed it to Pia", "assigned this to Pia" */ readonly CLAIM_EN_BEFORE_NAME: "(?:handed|gave|passed|assigned|delegated|sent|routed)\\s+(?:it|this|that|the\\s+\\w+(?:\\s+\\w+)?)\\s+(?:over\\s+)?to"; /** * Words before the hand-off that make it a plan, not a claim (crewly#1427 item 5): * "你看过说可以,我再交给 Dex", "会随这次一起交给 Dex", "你说改,我就做成改动交给 Owen", * "最后「放进 Flopost」:卡交给 Dex 带进下一次发版" — the 2026-10-23 false nudges. */ readonly CLAIM_PLAN_ZH: RegExp; /** English words before the hand-off that make it a plan: "once you approve, Pia will be doing it", "then I'll hand it to Dana" */ readonly CLAIM_PLAN_EN: RegExp; }; /** * Token usage: the unit, the stats endpoint, and the Codex / Antigravity * ledgers (specs/2026-10-02-spend-cap.md). */ export declare const USAGE_CONSTANTS: { /** * Tokens per US dollar, used ONCE to convert settings written in USD * (the ticket autopilot's `dailyBudgetUsd`, pre-token spend caps) to * tokens; each conversion is logged. A round migration default, not a * price: the owner's Mac blended ~0.66M tokens per API-equivalent $ in the * 7 days to 2026-10-01 (Opus 0.51M/$, Sonnet 2–3M/$, DeepSeek ~11M/$; * cache reads are >99% of the tokens), so a converted budget is within * 1.5× of what the dollar figure bought. */ readonly TOKENS_PER_USD: 1000000; /** groupBy values of GET /api/system/usage */ readonly GROUP_BY: readonly string[]; /** Row key / label of usage whose model the source did not record (or recorded as a `-default` placeholder) */ readonly UNKNOWN_MODEL_KEY: "(unknown-model)"; readonly UNKNOWN_MODEL_LABEL: "Unknown model"; /** Rows returned for groupBy=workItem (highest first) */ readonly MAX_WORK_ITEM_ROWS: 50; /** Row key / label (groupBy=workItem) of usage while the agent had no work item running */ readonly NO_WORK_ITEM_KEY: "(no-work-item)"; readonly NO_WORK_ITEM_LABEL: "(no work item)"; /** Label of usage no team / project / work item can be attributed to */ readonly UNATTRIBUTED: "(unattributed)"; }; /** Codex rollout usage sync (`~/.codex/sessions/**\/rollout-*.jsonl`). */ export declare const CODEX_USAGE_SYNC_CONSTANTS: { /** Poll interval (ms) */ readonly SYNC_INTERVAL_MS: 60000; /** Cursor file under CREWLY_HOME */ readonly CURSOR_FILE: "codex-rollout-cursors.json"; /** Model recorded when a rollout names none */ readonly DEFAULT_MODEL: "codex-cli-default"; /** Directory entries visited when looking a rollout up by id */ readonly MAX_SCAN_ENTRIES: 20000; }; /** Antigravity (`agy`) usage sync — best effort, see the spec. */ export declare const ANTIGRAVITY_USAGE_SYNC_CONSTANTS: { /** Poll interval (ms) */ readonly SYNC_INTERVAL_MS: 60000; /** Cursor file under CREWLY_HOME */ readonly CURSOR_FILE: "antigravity-usage-cursors.json"; /** Model recorded (agy stores its model as an enum, not a name) */ readonly MODEL: "antigravity-cli-default"; /** * Protobuf path of the usage message in a `steps.metadata` blob (field 9), * and its input / output token fields. Undocumented — read from agy 1.x * conversation databases; a blob without them is skipped, never guessed. */ readonly USAGE_FIELD: 9; readonly INPUT_FIELD: 2; readonly OUTPUT_FIELD: 3; }; /** * Runtime fallback: switch an agent to another runtime when its runtime runs * out of usage, and back when the limit resets. * specs/2026-10-01-runtime-fallback.md */ /** * A planned relaunch (the runtime fallback moving an agent to another * runtime and back) is not a crash or a hang: the restart / heartbeat / hung * monitors ignore the session for this long and send no alarm. */ export declare const PLANNED_RELAUNCH_CONSTANTS: { /** How long a session counts as "being relaunched on purpose" */ readonly WINDOW_MS: number; }; export declare const RUNTIME_FALLBACK_CONSTANTS: { /** State + settings file under CREWLY_HOME */ readonly STATE_FILE: "runtime-fallback.json"; /** Default global fallback chain */ readonly DEFAULT_CHAIN: readonly string[]; /** Model a Crewly Agent fallback runs (provider/model) */ readonly DEFAULT_CREWLY_AGENT_MODEL: "deepseek/deepseek-chat"; /** Default switch-back probe cadence */ readonly DEFAULT_PROBE_INTERVAL_MINUTES: 15; /** Bounds of the probe cadence setting */ readonly MIN_PROBE_INTERVAL_MINUTES: 5; readonly MAX_PROBE_INTERVAL_MINUTES: 240; /** Main tick (switch-back checks, idle-boundary reverts) */ readonly TICK_MS: 30000; /** Poll while waiting for an agent's safe point */ readonly SAFE_POINT_POLL_MS: 5000; /** Longest wait for a safe point before switching anyway (a limit ends the turn) */ readonly SAFE_POINT_MAX_WAIT_MS: number; /** Wait after the first switch of an event before the owner DM (so the count means something) */ readonly NOTICE_DELAY_MS: 45000; /** Probe again this long after a parsed reset time */ readonly RESET_GRACE_MS: number; /** * Out of money/credit (DeepSeek 402 "Insufficient Balance", "credit * balance is too low"): no reset time and no timed retry — only a probe, * at most this often, can bring the runtime back. */ readonly BILLING_PROBE_INTERVAL_MS: number; /** A limit seen again this soon after a switch-back counts as a failed switch-back */ readonly FAILED_REVERT_WINDOW_MS: number; /** Each failed switch-back doubles the probe interval, up to this */ readonly MAX_PROBE_BACKOFF_MS: number; /** Where the owner tops up, by billing provider */ readonly TOP_UP_URLS: Readonly>; /** A probe that says "fine" mutes detection for this long (false positive) */ readonly FALSE_POSITIVE_MUTE_MS: number; /** Transient rate limits on one session that escalate to a usage limit */ readonly TRANSIENT_ESCALATE_COUNT: 4; readonly TRANSIENT_WINDOW_MS: number; /** Horizon of an escalated transient limit */ readonly TRANSIENT_ESCALATED_HORIZON_MS: number; /** A revert is not attempted while the PTY wrote within this window */ readonly IDLE_QUIET_MS: 20000; /** * A session's model turn (a Claude Code PreToolUse hook) counts as proof that * its runtime target has usage only this long after the target was marked out * of usage (a response already streaming when the limit hit is not proof) */ readonly WORKING_EVIDENCE_GRACE_MS: number; /** Working-evidence reports of one session are looked at no more often than this */ readonly WORKING_EVIDENCE_THROTTLE_MS: 60000; /** Smoke tests */ readonly SMOKE: { readonly TEAM_PREFIX: "zz-runtime-smoke-"; readonly MEMBER_NAME: "smoke"; readonly MEMBER_ROLE: "developer"; readonly TIMEOUT_MS: number; readonly POLL_MS: 3000; /** Keep finished jobs this long for GET */ readonly JOB_TTL_MS: number; /** Lines read from the agent's screen / log while waiting */ readonly CAPTURE_LINES: 200; /** Screen lines kept in a failure report */ readonly SCREEN_LINES: 60; }; /** Display names used in owner messages and badges */ readonly LABELS: Readonly>; /** Short names for "(Claude limit)" */ readonly SHORT_LABELS: Readonly>; }; /** * Per-session usage-limit state (2026-10-16 incident: the orchestrator sat on * Claude Code's weekly-limit menu for ~38 hours while Crewly restarted it, * waited for its prompt, and reported it "active and ready"). While a session * is usage-limited it is not restarted, not waited on and not written to. */ export declare const USAGE_LIMIT_STATE_CONSTANTS: { /** How long to back off when the limit message names no reset time (ms) */ readonly NO_RESET_BACKOFF_MS: number; /** Grace after the parsed reset time before the session is tried again (ms) */ readonly RESET_GRACE_MS: number; /** * Furthest a parsed reset time may lie ahead. Weekly limits reset within * 7 days; a dated message further out is an old one (a past "Oct 10" is * read as next year's), so it is not trusted. */ readonly MAX_RESET_AHEAD_MS: number; /** Trailing non-empty screen lines inspected for a usage-limit state */ readonly SCREEN_TAIL_LINES: 25; /** Longest a held queue waits before looking at the state again (ms) */ readonly RECHECK_MAX_MS: 60000; /** Delay between the keys that answer the usage-limit menu (ms) */ readonly MENU_KEY_DELAY_MS: 300; /** Marker on a delivery result held because the agent is usage-limited */ readonly QUEUED_MARKER: "[USAGE_LIMITED]"; /** * Fewest printable characters (whitespace collapsed) a live output chunk * needs to count as fresh activity that lifts a usage-limit state; cursor * moves and spinner ticks stay below it. */ readonly FRESH_OUTPUT_MIN_CHARS: 40; /** * After a limit is last seen (or its menu answered), activity this soon is * not trusted to lift the state: it is the repaint of the limit screen or * of the key Crewly pressed (ms). */ readonly LIFT_GRACE_MS: 10000; }; /** * Per-person access (issue #968, epic #967): the people directory, the person * each agent turn acts for, and grant sharing. specs/per-person-access.md */ export declare const PEOPLE_CONSTANTS: { /** People directory under CREWLY_HOME */ readonly STORE_FILE: "people.json"; /** Who each agent session acts for, under CREWLY_HOME */ readonly ACTING_FOR_FILE: "acting-for.json"; /** Person id of the instance owner — always this, whatever their Slack user id (stored and sent to Cloud) */ readonly OWNER_ID: "owner"; /** Env var naming the owner's Slack user id(s), comma-separated — for Slack credentials from env (no installer known) */ readonly OWNER_SLACK_USER_ID_ENV: "SLACK_OWNER_USER_ID"; /** Error code when Crewly Cloud is too old for per-person access (sharing endpoint missing) */ readonly CLOUD_UPDATE_REQUIRED_CODE: "cloud_update_required"; /** What the dashboard shows for that */ readonly CLOUD_UPDATE_REQUIRED_MESSAGE: "Requires a Cloud update"; /** Roles a person can have */ readonly ROLES: readonly string[]; /** Role a newly seen Slack user gets */ readonly DEFAULT_ROLE: "member"; /** Headers the backend sends Cloud with each credential request (never taken from an agent) */ readonly ACTING_FOR_HEADER: "X-Crewly-Acting-For"; readonly ACTING_FOR_ROLE_HEADER: "X-Crewly-Acting-For-Role"; /** Cloud's refusal code when the person may not use a grant */ readonly NOT_PERMITTED_CODE: "not_permitted"; /** What an agent is told to do with that refusal */ readonly NOT_PERMITTED_HINT: "Tell the person you are working for exactly this, in one line. Do not retry, and do not use another account or connector to get around it."; /** Grant sharing modes: only the person who authorized it, named people, or every member (not guests) */ readonly SHARING_MODES: readonly string[]; /** Longest display name kept */ readonly MAX_NAME_LENGTH: 80; /** Slack user ids (`U…` / `W…`) */ readonly SLACK_USER_ID_PATTERN: RegExp; }; /** * Daily signal digest (#987, specs/2026-10-03-signal-digest.md): a team lead * turns a site's signals (GA4, Search Console, inbound mail, errors) into 3–5 * actions; the owner answers Do / Skip per action on one Slack card, and Do * opens an experiment ticket. */ export declare const SIGNAL_DIGEST_CONSTANTS: { /** Persisted digests under CREWLY_HOME */ readonly STORE_FILENAME: "signal-digests.json"; /** Id prefix of a digest (`SD-`) */ readonly ID_PREFIX: "SD-"; /** Actions per digest */ readonly MIN_ITEMS: 3; readonly MAX_ITEMS: 5; /** Field length caps (characters) */ readonly SITE_MAX_CHARS: 120; readonly KEY_MAX_CHARS: 160; readonly SIGNAL_MAX_CHARS: 400; readonly PROPOSAL_MAX_CHARS: 200; readonly EXPECTED_MAX_CHARS: 240; readonly EFFORT_MAX_CHARS: 60; readonly METRIC_MAX_CHARS: 200; /** Ticket titles are cut here */ readonly TICKET_TITLE_MAX_CHARS: 120; /** Item sources an action can name */ readonly SOURCES: readonly string[]; /** An action the owner chose Do on is not proposed again for this long (ms) */ readonly DO_BLOCK_MS: number; /** An action the owner skipped is not proposed again for this long (ms) */ readonly SKIP_BLOCK_MS: number; /** Experiment observation window written on Do tickets (days, #986 default) */ readonly EXPERIMENT_WINDOW_DAYS: 14; /** Labels on the ticket a Do creates */ readonly TICKET_LABELS: readonly string[]; /** Status of the ticket a Do creates */ readonly TICKET_STATUS: "ready"; /** * Block Kit action ids: `decision:signal::`. The `decision:` * prefix rides the existing Socket Mode / Cloud interactivity route; the * decision service ignores these (their value has no decision id). */ readonly ACTION_PREFIX: "decision:signal:"; /** Digests are kept this long after their last change, then pruned (ms) */ readonly KEEP_MS: number; /** * A site's digest proposed within this window with actions still open * stands: proposing again returns it (same actions) or answers 409. */ readonly DUPLICATE_WINDOW_MS: number; /** A source report older than this is not attached to a new digest (ms) */ readonly SOURCE_REPORT_MAX_AGE_MS: number; /** Source names / failure details are cut at these lengths */ readonly SOURCE_NAME_MAX_CHARS: 40; readonly SOURCE_DETAIL_MAX_CHARS: 200; /** Most sources one report may list */ readonly MAX_SOURCES: 10; /** How the card names the known sources */ readonly SOURCE_LABELS: Readonly>; }; /** * Experiment cards (issue #986, epic #982): a hypothesis on an optimisation * ticket, measured automatically at the end of its window. specs/experiment-cards.md */ export declare const EXPERIMENT_CONSTANTS: { /** Experiment store under CREWLY_HOME */ readonly STORE_FILE: "experiments.json"; /** Id prefix (EXP-1, EXP-2, …) */ readonly ID_PREFIX: "EXP-"; /** Default observation window; whole weeks keep weekdays balanced */ readonly DEFAULT_WINDOW_DAYS: 14; /** Shortest / longest window accepted */ readonly MIN_WINDOW_DAYS: 7; readonly MAX_WINDOW_DAYS: 90; /** Days a source's numbers take to settle (Search Console 2-3, GA4 1-2) */ readonly SOURCE_LAG_DAYS: Readonly>; /** Measures each source offers (the seo-ops `metric` command) */ readonly SOURCE_MEASURES: Readonly>; /** Measures where lower is better (default direction "decrease") */ readonly LOWER_IS_BETTER: readonly string[]; /** Fewest events (baseline + result) before a count verdict is more than "inconclusive" */ readonly MIN_COUNT_VOLUME: Readonly>; /** CTR / position: impressions needed in each window */ readonly MIN_IMPRESSIONS_PER_WINDOW: 200; /** CTR: clicks needed across both windows */ readonly MIN_CTR_CLICKS: 10; /** Position: days with impressions needed in each window */ readonly MIN_POSITION_DAYS: 7; /** |z| (or Welch t) at or above this is a real change (~95% two-sided) */ readonly SIGNIFICANCE_Z: 1.96; /** Confidence recorded on the agent's prediction when none is given */ readonly DEFAULT_CONFIDENCE: 0.6; /** Scheduler tick */ readonly TICK_INTERVAL_MS: number; /** Failed fetches before the owner is told a measurement is stuck */ readonly MAX_FETCH_ATTEMPTS: 6; /** After MAX_FETCH_ATTEMPTS failures in a row, retry this often instead of every tick */ readonly FETCH_BACKOFF_MS: number; /** seo-ops script, relative to the package root */ readonly SEO_OPS_SCRIPT: "config/skills/agent/marketplace/seo-ops/seo_ops.py"; /** Python used to run it */ readonly PYTHON_BIN: "python3"; /** seo-ops metric timeout */ readonly FETCH_TIMEOUT_MS: 120000; /** Wiki page (append-only log) results are written to, inside the vault */ readonly WIKI_LOG_PATH: "llm-curated/experiments/log.md"; /** Timeline entries kept per experiment */ readonly MAX_TIMELINE: 200; /** Longest hypothesis / title kept */ readonly MAX_TEXT_LENGTH: 1000; /** Kill switch for the scheduler */ readonly ENV_SWITCH: "CREWLY_EXPERIMENTS"; /** Autopilot cards: extra outcome metrics allowed besides the primary (specs/2026-10-03-autopilot-experiments.md) */ readonly MAX_EXTRA_METRICS: 5; /** Autopilot cards: one owner check-in this often while running */ readonly CHECK_IN_INTERVAL_MS: number; /** A check-in whose process read (or send) failed is retried after this, doubling up to the max */ readonly CHECK_IN_RETRY_MIN_MS: number; readonly CHECK_IN_RETRY_MAX_MS: number; /** A supervisor session name (#1026): what an agent session name looks like */ readonly SUPERVISOR_PATTERN: RegExp; /** Statuses in which a card's supervisor and creator may read its autopilot project (access ends when measured or cancelled) */ readonly SUPERVISION_STATUSES: readonly string[]; }; /** * Release input-guard check (specs/2026-10-04-release-input-guard-check.md): * classify every live agent's input box with the NEW build before restarting. */ export declare const INPUT_GUARD_CHECK_CONSTANTS: { /** Script of the new build that classifies views, relative to a build's `dist/` */ readonly SCRIPT_RELATIVE: "backend/backend/src/scripts/input-guard-classify.js"; /** Longest the child process may run (ms) */ readonly CHILD_TIMEOUT_MS: number; /** Largest child output accepted (bytes) */ readonly CHILD_MAX_OUTPUT_BYTES: number; /** Env switch: skip the gate in the auto-update path */ readonly SKIP_ENV: "CREWLY_SKIP_INPUT_GUARD_CHECK"; /** Once-per-version "blocked" ledger under CREWLY_HOME */ readonly LEDGER_FILE: "input-guard-blocked.json"; /** Route (under /api) */ readonly ROUTE: "/system/input-guard-check"; /** Marker of a build installed on disk but not restarted onto, under CREWLY_HOME */ readonly BLOCKED_BUILD_FILE: "blocked-build.json"; /** Only the bottom N non-empty screen rows are searched for markers (a transcript can quote anything) */ readonly TAIL_LINES: 12; /** * Lower-case footer / composer hints that mean "this runtime is at its chat * prompt": Claude Code's status footer (`⏵⏵ … (shift+tab to cycle)`), the * `? for shortcuts` hint (Codex, Antigravity, Gemini) and Gemini's composer * placeholder. Only with one of these on screen is a missing box a failure. */ readonly READY_MARKERS: readonly string[]; /** Lower-case text of dialogs, login and picker screens (no input box expected) */ readonly DIALOG_MARKERS: readonly string[]; }; /** * Temporary team pause (specs/2026-10-04-team-pause.md). A paused team is * not woken by automation and is hidden from other agents; the owner * pauses and resumes it (dashboard, API, or a DM to the orc). */ export declare const TEAM_PAUSE_CONSTANTS: { /** How often the auto-resume sweep checks `paused.until` (ms) */ readonly AUTO_RESUME_SWEEP_MS: number; /** API error code for a refused action on a paused team */ readonly ERROR_CODE: "team_paused"; /** Label the orchestrator sees for a paused team */ readonly ORC_STATUS_LABEL: "paused (owner)"; /** Longest accepted pause reason (characters) */ readonly MAX_REASON_LENGTH: 500; /** `owner/name` GitHub repository */ readonly ISSUE_REPO_PATTERN: RegExp; /** Slack auto-replies remembered (one per thread per team) */ readonly SLACK_NOTICE_MEMORY: 2000; /** Metadata key on a WorkItem unassigned because its team was paused */ readonly UNASSIGNED_METADATA_KEY: "pausedTeamUnassigned"; }; /** * Scheduled commands: an owner-edited list of host commands the backend runs * on an interval (e.g. the crewly-web release script). Read from disk only. */ export declare const SCHEDULED_COMMANDS: { /** Config file under CREWLY_HOME; absent = feature off */ readonly CONFIG_FILE: "scheduled-commands.json"; /** Directory under CREWLY_HOME that receives each command's stdout/stderr */ readonly LOG_DIR: "logs"; /** Log file name pattern; `{name}` is the entry name */ readonly LOG_FILE_TEMPLATE: "scheduled-{name}.log"; /** Shortest interval accepted (minutes); smaller values are rejected */ readonly MIN_INTERVAL_MINUTES: 1; /** Delay before the first run after boot (ms), so restarts do not starve a run */ readonly INITIAL_DELAY_MS: 30000; /** Entry names: letters, digits, dot, dash, underscore */ readonly NAME_PATTERN: RegExp; /** Milliseconds per minute */ readonly MS_PER_MINUTE: 60000; }; /** * Remote MCP servers (Zoho MCP and any other streamable-HTTP MCP server) the * owner connects in Connections. The URL usually carries the server's key, * so it is stored only under CREWLY_HOME and never logged or echoed. */ export declare const REMOTE_MCP_CONSTANTS: { /** Store file under CREWLY_HOME (mode 0600). */ readonly STORE_FILE: "remote-mcp-servers.json"; /** Per-agent launch files under CREWLY_HOME (mode 0600, one dir per session). */ readonly RUNTIME_DIR: "runtime/remote-mcp"; /** Claude Code `--mcp-config` file name inside a session's runtime dir. */ readonly CLAUDE_CONFIG_FILE: "claude-mcp.json"; /** Connector-access id prefix: `mcp:`. */ readonly CONNECTOR_PREFIX: "mcp:"; /** Server ids (also the MCP server name in the runtime config). */ readonly ID_PATTERN: RegExp; /** Longest label kept. */ readonly MAX_LABEL_LENGTH: 60; /** Longest URL accepted. */ readonly MAX_URL_LENGTH: 2048; /** Most servers one instance may hold. */ readonly MAX_SERVERS: 30; /** Test (initialize + tools/list) timeout per request, ms. */ readonly TEST_TIMEOUT_MS: 15000; /** MCP protocol version sent on initialize. */ readonly PROTOCOL_VERSION: "2025-03-26"; /** Most tool names returned by a test. */ readonly MAX_TOOL_NAMES: 50; /** Catalog presets the add form offers. */ readonly PROVIDERS: readonly string[]; /** * Tools an AGENT may never call through the proxy, per server kind (the * server's provider, or its id when it has none). Compared after * trim + NFKC + lower-casing, so case and width tricks do not bypass it. * Zoho has no draft tool: a draft is a mode of sendEmail, so one missing * field sends mail. Mail is only ever drafted through the backend's * draft-only path (CREW-400). resendVerification* mails an address a * verification link; denied too since an agent has no reason to trigger it. */ readonly DENIED_AGENT_TOOLS: Record; /** Zoho tool that saves a draft when `mode` is `draft` (it is the only send tool Zoho has). */ readonly ZOHO_DRAFT_TOOL: "ZohoMail_sendEmail"; /** Zoho tool listing the mail accounts (to find an account id from an address). */ readonly ZOHO_ACCOUNTS_TOOL: "ZohoMail_getMailAccounts"; /** Timeout for one draft (initialize + call), ms. */ readonly ZOHO_DRAFT_TIMEOUT_MS: 30000; /** Message an agent gets when it calls a denied tool. */ readonly DENIED_TOOL_MESSAGE: "sending mail is not allowed for agents; save a draft instead"; /** * OAuth state (clients, tokens, pending sign-ins) under CREWLY_HOME: * AES-256-GCM with the install's master key, mode 0600. */ readonly OAUTH_STORE_FILE: "remote-mcp-oauth.enc"; /** Master key the OAuth store is encrypted with (under CREWLY_HOME). */ readonly OAUTH_MASTER_KEY_FILE: "credentials/master.key"; /** Cloud broker that hosts the public redirect URI (crewly-services auth). */ readonly OAUTH_CLOUD_PATH: "/api/cloud/mcp-oauth"; /** Client name sent in dynamic client registration. */ readonly OAUTH_CLIENT_NAME: "Crewly"; /** Refresh an access token this long before it expires (Zoho's last ~1 h). */ readonly OAUTH_REFRESH_MARGIN_MS: number; /** Assumed lifetime when the token endpoint omits expires_in. */ readonly OAUTH_DEFAULT_TOKEN_TTL_MS: number; /** Discovery / registration / token request timeout. */ readonly OAUTH_REQUEST_TIMEOUT_MS: 15000; /** Poll the Cloud broker this often while the link is untapped. */ readonly OAUTH_POLL_SLOW_MS: 20000; /** ...and this often once the owner has opened it. */ readonly OAUTH_POLL_FAST_MS: 2000; /** Post the "sign in" card for a server at most this often while its link is unused. */ readonly OAUTH_CARD_THROTTLE_MS: number; /** Agent → backend proxy path suffix: `/api/connectors/remote-mcp//mcp`. */ readonly PROXY_SUFFIX: "/mcp"; /** JSON-RPC error code the proxy answers with when the owner must sign in. */ readonly PROXY_NEEDS_AUTH_RPC_CODE: -32001; }; /** * Reconciler wake pacing (CREW-304). On 2026-10-06 the reconciler traced * 2,295 failed wakes in 6h for two CE sessions — one per 10s fast-loop tick — * because a wake that did not work was simply asked again next tick. */ export declare const RECONCILER_WAKE_CONSTANTS: { /** First wait after a failed start / rehydrate / redeliver for one session + WorkItem (ms) */ readonly FAILURE_BACKOFF_BASE_MS: 60000; /** Ceiling of the doubling failure backoff (ms) */ readonly FAILURE_BACKOFF_MAX_MS: number; /** * How long a pass waits on a start before treating it as pending (ms). A * start queued behind the running-agent cap can wait minutes for a slot; * the reconciler must not stall every other rule meanwhile, nor ask again. */ readonly START_AWAIT_MS: 15000; /** * Wait after a SUCCESSFUL wake before the same session + WorkItem is woken again (ms). Failed wakes * were paced (CREW-304) but successful ones were not: a WorkItem that stayed queued after its * wake "worked" got a wake every pass (637 wakes for one agent in ~2h, 2026-10-07). */ readonly SUCCESS_BACKOFF_BASE_MS: number; /** Ceiling of the doubling success backoff (ms) */ readonly SUCCESS_BACKOFF_MAX_MS: number; /** At most this many successful wakes per session inside {@link SESSION_WAKE_WINDOW_MS}, whatever the WorkItem */ readonly MAX_WAKES_PER_SESSION_PER_WINDOW: 6; /** Rolling window of {@link MAX_WAKES_PER_SESSION_PER_WINDOW} (ms) */ readonly SESSION_WAKE_WINDOW_MS: number; }; /** * Creation-time dedupe (audit 2026-10-14): tickets CE-169..176 were created as four pairs within 90s, * one promise became ten "Follow through" WorkItems, and 93 identical agent-to-agent messages were * resent within 5 minutes. The same creator making the same thing again inside the window gets the * existing one back. */ export declare const DUPLICATE_CREATION_CONSTANTS: { /** Same creator + same normalized title for a project ticket within this long is the same ticket (ms) */ readonly TICKET_WINDOW_MS: number; /** Same creator + target + normalized title for a WorkItem within this long is the same WorkItem (ms) */ readonly WORK_ITEM_WINDOW_MS: number; /** One harness "Follow through" WorkItem per target + promise text within this long, whatever its status (ms) */ readonly FOLLOW_THROUGH_WINDOW_MS: number; /** An identical agent message to the same target within this long is dropped (ms) */ readonly AGENT_MESSAGE_WINDOW_MS: number; /** Messages shorter than this (characters, normalized) are never treated as duplicates ("ok", "thanks") */ readonly AGENT_MESSAGE_MIN_CHARS: 20; /** Senders whose repeats are deliberate redeliveries paced elsewhere (the reconciler) */ readonly AGENT_MESSAGE_EXEMPT_SENDERS: readonly string[]; /** Most message fingerprints kept in memory */ readonly AGENT_MESSAGE_MAX_TRACKED: 2000; }; /** * Harness reminders (follow-up, owner report due, follow-through). A reminder says which attempt it * is and how old the original request is, tells the agent not to re-post what the owner already has, * and stops once the request is too old (audit 2026-10-14: 317 reminders in 7 days, 77 sent more * than 24h after the request). */ export declare const HARNESS_REMINDER_CONSTANTS: { /** Reminders sent for one item before the harness stops (the first prompt counts as attempt 1) */ readonly MAX_REMINDERS_PER_ITEM: 2; /** A request older than this is not reminded about; the team lead is told once instead (ms) */ readonly CUTOFF_MS: number; }; /** * Stalled-agent recovery (CREW-303). A member with queued work that nobody * progresses — its session stopped, hung, or idle — is restarted by the * reconciler instead of waiting for an owner (Owen, 2026-10-06: verify items * queued 13:09–17:50Z with his session down and nothing restarting it). */ export declare const STALLED_AGENT_RECOVERY_CONSTANTS: { /** Queued work this old (ms, since it was queued) marks its target as stalled */ readonly QUEUED_WORK_AGE_MS: number; /** * Work a STOPPED agent still holds (running / accepted / proposed, or blocked * by its outage) marks it as stalled once it has been down this long (ms). */ readonly HELD_WORK_STOPPED_MS: number; /** An awake agent counts as idle-not-progressing after this long without activity (ms) */ readonly IDLE_NO_PROGRESS_MS: number; /** One recovery per session at most this often (ms) */ readonly RECOVERY_COOLDOWN_MS: number; /** * Dropout reasons that mean the HARNESS parked the agent (idle stop, slot * freed for another agent, Crewly update, finished its task, stalled-work * recovery). A team dormant only because of these is resuming, not being * launched: the reconciler's own wake for that team's queued work does not * need a fresh owner approval. */ readonly HARNESS_PARK_REASONS: readonly string[]; /** A parked team counts as resuming for this long after the park (ms) */ readonly PARKED_RESUME_WINDOW_MS: number; }; /** * Owner-thread sentinel (specs/2026-10-08-owner-thread-sentinel.md): an owner * Slack thread where an agent promised something, or has owner work / a card * in flight, is never left silent when that agent gets stuck. */ export declare const OWNER_THREAD_SENTINEL_CONSTANTS: { /** Persisted state under CREWLY_HOME */ readonly STORE_FILENAME: "owner-thread-sentinel.json"; /** Evaluation cadence (promise deadlines, re-asks, restart lines) */ readonly TICK_MS: number; /** A thread stays watched this long after the owner's last unanswered message / the agent's last promise */ readonly ACTIVE_WINDOW_MS: number; /** Threads with no activity for this long are forgotten */ readonly FORGET_AFTER_MS: number; /** The same status is never posted again in a thread within this window (flapping guard) */ readonly REPEAT_STATE_MS: number; /** Two informational status lines in one thread are at least this far apart (actionable ones are not held) */ readonly MIN_INFO_GAP_MS: number; /** A promise of "~N min" is overdue at N times this factor */ readonly DEADLINE_FACTOR: 1.5; /** Floor of a promise deadline (ms) */ readonly MIN_DEADLINE_MS: number; /** * A promise with no stated time ("做好发到 #x", "I'll post the result") is * overdue after this much silence from the agent in the thread (#1240) (ms) */ readonly NO_ETA_SILENCE_MS: number; /** Longest promise the sentinel times (longer ones are not timed) */ readonly MAX_PROMISE_MINUTES: number; /** Slot freeing / idle stop spare an agent whose owner message or promise is this recent */ readonly RECENT_PROMISE_MS: number; /** After an approval card expires, the agent has this long to ask again before it is nudged once */ readonly REASK_GRACE_MS: number; /** At most this many threads get a line for one agent-wide event */ readonly MAX_THREADS_PER_EVENT: 1; /** Cap on tracked threads (oldest dropped) */ readonly MAX_THREADS: 500; /** Characters of a reason / question quoted in a status line */ readonly QUOTE_CHARS: 160; /** Cards older than this do not make their asker "work in hand" at boot */ readonly BOOT_CARD_MAX_AGE_MS: number; /** A card goes into an owner thread only when that thread was active this recently */ readonly CARD_THREAD_MAX_AGE_MS: number; /** * A watched thread with no owner activity for this long is dropped at * load and never counted as owed again (crewly#1344): a thread from 10-07 * whose owner last wrote on 10-08 was re-stamped "restarted" on every boot * and listed as "work in hand" for ten days. */ readonly STALE_AFTER_MS: number; /** * An agent counts as "in an owner conversation" while the owner wrote to * it (or it promised the owner something) in a watched thread within this * window — the per-task clear and the slot-cap eviction spare it (crewly#1344). */ readonly OWNER_ACTIVE_MS: number; }; /** * Drive mode briefing (specs/2026-10-08-drive-mode.md): the owner's queue of * things waiting on them, read out one at a time by the voice briefer. */ export declare const BRIEFING_CONSTANTS: { /** Per-machine state file under CREWLY_HOME (snoozes, passes, pending lookups) */ readonly STATE_FILENAME: "briefing-state.json"; /** "Next" / skip hides an item for this long without settling it (ms) */ readonly SKIP_HIDE_MS: number; /** "Later" with no time: tomorrow at this local hour */ readonly LATER_DEFAULT_HOUR_LOCAL: 9; /** Longest snooze accepted (ms) */ readonly LATER_MAX_MS: number; /** A decision whose deadline is this close counts as urgent (ms) */ readonly URGENT_DEADLINE_MS: number; /** A ticket that silence accepts this soon counts as normal instead of low (ms) */ readonly REVIEW_SOON_MS: number; /** A pending lookup older than this is given up and the item comes back (ms) */ readonly LOOKUP_GIVE_UP_MS: number; /** Spoken summary: longest length (characters) */ readonly SUMMARY_MAX_CHARS: 220; /** Details: longest length (characters) */ readonly DETAILS_MAX_CHARS: 2400; /** An agent's lookup answer: longest length handed to the briefer (characters) */ readonly LOOKUP_ANSWER_MAX_CHARS: 1500; /** Owner answer / question: longest length accepted (characters) */ readonly TEXT_MAX_CHARS: 2000; /** Items listed at most */ readonly MAX_ITEMS: 50; /** A confirmation token stays valid this long (ms) */ readonly CONFIRM_TTL_MS: number; /** Agent timeline messages looked at for a lookup answer */ readonly LOOKUP_SCAN_LIMIT: 30; /** An open card this long past its deadline is stale, not read out (ms) */ readonly CARD_STALE_AFTER_DEADLINE_MS: number; /** A card (or reply question) asked longer ago than this is stale, not read out (ms) */ readonly CARD_MAX_AGE_MS: number; /** A finished ticket in review longer than this is stale, not asked about (ms) */ readonly REVIEW_MAX_AGE_MS: number; /** A message this long (CJK counts double) with no request verb is a reflective voice note, not an ask */ readonly REVIEW_DISCUSSION_MIN_WEIGHTED: 220; /** Review line: longest quote of what the owner asked (characters) */ readonly REVIEW_ASK_MAX_CHARS: 70; /** Review item label (what the owner asked, for lists): longest length (characters) */ readonly REVIEW_LABEL_MAX_CHARS: 40; /** Review line: longest quote of what the agent reports (characters) */ readonly REVIEW_RESULT_MAX_CHARS: 110; /** Two cards from one asker whose questions are at least this similar (0–1, character bigrams) are duplicates */ readonly CARD_DUPLICATE_SIMILARITY: 0.6; /** Words in a title / question that make an item sensitive (spoken confirmation required) */ readonly SENSITIVE_PATTERN: RegExp; /** Decision sensitivities that always need a spoken confirmation */ readonly SENSITIVE_DECISION_KINDS: readonly ["email", "publish", "deploy", "spend", "browser_action", "app_access"]; /** Error codes returned to the page / the voice model */ readonly CODES: { readonly OWNER_ONLY: "owner_only"; readonly NOT_FOUND: "not_found"; readonly NOT_READY: "not_ready"; readonly INVALID: "invalid"; readonly CONFIRM_MISMATCH: "confirm_mismatch"; readonly FAILED: "failed"; }; }; /** * Drive mode, the machine side (specs/2026-10-08-drive-mode.md §7). Crewly * Cloud hosts the session; this machine delivers the owner's words to its * agents, carries their `reply --drive` answers to Cloud, reads back an * agent's recent messages (recall) and has each agent post its recap. */ export declare const DRIVE_CONSTANTS: { /** Per-machine file under CREWLY_HOME: this machine's Drive mode conversations */ readonly STATE_FILENAME: "drive-conversations.json"; /** Capability advertised to Cloud while the handler runs */ readonly CAPABILITY: "drive_message"; /** Relay message type Cloud reuses for Drive mode (told apart by `kind`) */ readonly RELAY_MESSAGE_TYPE: "talk_message"; readonly RELAY_KIND: "drive"; /** Cloud routes (under `/api/cloud/talk/session/:id`) */ readonly CLOUD_BASE_PATH: "/api/cloud/talk/session"; /** A session id Cloud makes */ readonly SESSION_ID_PATTERN: RegExp; /** Local conversations are forgotten this long after they started (ms) */ readonly KEEP_MS: number; /** Agent reply: longest length sent (characters) */ readonly TEXT_MAX_CHARS: 4000; /** Recall: how far back an agent's messages to the owner are read (ms) */ readonly RECALL_WINDOW_MS: number; /** Recall: messages returned at most */ readonly RECALL_MAX: 4; /** Recall: one message, longest length (characters) */ readonly RECALL_MESSAGE_MAX_CHARS: 900; /** Recall: messages scanned at most */ readonly RECALL_SCAN_LIMIT: 3000; /** Cloud calls: timeout and retries */ readonly REQUEST_TIMEOUT_MS: 10000; readonly FETCH_RETRIES: 3; /** Metadata `via` on every chat-v2 row Drive mode writes (the Slack mirrors skip them) */ readonly VIA: "drive-mode"; /** Keep-warm (v3): agents one session keeps warm, at most */ readonly MAX_WARM: 20; /** Status check (`op:'refresh'`): how long the agents get to update their items before the snapshot is rebuilt (ms) */ readonly STATUS_CHECK_WAIT_MS: 12000; /** Status check: agents asked at most (the ones holding the most open items first) */ readonly STATUS_CHECK_MAX_AGENTS: 6; /** Status check: items listed to one agent at most */ readonly STATUS_CHECK_MAX_ITEMS: 5; /** Session logs Cloud copies here (`op:'log'`): directory under CREWLY_HOME, `-.md` + `.jsonl` */ readonly LOG_DIRNAME: "drive-logs"; /** Local session logs are deleted this long after they were written (ms) */ readonly LOG_KEEP_MS: number; /** A log file larger than this is refused (bytes) */ readonly LOG_MAX_BYTES: number; /** `GET /api/drive-logs`: default and largest page */ readonly LOG_LIST_DEFAULT: 10; readonly LOG_LIST_MAX: 50; }; /** * Meetings P2b (crewly#1415): a voice 1:1 meeting with one agent. Cloud asks * this machine for the agent's meeting pack at join (`op:'meeting_pack'`), * searches its wiki live (`op:'wiki_search'`) and sends write-backs (a * delivery carrying `writeBack`) that land in the thread they belong to. */ export declare const MEETING_CONSTANTS: { /** Capability advertised to Cloud while the Drive handler runs with a pack builder */ readonly CAPABILITY: "meeting_pack"; /** Metadata `source` on the chat rows a write-back records: the owner's voice, via Cloud */ readonly SOURCE: "cloud-talk"; /** Metadata `via` on those rows (tells them apart from Talk and Drive mode rows) */ readonly VIA: "meeting"; /** How far back owner↔agent conversations are read for the agenda (ms) */ readonly AGENDA_WINDOW_MS: number; /** Agent timeline rows read at most */ readonly TIMELINE_SCAN: 200; /** Agenda topics at most */ readonly AGENDA_MAX: 15; /** One agenda topic: longest one-line topic (characters) */ readonly TOPIC_MAX_CHARS: 120; /** One agenda topic: recent lines kept, and their length */ readonly TOPIC_RECENT_MAX: 3; readonly TOPIC_RECENT_MAX_CHARS: 300; /** Owner↔agent messages in the pack at most, and their length */ readonly MESSAGES_MAX: 20; readonly MESSAGE_MAX_CHARS: 600; /** Handover text kept (characters) */ readonly HANDOVER_MAX_CHARS: 12000; /** Work items listed at most */ readonly WORK_MAX: 15; /** Waiting-on-owner items at most */ readonly WAITING_MAX: 10; /** Recent decisions at most, and how far back (ms) */ readonly DECISIONS_MAX: 10; readonly DECISIONS_WINDOW_MS: number; /** Wiki pages in the pack at most, and their length */ readonly WIKI_PAGES_MAX: 10; readonly WIKI_PAGE_MAX_CHARS: 1500; /** Live wiki search: hits returned at most, and their length */ readonly WIKI_HITS_MAX: 5; readonly WIKI_HIT_MAX_CHARS: 700; /** * Whole pack: token budget (estimate: CJK 1 a character, else 4 characters a token). * The pack sits in the Live system instruction and is billed on every turn, * so it stays small enough to leave room for the conversation. */ readonly PACK_MAX_TOKENS: 15000; /** A write-back's longest text (characters) */ readonly WRITE_BACK_MAX_CHARS: 1500; /** New-topic threads remembered per session + topic (so a second write-back reuses the thread) */ readonly NEW_THREADS_MAX: 200; }; /** * Ticket hygiene: keeps the project tickets honest so status views (Drive * mode, boards) do not show finished work as open. * * - An hourly sweep closes or advances tickets whose state is objectively * settled and flags tickets whose assignee / team no longer exists. No agent * is woken for it. * - Once a day per team, the team lead (the orchestrator when there is none) * gets ONE batched WorkItem listing the team's stale tickets to decide. */ export declare const TICKET_HYGIENE_CONSTANTS: { /** Kill switch: `0` turns the whole mechanism off */ readonly ENV_SWITCH: "CREWLY_TICKET_HYGIENE"; /** State file under the Crewly home */ readonly STATE_FILENAME: "ticket-hygiene-state.json"; /** How often the sweep runs (ms) */ readonly SWEEP_INTERVAL_MS: number; /** First sweep this long after boot (ms), so boot work settles first */ readonly FIRST_SWEEP_DELAY_MS: number; /** `in_progress` / `review` ticket with no activity this long is stale (days) */ readonly STALE_ACTIVE_DAYS: 3; /** `ready` / `backlog` ticket with no activity this long is stale (days) */ readonly STALE_IDLE_DAYS: 14; /** A ticket already sent to the lead is not sent again for this long (days) */ readonly REVIEW_COOLDOWN_DAYS: 3; /** At most one review batch per team this often (ms) */ readonly REVIEW_INTERVAL_MS: number; /** A review item still queued (never picked up) after this long is cancelled and may be replaced (ms) */ readonly REVIEW_STALE_QUEUED_MS: number; /** Max tickets in one batch (oldest activity first); the rest wait for the next day */ readonly MAX_TICKETS_PER_REVIEW: 15; /** Max characters of a ticket title in the brief */ readonly TITLE_MAX_CHARS: 120; /** WorkItem type of the batch */ readonly REVIEW_WORK_ITEM_TYPE: "ticket_hygiene_review"; /** `metadata.kind` of the batch (also how a live one is found) */ readonly REVIEW_METADATA_KIND: "ticket_hygiene_review"; /** Actor written in the ticket Log by the sweep (its lines never count as activity) */ readonly ACTOR: "crewly-hygiene"; /** Reason written in every automatic Log line */ readonly REASON: "auto-reconcile"; /** Label the sweep puts on a ticket whose assignee or team no longer exists */ readonly ORPHAN_LABEL: "orphaned"; /** Entries in the per-ticket "already sent" memory are dropped after this long (days) */ readonly STATE_RETENTION_DAYS: 30; }; /** * Routine nudge (crewly#1034): when a ticket is verified done and its result * describes recurring work, the verifying lead gets ONE internal WorkItem * telling it to set a routine or say why not. Never creates the cron itself. */ export declare const TICKET_ROUTINE_NUDGE_CONSTANTS: { /** Kill switch: `0` turns the nudge off */ readonly ENV_SWITCH: "CREWLY_TICKET_ROUTINE_NUDGE"; /** `metadata.kind` of the nudge WorkItem */ readonly METADATA_KIND: "ticket_routine_nudge"; /** Prefix of the ticket Log line that marks the nudge as sent (also the dedupe key) */ readonly LOG_MARKER: "routine nudge sent to"; /** Actor written in the ticket Log */ readonly ACTOR: "crewly-routine-nudge"; /** Max characters of the result text scanned for a cadence */ readonly MAX_SCAN_CHARS: 6000; /** Max characters of the matched sentence quoted in the nudge */ readonly MAX_QUOTE_CHARS: 240; /** Max characters of the ticket title in the nudge */ readonly TITLE_MAX_CHARS: 120; }; /** * Drive mode v3 status briefing (specs/2026-10-09-drive-mode-v3.md §1): the * compact per-team / per-agent snapshot this machine keeps up to date and * pushes to Crewly Cloud, so the voice answers status questions with no * agent round trip. Built from structured data only — no LLM calls. */ export declare const DRIVE_BRIEFING_CONSTANTS: { /** Cloud route (`:instanceId` = this machine's device id) */ readonly PUT_PATH: "/api/cloud/instances/:instanceId/briefing"; /** `0` / `off` turns the snapshot push off */ readonly KILL_SWITCH_ENV: "CREWLY_DRIVE_BRIEFING"; /** Gather events this long before rebuilding (ms) */ readonly DEBOUNCE_MS: 3000; /** At most one rebuild this often; events in between are coalesced (ms) */ readonly MIN_INTERVAL_MS: 30000; /** A project's tickets are re-read after this even without a change event (ms) */ readonly TICKET_CACHE_MS: number; /** The task pool is re-read after this even without an event (ms) */ readonly POOL_CACHE_MS: 60000; /** Rebuild anyway this often; uploads only when it changed (ms) */ readonly CHECK_INTERVAL_MS: 60000; /** Upload even when unchanged this often (Cloud sees the machine is alive) (ms) */ readonly FULL_SYNC_INTERVAL_MS: number; /** Cloud call timeout (ms) */ readonly REQUEST_TIMEOUT_MS: 10000; /** Backoff after a failed upload (ms) */ readonly BACKOFF_INITIAL_MS: 15000; readonly BACKOFF_MAX_MS: number; /** Cloud does not take snapshots (not deployed / refused): check again after (ms) */ readonly UNAVAILABLE_RETRY_MS: number; /** Largest snapshot sent (bytes of JSON); Cloud refuses above 64 KB */ readonly MAX_BYTES: number; readonly MAX_TEAMS: 30; readonly MAX_AGENTS: 80; readonly MAX_ITEMS: 80; readonly MAX_WAITING: 30; /** An agent's recent messages to the owner, at most */ readonly MAX_LAST_TO_OWNER: 3; /** How far back an agent's messages to the owner are read (ms) */ readonly OWNER_FEED_WINDOW_MS: number; /** chat-v2 rows scanned for them, at most */ readonly OWNER_FEED_SCAN_LIMIT: 1500; /** Done / cancelled work older than this is left out (ms) */ readonly DONE_WINDOW_MS: number; /** Text lengths (characters) */ readonly NAME_MAX: 60; readonly TITLE_MAX: 140; readonly TEXT_MAX: 240; }; /** * Drive mode voice layer: Gemini Live ephemeral tokens minted on this * machine with its Gemini API key (specs/2026-10-08-drive-mode.md §3). */ export declare const TALK_LIVE_CONSTANTS: { /** Live model (stable, low latency, native audio). Override: CREWLY_GEMINI_LIVE_MODEL */ readonly DEFAULT_MODEL: "gemini-3.8-live"; readonly MODEL_ENV: "CREWLY_GEMINI_LIVE_MODEL"; /** API version ephemeral tokens work on (the SDK uses v1alpha). Override: CREWLY_GEMINI_LIVE_API_VERSION */ readonly DEFAULT_API_VERSION: "v1alpha"; readonly API_VERSION_ENV: "CREWLY_GEMINI_LIVE_API_VERSION"; /** Gemini API base */ readonly API_BASE: "https://generativelanguage.googleapis.com"; /** Live WebSocket base (the page appends `/ws/google.ai.generativelanguage..GenerativeService.BidiGenerateContentConstrained`) */ readonly WS_BASE: "wss://generativelanguage.googleapis.com"; /** Env fallback when no key is stored for Antigravity */ readonly API_KEY_ENV: "GEMINI_API_KEY"; /** The token opens one session within this window (ms) */ readonly NEW_SESSION_WINDOW_MS: number; /** The session may run this long on one token (ms) */ readonly TOKEN_TTL_MS: number; /** Sessions one token may open */ readonly TOKEN_USES: 1; /** Google's answer deadline (ms) */ readonly REQUEST_TIMEOUT_MS: 15000; /** * Gemini Live voice activity detection, locked into the token's setup * (`realtimeInputConfig.automaticActivityDetection`). LOW start * sensitivity: a cough, a click or road noise does not count as the owner * starting to speak, so it no longer cuts the voice off. LOW end * sensitivity + 800 ms of silence: a short pause mid-sentence does not end * the owner's turn. 300 ms of padding keeps the first syllable. */ readonly VAD: { readonly START_OF_SPEECH_SENSITIVITY: "START_SENSITIVITY_LOW"; readonly END_OF_SPEECH_SENSITIVITY: "END_SENSITIVITY_LOW"; readonly PREFIX_PADDING_MS: 300; readonly SILENCE_DURATION_MS: 800; /** Speech over the voice interrupts it (barge-in stays on) */ readonly ACTIVITY_HANDLING: "START_OF_ACTIVITY_INTERRUPTS"; }; /** * Setup fields locked as a whole in the token's field mask. Google * refuses a mask path into an array (`tools.0`) — and a nested object is * masked by its top-level key too (`realtimeInputConfig`). */ readonly FIELD_MASK_WHOLE: readonly ["tools", "realtimeInputConfig"]; /** Gemini prebuilt voice the briefer speaks with (multilingual) */ readonly VOICE: "Aoede"; /** Languages the briefer starts in */ readonly LANGUAGES: readonly ["zh", "en", "es"]; readonly DEFAULT_LANGUAGE: "zh"; readonly CODES: { readonly OWNER_ONLY: "owner_only"; readonly NO_KEY: "no_gemini_key"; readonly GOOGLE_REJECTED: "google_rejected"; readonly UNREACHABLE: "unreachable"; readonly FAILED: "failed"; }; }; /** * Team roster (specs/2026-10-10-team-roster.md): the harness-generated map of * who owns what, injected into the orchestrator's prompt and served by the * `team-roster` / `find-owner-for` skills. */ export declare const TEAM_ROSTER_CONSTANTS: { /** Token budget of the roster section in the orchestrator prompt */ readonly PROMPT_MAX_TOKENS: 2500; /** How long a built roster is reused before it is rebuilt (ms) */ readonly CACHE_TTL_MS: 60000; /** Window of "recently finished" work (days) */ readonly RECENT_DAYS: 7; /** Window of ticket titles that feed matching, beyond open and recent work (days) */ readonly MATCH_HISTORY_DAYS: 30; /** Longest charter a lead may store (chars) */ readonly CHARTER_MAX_CHARS: 400; /** A description with fewer words (CJK: characters) than this is not a charter; one is derived instead */ readonly MIN_DESCRIPTION_TOKENS: 5; /** Work titles are cut to this many chars in the roster */ readonly TITLE_MAX_CHARS: 70; /** Member specialties shown per member */ readonly MAX_SPECIALTIES: 3; /** Default number of candidates `find-owner-for` returns */ readonly FIND_DEFAULT_LIMIT: 3; /** Hard cap on candidates */ readonly FIND_MAX_LIMIT: 8; /** A runner-up scoring at least this share of the best one makes the match ambiguous */ readonly AMBIGUOUS_SCORE_RATIO: 0.85; /** Longest request text matched (chars) */ readonly QUERY_MAX_CHARS: 600; /** Heading of the roster section */ readonly HEADING: "## Team Roster"; /** SessionStart sources after which the orchestrator's context lost the prompt: the hook hands the roster back */ readonly REINJECT_SOURCES: readonly ["compact", "clear"]; /** Boost for a request that names a member or team outright */ readonly NAME_MATCH_BOOST: 6; /** Detail tiers, richest first: open items, finished items, charter chars */ readonly TIERS: readonly [{ readonly open: 4; readonly done: 3; readonly charter: 260; readonly specialties: true; }, { readonly open: 2; readonly done: 1; readonly charter: 160; readonly specialties: true; }, { readonly open: 0; readonly done: 0; readonly charter: 120; readonly specialties: true; }, { readonly open: 0; readonly done: 0; readonly charter: 80; readonly specialties: false; }]; }; /** * Team move (crewly#1189): move a whole team to another machine of the same * account through Crewly Cloud. Cloud only relays an end-to-end encrypted * package; both machines run this code. See specs/2026-10-12-team-move.md. */ export declare const TEAM_MOVE_CONSTANTS: { /** Capability advertised to Cloud in the heartbeat once the driver runs. */ readonly CAPABILITY: "teamMove:1"; /** Relay message `type` Cloud pushes (`{moveId, role, action}`). */ readonly RELAY_MESSAGE_TYPE: "team_move"; /** Cloud REST path (under the Cloud base URL). */ readonly CLOUD_PATH: "/api/cloud/team-moves"; /** Package schema version; bump on any breaking layout change. */ readonly SCHEMA_VERSION: 1; /** Package kind written to the manifest. */ readonly PACKAGE_KIND: "crewly-team-move"; /** Directory under CREWLY_HOME holding per-move state (journal, keys, archive). */ readonly HOME_DIR: "team-moves"; /** Upload/download chunk size (matches Cloud's 8 MiB chunks). */ readonly TRANSFER_CHUNK_BYTES: number; /** Plaintext frame size of the encrypted stream. */ readonly CRYPTO_FRAME_BYTES: number; /** The source keeps the archived team this long for rollback. */ readonly ARCHIVE_RETENTION_MS: number; /** Per-file cap for project-local `.crewly` files in a package. */ readonly PROJECT_FILE_MAX_BYTES: number; /** Total cap for project-local `.crewly` files in a package (all projects). */ readonly PROJECT_FILES_MAX_BYTES: number; /** Total cap for the working files of projects that are not git repositories (all such projects). */ readonly WORK_FILES_MAX_BYTES: number; /** Per-file cap for those working files. */ readonly WORK_FILE_MAX_BYTES: number; /** Env vars (megabytes) overriding the two caps above. */ readonly WORK_FILES_MAX_MB_ENV: "CREWLY_TEAM_MOVE_WORK_MAX_MB"; readonly WORK_FILE_MAX_MB_ENV: "CREWLY_TEAM_MOVE_WORK_FILE_MAX_MB"; /** Directory names never packaged from a non-git project (dependencies and build output). */ readonly WORK_EXCLUDED_DIRS: readonly ["node_modules", ".next", "dist", "build", ".cache", ".crewly", ".git"]; /** A machine counts as live when its Cloud heartbeat is newer than this. */ readonly MACHINE_LIVE_WINDOW_MS: number; /** Cloud instance ids/names that are not machines (virtual instances). */ readonly NON_MACHINE_INSTANCE_PATTERN: "^crewly-cloud"; /** Newest handover notes kept per agent. */ readonly HANDOVERS_PER_AGENT: 10; /** * Largest Claude Code transcript (the agent's current conversation) that * travels with a move, per agent (crewly#1344). Only the latest * conversation per agent is packaged, under `transcripts//`, and * resumed on the target; a bigger one is left behind and listed. */ readonly TRANSCRIPT_MAX_BYTES: number; /** Newest chat-v2 messages of the team's Slack room carried in the package (per channel). */ readonly CHAT_MESSAGES_PER_CHANNEL: 500; /** Total cap on carried chat messages' content (bytes), all channels together. */ readonly CHAT_MESSAGES_MAX_BYTES: number; /** * Suffix of the folder a project is given on the target when the team * lists a project the package did not carry: a clearly flagged placeholder * the owner is told to re-point (never silently dropped, crewly#1344). */ readonly PLACEHOLDER_PROJECT_SUFFIX: "-NEEDS-PATH"; /** Largest text file that is path-rewritten and secret-scanned in memory. */ readonly TEXT_FILE_MAX_BYTES: number; /** Free disk the target needs beyond the package (bytes): 2x package + this. */ readonly DISK_HEADROOM_BYTES: number; /** Oldest Crewly version that can take part in a move. */ readonly MIN_CREWLY_VERSION: "1.20.304"; /** Folder (under the target's home dir) projects are relocated to. */ readonly DEFAULT_PROJECTS_DIR_NAME: "crewly-projects"; /** Env var that overrides the target's projects root. */ readonly PROJECTS_ROOT_ENV: "CREWLY_PROJECTS_ROOT"; /** Cloud request timeout (ms). */ readonly REQUEST_TIMEOUT_MS: 30000; /** Upload/download retries per chunk. */ readonly CHUNK_RETRIES: 4; /** Backoff base between retries (ms). */ readonly RETRY_BASE_MS: 1000; /** How often the driver re-reads pending moves from Cloud (ms). */ readonly POLL_INTERVAL_MS: number; /** Delay before the first pending-move check after boot (ms). */ readonly BOOT_DELAY_MS: 20000; /** Shortest wall-clock limit of one git network call (ms); also the default when the repository size is unknown. */ readonly GIT_TIMEOUT_MS: number; /** Longest wall-clock limit of one git network call (ms), however big the repository. */ readonly GIT_MAX_TIMEOUT_MS: number; /** Transfer speed assumed when scaling the limit to the repository size (bytes per second). */ readonly GIT_ASSUMED_BYTES_PER_SEC: number; /** A git network call that prints no progress for this long is stalled and gets killed (ms). */ readonly GIT_STALL_MS: number; /** `git ls-remote` access test (ms). */ readonly GIT_LS_REMOTE_TIMEOUT_MS: 30000; /** How often an import logs (and tells Cloud) what the clone is doing (ms). */ readonly PROGRESS_REPORT_INTERVAL_MS: 30000; /** "Carry uncommitted changes": most bytes of patches and untracked files taken from one repository. */ readonly CARRY_MAX_BYTES: number; /** ...largest single patch or untracked file. */ readonly CARRY_FILE_MAX_BYTES: number; /** Env vars (megabytes) overriding the two carry caps above. */ readonly CARRY_MAX_MB_ENV: "CREWLY_TEAM_MOVE_CARRY_MAX_MB"; readonly CARRY_FILE_MAX_MB_ENV: "CREWLY_TEAM_MOVE_CARRY_FILE_MAX_MB"; /** Env vars (megabytes) overriding the `.crewly` project-file caps (PROJECT_FILE_MAX_BYTES / PROJECT_FILES_MAX_BYTES). */ readonly PROJECT_FILE_MAX_MB_ENV: "CREWLY_TEAM_MOVE_PROJECT_FILE_MAX_MB"; readonly PROJECT_FILES_MAX_MB_ENV: "CREWLY_TEAM_MOVE_PROJECT_FILES_MAX_MB"; /** Git bundle ("carry the repo"): largest bundle of one repository. Streamed to disk, never held in memory. */ readonly BUNDLE_REPO_MAX_BYTES: number; /** ...all bundles of one move together. */ readonly BUNDLES_MAX_BYTES: number; /** Env vars (megabytes) overriding the two bundle caps above. */ readonly BUNDLE_REPO_MAX_MB_ENV: "CREWLY_TEAM_MOVE_BUNDLE_MAX_MB"; readonly BUNDLES_MAX_MB_ENV: "CREWLY_TEAM_MOVE_BUNDLES_MAX_MB"; /** Package path (below `projects//`) of a repository's bundle. */ readonly BUNDLE_FILE: "bundle/repo.bundle"; /** `git bundle create` timeout (ms). */ readonly BUNDLE_TIMEOUT_MS: number; /** ...most changed files (patches plus untracked files) per repository. */ readonly CARRY_MAX_FILES: 2000; /** Changed files named per repository in a preflight report. */ readonly PREFLIGHT_TOP_FILES: 8; /** Flagged files named in a preflight report. */ readonly PREFLIGHT_MAX_FINDINGS: 30; /** The heavy part of a preflight (package dry run + secret scan) is reused this long while nothing changed (ms). */ readonly PREFLIGHT_SCAN_TTL_MS: number; /** Relay actions Cloud pushes for a preflight (before any move exists). */ readonly PREFLIGHT_ACTIONS: readonly ["preflight", "ask_team"]; /** Tools a package may require; found by name in prompts, norms and SOPs. */ readonly KNOWN_TOOLS: readonly ["ffmpeg", "docker", "xcodebuild", "gh", "python3", "node", "git", "jq", "yt-dlp", "imagemagick", "whisper-cli"]; /** Binary each known tool is looked up by on PATH. */ readonly TOOL_BINARIES: { readonly ffmpeg: "ffmpeg"; readonly docker: "docker"; readonly xcodebuild: "xcodebuild"; readonly gh: "gh"; readonly python3: "python3"; readonly node: "node"; readonly git: "git"; readonly jq: "jq"; readonly 'yt-dlp': "yt-dlp"; readonly imagemagick: "convert"; readonly 'whisper-cli': "whisper-cli"; }; }; /** * Owner Board, machine side (crewly#1247): the activity normalizer, the * snapshot builder and the push to Crewly Cloud. Wire contract: * `services/board/board.types.ts`. */ export declare const BOARD_CONSTANTS: { /** Kill switch: `CREWLY_BOARD_PUSH=0` turns the push off (the local `GET /api/board` still works) */ readonly ENV_SWITCH: "CREWLY_BOARD_PUSH"; /** Machine → Cloud: full snapshot */ readonly SNAPSHOT_PATH: "/api/cloud/board/snapshot"; /** Machine → Cloud: changed agent rows + timeline events */ readonly DELTA_PATH: "/api/cloud/board/delta"; /** Header carrying this machine's Cloud instance id */ readonly INSTANCE_HEADER: "X-Crewly-Instance-Id"; /** Push a full snapshot at least this often (ms); Cloud may ask for another cadence */ readonly FULL_INTERVAL_MS: 60000; /** A change is pushed within this long (ms); later changes join the same delta */ readonly DELTA_DELAY_MS: 2000; /** First failure waits this long, doubling up to the max (ms) */ readonly BACKOFF_INITIAL_MS: 5000; readonly BACKOFF_MAX_MS: number; /** Cloud answered 404 / 400 / 503: not deployed or refusing; look again after this (ms) */ readonly UNAVAILABLE_RETRY_MS: number; /** Request timeout (ms) */ readonly REQUEST_TIMEOUT_MS: 15000; /** Cloud's body limit (bytes) */ readonly MAX_BODY_BYTES: number; /** Text fields never exceed this many characters */ readonly MAX_TEXT: 140; /** Timeline kept per agent (events) and for how long (ms) */ readonly TIMELINE_MAX_EVENTS: 200; readonly TIMELINE_MAX_AGE_MS: number; /** Timeline events sent in one delta, per agent */ readonly DELTA_TIMELINE_MAX: 40; /** After a tool ends, its activity still shows this long (ms), then the agent is "thinking" */ readonly ACTIVITY_LINGER_MS: 20000; /** A tool start without an end is dropped after this long (a lost PostToolUse) (ms) */ readonly OPEN_TOOL_MAX_MS: number; /** Rows per outcome list */ readonly MAX_DONE: 50; readonly MAX_WAITING: 50; readonly MAX_STUCK: 30; readonly MAX_SUMMARY_PHRASES: 6; /** A queued WorkItem older than this is "stuck" (ms) */ readonly STUCK_QUEUED_MS: number; /** A running WorkItem older than this is "stuck" (ms) */ readonly STUCK_RUNNING_MS: number; /** A failed WorkItem stays on the stuck list this long (ms) */ readonly STUCK_FAILED_MAX_AGE_MS: number; /** An agent's ledger unit counts as its current task if it had usage this recently (ms) */ readonly LEDGER_TASK_MAX_AGE_MS: number; /** Project-ticket scan is cached this long (ms) */ readonly PROJECT_TICKET_CACHE_MS: number; /** Canvas links (CANVAS-V2-CONTRACT.md): at most this many in a snapshot */ readonly LINKS_MAX: 300; /** A link's label is at most this many characters */ readonly LINK_LABEL_MAX: 60; /** Delegations count while open or finished this recently (ms) */ readonly LINK_DELEGATION_WINDOW_MS: number; /** Agent-to-agent messages count this long (ms) */ readonly LINK_MESSAGING_WINDOW_MS: number; /** A messaging link is "active" if the last message is this recent (ms) */ readonly LINK_MESSAGING_ACTIVE_MS: number; /** Messages remembered per sender/receiver pair */ readonly LINK_MESSAGING_MAX_PER_PAIR: 100; /** Most agents linked by one shared ticket (pairs grow quadratically) */ readonly LINK_SAME_TASK_MAX_GROUP: 6; }; /** * Crewly Canvas, machine side (crewly#1247 v2): the live step stream, the * owner actions Cloud relays to this machine, the masked terminal tail and the * per-agent capabilities. Shared contract: CANVAS-CONTRACT.md. */ export declare const CANVAS_CONSTANTS: { /** Steps kept per agent in memory */ readonly STEPS_MAX_PER_AGENT: 300; /** Steps older than this are dropped from memory (ms) */ readonly STEPS_MAX_AGE_MS: number; /** Steps sent in one delta (all agents) and per agent */ readonly DELTA_STEPS_MAX: 120; readonly DELTA_STEPS_PER_AGENT_MAX: 40; /** Unsent steps kept per agent (a failed push loses nothing, up to this) */ readonly UNSENT_MAX_PER_AGENT: 200; /** The agent's own text lines: longest (chars) */ readonly TEXT_MAX: 280; /** An instant step equal to the previous one within this long is dropped (ms) */ readonly STEP_DEDUPE_MS: 5000; /** A tool start without an end is closed after this long (ms) */ readonly OPEN_STEP_MAX_MS: number; /** While Cloud says viewers are watching, a change is pushed within this long (ms) */ readonly LIVE_DELTA_DELAY_MS: 1000; /** A "live" answer from Cloud is believed this long (ms) */ readonly LIVE_TTL_MS: 30000; /** Transcript tail: poll cadence with viewers / without (ms) */ readonly TAIL_LIVE_INTERVAL_MS: 2000; readonly TAIL_IDLE_INTERVAL_MS: 8000; /** A transcript path found (or not found) is trusted this long (ms) */ readonly TAIL_LOCATE_TTL_MS: 30000; /** First look at a transcript reads at most this many bytes from its end */ readonly TAIL_FIRST_READ_BYTES: number; /** One pass reads at most this many new bytes per file */ readonly TAIL_MAX_READ_BYTES: number; /** First look only keeps text newer than this (ms) */ readonly TAIL_FIRST_MAX_AGE_MS: number; /** Relay message `type` Cloud pushes for an owner action (ids only) */ readonly RELAY_MESSAGE_TYPE: "canvas_action"; /** Capability advertised to Cloud once the action handler runs */ readonly CAPABILITY: "canvas_actions"; /** `GET /:actionId?instanceId=` fetches an action; `POST /:actionId/result` reports it */ readonly ACTIONS_PATH: "/api/cloud/canvas/actions"; /** Machine -> Cloud: one masked terminal frame */ readonly TERMINAL_FRAME_PATH: "/api/cloud/canvas/terminal/frame"; /** Kinds of owner action */ readonly ACTION_KINDS: readonly ["message", "stop", "terminal_open", "terminal_renew", "terminal_close"]; /** Longest owner message accepted (chars) */ readonly MESSAGE_MAX: 4000; /** Delays before retrying a failed action fetch (ms) */ readonly FETCH_RETRY_DELAYS_MS: readonly [1000, 5000, 15000]; readonly REQUEST_TIMEOUT_MS: 15000; /** Actions already carried out are remembered this long / this many */ readonly DONE_ACTIONS_TTL_MS: number; readonly DONE_ACTIONS_MAX: 500; /** Terminal stream: frame cadence, lease without a renew, longest total, and streams at once */ readonly TERMINAL_FRAME_INTERVAL_MS: 1000; readonly TERMINAL_LEASE_MS: number; readonly TERMINAL_MAX_STREAM_MS: number; readonly TERMINAL_MAX_STREAMS: 3; readonly TERMINAL_TAIL_LINES: 200; readonly TERMINAL_MAX_LINE_CHARS: 400; /** Failed frame posts in a row before the stream stops */ readonly TERMINAL_MAX_FAILURES: 5; /** A Chrome session counts as the agent's browser while it was active this recently (ms) */ readonly BROWSER_ACTIVE_MS: number; }; /** * Hand-off of an owner-approved change to a recurring job to the agent that * runs it (crewly#1137). */ export declare const RECURRING_CHANGE_HANDOFF_CONSTANTS: { /** Share of the schedule's name/title terms the decision text must contain */ readonly MIN_COVERAGE: 0.6; /** Distinct terms the decision and the schedule must share */ readonly MIN_SHARED_TERMS: 2; /** ...or this many shared terms whatever the coverage (labels mixing two languages) */ readonly SURE_SHARED_TERMS: 3; /** Terms shorter than this (Latin words) are noise */ readonly MIN_WORD_CHARS: 3; /** Chars of a schedule label that name the job (the rest is its instructions) */ readonly MAX_IDENTITY_CHARS: 80; /** * When-words that say how often or at what time of day a job runs, not what * it is about. Two unrelated jobs share them ("every morning"), so they never * count towards a match (D-747 was matched to a different 8am job through * "每天早上" alone). */ readonly GENERIC_CADENCE_PATTERN: RegExp; /** Latin words of the same kind (lower-case) */ readonly GENERIC_CADENCE_WORDS: readonly ["daily", "weekly", "monthly", "nightly", "hourly", "every", "each", "day", "week", "month", "morning", "evening", "night", "today", "tomorrow", "yesterday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday"]; /** `WorkItem.metadata` marker on the hand-off WorkItem */ readonly METADATA_KEY: "recurringChangeHandoff"; /** Option labels / answers that decline a change (nothing to hand off) */ readonly DECLINE_PATTERN: RegExp; }; /** * Automatic cross-machine file hand-off: files an agent mentions or attaches * travel through Cloud's file-handoff relay (apps SPEC section 18) and land * in the receiving agent's project inbox. */ export declare const FILE_HANDOFF_CONSTANTS: { /** Largest file Cloud accepts for one handoff (2 GB) */ readonly MAX_FILE_BYTES: number; /** Files attached to one message, explicit and automatic together */ readonly MAX_FILES_PER_MESSAGE: 5; /** Marker token a message carries; the id follows the colon */ readonly MARKER_PREFIX: "crewly-file:"; /** Glyph that starts a marker line in front of a human reader */ readonly MARKER_GLYPH: "📎"; /** Handoff id shape (22 url-safe characters after `hf_`) */ readonly ID_PATTERN: "hf_[A-Za-z0-9_-]{22}"; /** Per-file upload ceiling */ readonly UPLOAD_TIMEOUT_MS: number; /** Per-file download ceiling before the message is delivered without it */ readonly DOWNLOAD_TIMEOUT_MS: number; /** All downloads of one incoming message together */ readonly DOWNLOAD_TOTAL_TIMEOUT_MS: number; /** Text files up to this size are scanned for secrets before they leave the machine */ readonly SECRET_SCAN_MAX_BYTES: number; /** Inbox folder under the project data directory */ readonly INBOX_DIR: "inbox"; /** Fallback content type when none is known */ readonly DEFAULT_CONTENT_TYPE: "application/octet-stream"; /** File names (lower-cased) that never leave the machine */ readonly SECRET_NAME_PATTERNS: readonly RegExp[]; /** Directory names (lower-cased) a path must not pass through */ readonly SECRET_DIR_NAMES: readonly string[]; }; /** * Slack inbound backfill (CREW-89): after Cloud could not deliver Slack * events to this machine (queue registration failing, then recovered), read * the owner's messages straight from Slack and ingest the ones we never saw. */ export declare const SLACK_INBOUND_BACKFILL_CONSTANTS: { /** Start this far before the gap began, to catch events in flight (ms) */ readonly GAP_MARGIN_MS: number; /** Never look further back than this, however long the gap (ms) — 24 h */ readonly MAX_LOOKBACK_MS: number; /** Only channels where the owner wrote within this window are scanned (ms) — 14 d */ readonly CHANNEL_ACTIVITY_WINDOW_MS: number; /** Most channels scanned in one backfill */ readonly MAX_CHANNELS: 50; /** `conversations.history` page size */ readonly HISTORY_LIMIT: 200; /** Most threads whose replies are read per channel */ readonly MAX_THREADS_PER_CHANNEL: 20; /** Slack Web API base URL */ readonly API_BASE: "https://slack.com/api"; /** Per-request timeout (ms) */ readonly REQUEST_TIMEOUT_MS: 15000; }; /** * Browser tool order for every agent prompt. * * Crewly in Chrome (the `remote-browser` skill) drives the user's real Chrome, * so requests carry the user's own cookies and fingerprint and pass the bot * checks that stop headless and automation browsers. It always comes first; * Playwright / headless / stealth / CDP-attach browsers are fallbacks, and a * fallback that hits a wall goes back to Crewly in Chrome instead of fighting * the block. */ export declare const BROWSER_PRIORITY_CONSTANTS: { /** * Prompt section shared by the legacy init prompt and the modular * skills-reference module. * * @param remoteBrowserPath - Absolute path of the remote-browser `execute.sh` * @returns Markdown section */ readonly promptSection: (remoteBrowserPath: string) => string; }; /** * Ticket holds (crewly#1369 gap 1): a ticket the owner put on hold ("do not * merge PR #1138") blocks follow-up work on the same PR / branch / worktree. * Only closing the PR and removing the worktree stays allowed. */ export declare const TICKET_HOLD_CONSTANTS: { /** Labels that put a ticket on hold */ readonly LABELS: readonly string[]; /** Prefix of the Log line written when a hold is set */ readonly LOG_PREFIX: "HOLD:"; /** Log line written when a hold is lifted */ readonly LIFT_LOG_LINE: "hold lifted"; /** Prefix of the hold reason of a ticket held because it references a held ticket */ readonly LINKED_PREFIX: "held by"; /** What may still be done about held work */ readonly ALLOWED_ACTIONS: "close the PR and remove the worktree"; /** Max characters of a hold reason quoted in a message */ readonly REASON_MAX_CHARS: 240; }; /** * Task timing: per-agent records of how long completed tasks took, used to * ground ETAs in the agent's own history (task-timing.service.ts). */ export declare const TASK_TIMING_CONSTANTS: { /** File under ~/.crewly/agents/{session}/ */ readonly FILE_NAME: "task-timings.json"; /** Records kept per agent (oldest dropped first) */ readonly MAX_RECORDS: 500; /** A claim-to-completion span over this is waiting, not work: excluded from medians */ readonly WORK_CAP_MINUTES: number; /** Kinds (and the ETA bias) need at least this many records to be shown */ readonly MIN_SAMPLES: 2; /** ETA/actual gap past which complete-task invites a learning */ readonly GAP_RATIO: 1.5; /** ETA/actual ratio within which ETAs read as "about right" */ readonly BIAS_TOLERANCE: 1.25; /** Largest ETA accepted, in minutes (7 days) */ readonly ETA_MAX_MINUTES: number; /** Durations from this many minutes up are written in hours */ readonly HOURS_FROM_MINUTES: 90; /** Longest agent-supplied kind */ readonly KIND_MAX_CHARS: 32; /** Title characters stored per record */ readonly TITLE_MAX_CHARS: 120; /** WorkItem.metadata key holding the first ETA the agent stated */ readonly ETA_METADATA_KEY: "etaMinutes"; /** WorkItem.metadata key holding the agent-named task kind */ readonly KIND_METADATA_KEY: "taskKind"; }; /** * Activity log (crewly#881): the append-only record of who changed what and * when — team / member config edits, owner decisions, later budget stops and * outbound sends. `specs/2026-10-25-activity-log.md`. */ export declare const ACTIVITY_LOG_CONSTANTS: { /** Directory under CREWLY_HOME holding one `YYYY-MM.jsonl` file per month */ readonly DIR: "activity-log"; /** File extension of a monthly log file */ readonly FILE_EXT: ".jsonl"; /** Entries a read returns when the caller names no limit */ readonly DEFAULT_LIMIT: 100; /** Most entries one read returns */ readonly MAX_LIMIT: 1000; /** Most monthly files one read opens (newest first) */ readonly MAX_FILES_PER_READ: 24; /** Longest string kept in a before / after summary */ readonly MAX_SUMMARY_CHARS: 400; /** Keys whose values are never written (case-insensitive substring match) */ readonly SECRET_KEY_PATTERN: RegExp; /** What a masked value is written as */ readonly MASK: "***"; /** Query parameter names of GET /api/activity */ readonly QUERY: { readonly ACTOR_TYPE: "actorType"; readonly ACTOR_ID: "actorId"; readonly ACTION: "action"; readonly TARGET_TYPE: "targetType"; readonly TARGET_ID: "targetId"; readonly SINCE: "since"; readonly UNTIL: "until"; readonly LIMIT: "limit"; }; }; //# sourceMappingURL=constants.d.ts.map