Commit Graph
230 Commits
Author SHA1 Message Date
ZacharyZhang-NY 183f22e922 fix(onboarding): derive step counter from STEP_ORDER
All six step panels previously hard-coded "X / 5" or "X / 6" in their
header captions, which drifted out of sync with the 7-step STEP_ORDER in
OnboardingHero. Export STEP_ORDER / TOTAL_STEPS / getStepNumber from
OnboardingHero and consume them in every step so the counter is always
the single source of truth.
2026-04-16 22:03:57 -04:00
ZacharyZhang-NY 5a5587c2a1 refactor(automation): use Modal for RuleFormModal 2026-04-16 22:03:24 -04:00
ZacharyZhang-NY 8243f58ef7 refactor(automation): use Modal/ConfirmDialog for DeleteConfirmDialog 2026-04-16 22:02:29 -04:00
ZacharyZhang-NY d0c6d1f6d0 perf(db): resolve chain of command in a single recursive CTE to eliminate N+1 queries 2026-04-16 21:59:18 -04:00
ZacharyZhang-NY 2ec77677cb fix(renderer): classify DesktopEvent types to stop full-refresh fallthrough (D-05)
All 14+ non-specially-handled events were pushing into the batcher with
entityKeyFromEvent returning null, which set hasDomainChanged = true and
triggered a full snapshot refresh on every agent-message, run-status, and
heartbeat-status -- defeating the granular patch machinery.

Per-type classification:
- run-status         -> granular run-updated (patchRun by runId)
- heartbeat-status   -> granular agent-updated (patchAgent by agentId)
- agent-message      -> granular task-updated (patchTask refetches comments)
- new-message, thread-wait-state-updated, domain-changed
                     -> full refresh, coalesced via 500ms batcher window
- automation-fired, recovery-needed
                     -> snapshot-irrelevant, skipped at event source

Widen batcher window from 50ms to 500ms so bursts coalesce into one reload.
Granular batches still flush at the same window cadence.
2026-04-16 21:58:32 -04:00
ZacharyZhang-NY 8cf6be05bb fix(skills): enforce per-file and aggregate size caps for imported SKILL.md content 2026-04-16 21:58:32 -04:00
ZacharyZhang-NY c81d72fb9c fix(api-server): SSE stream pulls incremental events via seq cursor
Replace per-poll 5000-row scan + JS filter with indexed WHERE seq > ?
query (uses idx_run_events_run). Batch up to 500 per call, drain loop
on reconnect. Poll interval raised 500ms -> 1000ms.
2026-04-16 21:57:35 -04:00
ZacharyZhang-NY c5c3919c78 fix(skills): bound local skill walk with depth, file count, and symlink loop protection 2026-04-16 21:57:29 -04:00
ZacharyZhang-NY 0123103f58 fix(renderer): subscribe ConsoleView to logRevision to trigger re-render (D-04)
logRevision was bumped on every run-log event but no component subscribed,
so ref mutations to logBufferRef never triggered a re-render -- the console
pre stayed stale until something else forced a render.

Subscribe to logRevision via useAppStore and depend on it in the useMemo
that reads logBufferRef, keeping the ref-based buffer (cheap writes) while
restoring re-renders.
2026-04-16 21:57:08 -04:00
ZacharyZhang-NY 0119872db6 fix(renderer): use snapshot.currentCompanyId as single source of truth (D-03)
CompanySlice's standalone currentCompanyId field + setCurrentCompanyId action
were never written by any caller, so useDesktopEvents.ts read null and fell
through to full refresh for every entity event -- the granular patch
machinery was dead code.

- Remove currentCompanyId field + setCurrentCompanyId from CompanySlice
- refreshBadges reads from snapshot.currentCompanyId
- useDesktopEvents reads companyId from snapshot.currentCompanyId
- Update store unit test to seed snapshot instead of calling removed action
2026-04-16 21:56:40 -04:00
ZacharyZhang-NY a130dc29f5 fix(tasks): route updateTaskSession/getTaskSession through task_sessions table
Stop writing new session data back to the legacy tasks.session_json
column; updateTaskSession now upserts the active task_sessions row
(updating the session pointed to by tasks.active_session_id, creating
one when absent), and getTaskSession reads from the same row so
session data has a single source of truth. Adds migration 27 to
backfill pre-existing tasks.session_json values into task_sessions
idempotently via INSERT OR IGNORE guarded on task_id. The legacy
column is kept for read-only history and is not dropped.
2026-04-16 21:53:12 -04:00
ZacharyZhang-NY ccf0c2a500 fix(schema): remove duplicate-column fallback; make migrations idempotent
Stop silently swallowing duplicate-column errors in the migration
runner; any real migration failure now throws. Replace try/catch-based
column additions in ensureOptionalColumns and migrations 19-22 with
pragma table_info checks so repeated startup is idempotent without
tolerating error signals. Reorder initializeDatabase so runMigrations
runs before ensureOptionalColumns to avoid migration 13 adding columns
that ensureOptionalColumns already created on fresh databases.
2026-04-16 21:52:43 -04:00
ZacharyZhang-NY 5201b6623b fix(database): enable WAL mode and busy_timeout on every DB connection
Applies WAL journal, 5s busy_timeout, synchronous=NORMAL, and
foreign_keys=ON pragmas immediately after opening each connection so
concurrent main+worker writers share the same locking configuration
and avoid SQLITE_BUSY under contention.
2026-04-16 21:52:25 -04:00
ZacharyZhang-NY a6d0a567a8 fix(ipc-credentials): use shared registerHandle and precise redactSecrets (I-02)
Two tightly-coupled hardening changes to the credential IPC module.

(1) Drop the self-built `DesktopOk/DesktopErr/ok/err/handle` helpers
that called `ipcMain.handle` directly, bypassing the trusted-renderer
check in `ipc-support.registerHandle` and the `ZodError ->
IPC_INVALID_PAYLOAD` sanitizer. The module now takes `registerHandle`
and `fail` as deps (wired from `createIpcSupport`) and imports `ok`
from `ipc-core-common`, mirroring the OAuth handler layout.

(2) Tighten `redactSecrets` to issuer-prefixed patterns only (Bearer,
`sk-`, `sk-ant-`, `sk-proj-`, AKIA access key IDs, three-segment JWTs).
The previous blanket `[A-Fa-f0-9]{40,}` and `[A-Za-z0-9+/]{48,}={0,2}`
rules were shredding legitimate diagnostic text — git SHAs, UUIDs,
Fireworks model identifiers like `accounts/fireworks/models/...`, and
any long opaque ID — into `[REDACTED-HEX]` / `[REDACTED-B64]`, which
made provider errors unreadable in the renderer.
2026-04-16 21:49:53 -04:00
ZacharyZhang-NY ff0c01fe87 fix(worker): claim workspace lock in main before enqueue (D-02)
The lock was previously claimed when the worker reported `running`, which
is after `agent.prompt` had already started streaming. Two concurrent
runs targeting the same workspace could both pass the main-side checks,
both get enqueued, and both read half-written state before one of them
lost the lock race and was cancelled mid-flight.

Move the `claimWorkspaceLock` call into `dispatchRunToWorker` so main
coordinates exclusivity authoritatively. If the lock is unavailable,
the run is finished as failed and never enqueued. The worker-side
lease-renewal path is preserved since it is a lease, not a lock.
2026-04-16 21:49:30 -04:00
ZacharyZhang-NY f86b3ddbac fix(ipc-oauth): use shared registerHandle for trust and error sanitization (I-01)
The self-built `ok/err/handle` helpers called `ipcMain.handle` directly,
which meant every OAuth IPC channel skipped the trusted-renderer check
in `ipc-support.registerHandle` and its `ZodError -> IPC_INVALID_PAYLOAD`
sanitizer. Any renderer origin could invoke these channels, and thrown
zod errors would leak the raw validator message.

Take `registerHandle` and `fail` as dependencies so callers wire in the
hardened variants from `createIpcSupport`, drop the local helpers, and
import `ok` from `ipc-core-common`. Zod inputs stay on `safeParse` to
preserve the explicit `INVALID_INPUT` code + message path.
2026-04-16 21:48:47 -04:00
ZacharyZhang-NY a1f021ff4a fix(worker): invert connector bypass default and gate on packaged build (S-01)
Previously `AGENTCOMPANY_ALLOW_UNSAFE_CONNECTOR_BYPASS !== "false"` meant
packaged builds defaulted to passing `dangerouslyBypassApprovalsAndSandbox`,
`dangerouslySkipPermissions`, `yolo`, and `--sandbox danger-full-access`
to CLI connectors, bypassing ApprovalService entirely.

Require both explicit opt-in (`AGENTCOMPANY_ALLOW_UNSAFE_CONNECTOR_BYPASS=true`)
and a non-packaged build. Packaged builds hard-refuse regardless of the env
var. Main process forks the worker with `AGENTCOMPANY_PACKAGED=<app.isPackaged>`
so the worker has an authoritative packaged signal.
2026-04-16 21:48:10 -04:00
ZacharyZhang-NY f8052c3741 refactor(scheduler): split 15s tick into 4 independent intervals
Split the single 15s setInterval in scheduler.ts (6 serial jobs) into 4
independent schedulers so a slow job no longer stalls the others:

- scheduler-heartbeat.ts (15s): wake eligible agents, CEO bootstrap,
  schedule automation trigger
- scheduler-lock-recovery.ts (60s): stale task-lock recovery and stale
  workspace-lock release
- scheduler-idle-automation.ts (60s): idle-agent automation dispatch
- scheduler-housekeeping.ts (300s): monthly budget reset, expired budget
  incident auto-close, stale review sweep, expired wakeup purge

scheduler.ts is now a thin composer exposing startAllSchedulers /
stopAllSchedulers. Bootstrap and service-graph updated accordingly.
Business logic, error isolation, and atomic actions preserved.
2026-04-16 21:47:09 -04:00
ZacharyZhang-NY 2676400e0c fix(ipc-credentials): serialize testCredential env mutations (S-03)
Pi-ai's `streamSimple`/`SimpleStreamOptions` has no per-request env
escape hatch; its Azure/AWS/GCP ADC providers read `process.env`
directly. The previous implementation wrote `extraEnv` into
`process.env` globally and relied on a `finally` after
`stream.result()` to clean up, so two concurrent `testCredential`
calls would clobber each other's env, expose the wrong credential
to in-flight streams, and on error paths fail to restore
previously-set values.

Wrap the env-mutating branch in a module-level promise chain so at
most one `extraEnv` test runs at a time, snapshot the prior value
of every key we overwrite, and restore (or delete when unset) in
`finally`. Tests that resolve to `extraEnv: undefined` bypass the
mutex entirely.
2026-04-16 21:43:44 -04:00
ZacharyZhang-NY ff08ab3e35 fix(desktop-shell-window): stop blocking main process on untrusted-link dialog
showMessageBoxSync froze the main-process event loop while the user
decided whether to open an external link, stalling agent-event workers,
IPC, and the scheduler for the duration of the dialog. setWindowOpenHandler
still returns synchronously with action: "deny"; the confirmation dialog
runs in an async continuation that only reaches shell.openExternal when
the user approves.
2026-04-16 21:42:29 -04:00
ZacharyZhang-NY f803b0b826 fix(api-server): require Origin header when no bearer token
Origin-less requests previously skipped the allowlist entirely, meaning
any process that omitted the Origin header (trivial for a non-browser
attacker) could reach the auth layer. Browsers always send Origin, so
rejecting Origin-less browser-style requests closes the bypass without
breaking agent CLIs, which authenticate with a bearer token instead.

New policy:
 - Requests with a Bearer Authorization header: Origin optional; if
   present it must be in the allowlist.
 - All other requests (including CORS preflight): Origin required and
   must be in the allowlist.
2026-04-16 21:42:04 -04:00
ZacharyZhang-NY 61caeb5f37 test(database): align with vault insecure-fallback opt-in convention
Aligns tests/unit/database.test.ts with the existing convention in
oauth-login-service/credential-resolver/credential-store tests: set
AGENTCOMPANY_ALLOW_INSECURE_VAULT=true in beforeAll so the in-memory
database test can call saveSecret without a real OS keychain.

Follow-up to aa1afd0 which removed the NODE_ENV=test/VITEST auto-enable.
2026-04-16 21:39:37 -04:00
ZacharyZhang-NY deb980448c fix(ipc-thread-handlers): drop redundant try/catch wrappers and duplicate emit
Every handler was wrapping its body in try/catch and returning fail(code,
error.message), leaking raw DB messages to the renderer and bypassing the
sanitizer in registerHandle. Removed the 14 inner try/catch shells so
errors surface through ipc-support's IPC_HANDLER_FAILED / IPC_INVALID_PAYLOAD
path. Also removed the duplicate emitEvent({ type: 'domain-changed' }) in
addThreadEvent — publishDomainChanged already emits the same event.
2026-04-16 21:37:58 -04:00
ZacharyZhang-NY 6c6cc96060 fix(agent-permissions): propagate DB errors instead of silently denying
safeGetAgent was catching all errors and returning null, causing every
permission check (canControlAgent / canManageOrganization / canRequestHire)
to silently deny when the database threw. Only swallow the 'Agent not found'
case and let real DB errors propagate up to registerHandle's sanitizer.
2026-04-16 21:37:16 -04:00
ZacharyZhang-NY 893a0ddec1 chore(preload): drop stale @planned notes on delete{Task,Goal,Project,Workspace}
Handlers for deleteTask/deleteGoal/deleteProject live in
ipc-core-task-graph-handlers.ts:166-168 (backed by
database/tasks-crud.ts and database/entity-crud.ts), and
deleteWorkspace lives at line 169 (backed by database/workspaces.ts).
The four @planned comments were misleading.
2026-04-16 21:36:55 -04:00
ZacharyZhang-NY 49df7e528e fix(release): exit after printing --help in release-cloudflare wrapper 2026-04-16 21:36:46 -04:00
ZacharyZhang-NY 734c789f50 fix(orchestration): remove one-time heartbeat cleanup from scheduler startup 2026-04-16 21:36:43 -04:00
ZacharyZhang-NY 1a57622849 fix(automation): remove silent JSON.parse fallback in executeAutomationAction 2026-04-16 21:36:41 -04:00
ZacharyZhang-NY a612ea7bea fix(communication): set searchLoading=true in setSearchQuery
setSearchQuery only incremented searchRequestId without flipping
searchLoading, so the UI could not distinguish "searching" from "no
results". Now loading flips true on non-empty query and clears
(results + loading) on empty query. setSearchResults keeps the
existing id-match logic to flip loading false on arrival.
2026-04-16 21:36:37 -04:00
ZacharyZhang-NY aa1afd0db2 fix(vault): remove NODE_ENV=test and VITEST auto-enable of insecure fallback so only explicit opt-in unlocks it 2026-04-16 21:36:29 -04:00
ZacharyZhang-NY cfe5f9ff73 fix(oauth): scope failPending to matching connectorId so cancelling one OAuth does not reject others 2026-04-16 21:36:22 -04:00
ZacharyZhang-NY d8904c88e5 docs: map existing codebase 2026-04-16 15:07:32 -04:00
ZacharyZhang-NY 1b578c111a update 2026-04-16 14:48:51 -04:00
ZacharyZhang-NY 9779c4d1ca update 2026-04-07 20:18:47 -04:00
ZacharyZhang-NY f5d4f8b705 update 2026-04-06 00:35:27 -04:00
ZacharyZhang-NY 13166fd860 Merge branch 'main' of github.com:ZacharyZhang-NY/AgentCompany 2026-04-05 23:47:58 -04:00
ZacharyZhang-NY 72157f0703 update 2026-04-05 23:47:56 -04:00
ZacharyZhang-NY 69a1139eb3 Add remote Boss workflow used by automated release script 2026-04-05 21:23:18 -04:00
ZacharyZhang-NY 9145572422 update 2026-04-05 20:14:48 -04:00
ZacharyZhang-NY 09971cabc2 Merge branch 'main' of github.com:ZacharyZhang-NY/AgentCompany 2026-04-04 17:01:28 -04:00
ZacharyZhang-NY b8dda3c8d0 update 2026-04-04 17:01:26 -04:00
ZacharyZhang-NY 42bb2f196f Tighten Linux x64 upload globs in the remote release workflow
Constraint: Linux arm64 assets and x64 assets share the same draft release transport bucket
Rejected: Broad *.AppImage/*.deb/*.rpm globs | x64 upload step collided with arm64 assets and failed the workflow
Confidence: high
Scope-risk: narrow
Directive: Keep per-architecture upload globs disjoint whenever multiple jobs share one draft release
Tested: Prior run proved packaging succeeded and isolated the failure to the x64 upload selection
Not-tested: Updated workflow dispatch before rerun
2026-04-04 16:19:37 -04:00
ZacharyZhang-NY bc878c8f93 Add remote workflow used by automated Cloudflare release script 2026-04-04 15:47:50 -04:00
ZacharyZhang-NY 67c757f481 update 2026-04-04 12:03:50 -04:00
ZacharyZhang-NYandClaude Sonnet 4.6 66158e222e update
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-30 22:02:28 -04:00
ZacharyZhang-NY 16d8426119 update 2026-03-30 21:48:44 -04:00
ZacharyZhang-NY aed312124b update 2026-03-30 16:24:29 -04:00
ZacharyZhang-NY 400417a6af Update .gitignore 2026-03-30 10:50:25 -04:00
ZacharyZhang-NY 28740faf27 update 2026-03-30 10:45:53 -04:00
ZacharyZhang-NY 6f1de8047e update 2026-03-30 00:15:21 -04:00