indusagi-coding-agent 0.2.11 → 0.2.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.review.example +25 -0
- package/CHANGELOG.md +6 -0
- package/README.md +11 -0
- package/REVIEW_AGENT.md +137 -0
- package/dist/entry.js +103 -25
- package/dist/guardrails.js +67 -1
- package/dist/index.js +1034 -66
- package/dist/review-agent.js +4177 -0
- package/dist/review-server.js +4349 -0
- package/package.json +15 -10
- package/src/boot/contract.ts +1 -1
- package/src/boot/runners/session.ts +8 -4
- package/src/boot/stages.ts +3 -6
- package/src/conductor/catalog/catalog.ts +3 -1
- package/src/conductor/conductor.ts +10 -5
- package/src/conductor/contract.ts +8 -4
- package/src/conductor/index.ts +1 -1
- package/src/conductor/signal-hub/translate.ts +2 -2
- package/src/console/slash/commands/workbench.ts +2 -2
- package/src/index.ts +1 -0
- package/src/launch/catalog.ts +4 -2
- package/src/launch/contract.ts +1 -0
- package/src/launch/invocation/flags.ts +1 -1
- package/src/launch/invocation/read.ts +5 -4
- package/src/models.ts +90 -0
- package/src/review-agent/config.ts +75 -0
- package/src/review-agent/contract.ts +59 -0
- package/src/review-agent/github.ts +242 -0
- package/src/review-agent/index.ts +8 -0
- package/src/review-agent/prompt.ts +132 -0
- package/src/review-agent/reviewer.ts +118 -0
- package/src/review-agent/server.ts +221 -0
- package/src/review-agent/service.ts +165 -0
- package/src/review-agent/supabase.ts +209 -0
- package/src/review-agent/webhook.ts +76 -0
- package/src/workspace/brand.ts +1 -1
- package/supabase/migrations/20260827031803_indusagi_review_agent.sql +109 -0
- package/supabase/migrations/20260827035747_atomic_webhook_enqueue.sql +75 -0
- package/supabase/migrations/20260827040104_check_run_reconciliation.sql +8 -0
- package/supabase/migrations/20260827041049_owned_job_leases.sql +80 -0
- package/supabase/migrations/20260827042500_static_token_webhooks.sql +75 -0
- package/src/conductor/signal-hub/translate.test.ts +0 -81
- package/src/entry.test.ts +0 -17
- package/src/integrations/sarvam/sarvam.test.ts +0 -60
- package/src/integrations/zoho/zoho.test.ts +0 -50
- package/src/launch/oauth.test.ts +0 -18
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
create extension if not exists pgcrypto;
|
|
2
|
+
|
|
3
|
+
create table if not exists public.indusagi_review_deliveries (
|
|
4
|
+
delivery_id text primary key,
|
|
5
|
+
event_name text not null,
|
|
6
|
+
received_at timestamptz not null default now()
|
|
7
|
+
);
|
|
8
|
+
|
|
9
|
+
create table if not exists public.indusagi_review_jobs (
|
|
10
|
+
id uuid primary key default gen_random_uuid(),
|
|
11
|
+
delivery_id text references public.indusagi_review_deliveries(delivery_id) on delete set null,
|
|
12
|
+
installation_id bigint not null,
|
|
13
|
+
repository text not null,
|
|
14
|
+
owner text not null,
|
|
15
|
+
repo text not null,
|
|
16
|
+
pr_number integer not null check (pr_number > 0),
|
|
17
|
+
head_sha text not null,
|
|
18
|
+
title text not null,
|
|
19
|
+
status text not null default 'pending' check (status in ('pending', 'running', 'completed', 'failed')),
|
|
20
|
+
attempts integer not null default 0 check (attempts >= 0),
|
|
21
|
+
max_attempts integer not null default 3 check (max_attempts between 1 and 10),
|
|
22
|
+
available_at timestamptz not null default now(),
|
|
23
|
+
worker_id text,
|
|
24
|
+
claimed_at timestamptz,
|
|
25
|
+
completed_at timestamptz,
|
|
26
|
+
review jsonb,
|
|
27
|
+
github_review_id bigint,
|
|
28
|
+
github_comment_id bigint,
|
|
29
|
+
check_run_id bigint,
|
|
30
|
+
error text,
|
|
31
|
+
created_at timestamptz not null default now(),
|
|
32
|
+
updated_at timestamptz not null default now(),
|
|
33
|
+
unique (repository, pr_number, head_sha)
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
create index if not exists indusagi_review_jobs_claim_idx
|
|
37
|
+
on public.indusagi_review_jobs (status, available_at, created_at)
|
|
38
|
+
where status = 'pending';
|
|
39
|
+
create index if not exists indusagi_review_jobs_pr_idx
|
|
40
|
+
on public.indusagi_review_jobs (repository, pr_number, created_at desc);
|
|
41
|
+
|
|
42
|
+
alter table public.indusagi_review_deliveries enable row level security;
|
|
43
|
+
alter table public.indusagi_review_jobs enable row level security;
|
|
44
|
+
|
|
45
|
+
create or replace function public.claim_indusagi_review_job(p_worker_id text)
|
|
46
|
+
returns setof public.indusagi_review_jobs
|
|
47
|
+
language plpgsql
|
|
48
|
+
security definer
|
|
49
|
+
set search_path = public
|
|
50
|
+
as $$
|
|
51
|
+
declare
|
|
52
|
+
claimed public.indusagi_review_jobs;
|
|
53
|
+
begin
|
|
54
|
+
if nullif(trim(p_worker_id), '') is null then
|
|
55
|
+
raise exception 'worker id is required';
|
|
56
|
+
end if;
|
|
57
|
+
|
|
58
|
+
select * into claimed
|
|
59
|
+
from public.indusagi_review_jobs
|
|
60
|
+
where status = 'pending' and available_at <= now()
|
|
61
|
+
order by created_at
|
|
62
|
+
for update skip locked
|
|
63
|
+
limit 1;
|
|
64
|
+
|
|
65
|
+
if claimed.id is null then
|
|
66
|
+
return;
|
|
67
|
+
end if;
|
|
68
|
+
|
|
69
|
+
update public.indusagi_review_jobs
|
|
70
|
+
set status = 'running',
|
|
71
|
+
attempts = attempts + 1,
|
|
72
|
+
worker_id = p_worker_id,
|
|
73
|
+
claimed_at = now(),
|
|
74
|
+
updated_at = now()
|
|
75
|
+
where id = claimed.id
|
|
76
|
+
returning * into claimed;
|
|
77
|
+
|
|
78
|
+
return next claimed;
|
|
79
|
+
end;
|
|
80
|
+
$$;
|
|
81
|
+
|
|
82
|
+
create or replace function public.recover_stale_indusagi_review_jobs(p_stale_minutes integer default 15)
|
|
83
|
+
returns integer
|
|
84
|
+
language plpgsql
|
|
85
|
+
security definer
|
|
86
|
+
set search_path = public
|
|
87
|
+
as $$
|
|
88
|
+
declare
|
|
89
|
+
recovered integer;
|
|
90
|
+
begin
|
|
91
|
+
update public.indusagi_review_jobs
|
|
92
|
+
set status = case when attempts < max_attempts then 'pending' else 'failed' end,
|
|
93
|
+
available_at = now(),
|
|
94
|
+
worker_id = null,
|
|
95
|
+
claimed_at = null,
|
|
96
|
+
error = coalesce(error, 'worker lease expired'),
|
|
97
|
+
completed_at = case when attempts >= max_attempts then now() else completed_at end,
|
|
98
|
+
updated_at = now()
|
|
99
|
+
where status = 'running'
|
|
100
|
+
and claimed_at < now() - make_interval(mins => greatest(1, p_stale_minutes));
|
|
101
|
+
get diagnostics recovered = row_count;
|
|
102
|
+
return recovered;
|
|
103
|
+
end;
|
|
104
|
+
$$;
|
|
105
|
+
|
|
106
|
+
revoke all on function public.claim_indusagi_review_job(text) from public, anon, authenticated;
|
|
107
|
+
revoke all on function public.recover_stale_indusagi_review_jobs(integer) from public, anon, authenticated;
|
|
108
|
+
grant execute on function public.claim_indusagi_review_job(text) to service_role;
|
|
109
|
+
grant execute on function public.recover_stale_indusagi_review_jobs(integer) to service_role;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
create or replace function public.enqueue_indusagi_review_job(
|
|
2
|
+
p_delivery_id text,
|
|
3
|
+
p_event_name text,
|
|
4
|
+
p_installation_id bigint,
|
|
5
|
+
p_repository text,
|
|
6
|
+
p_owner text,
|
|
7
|
+
p_repo text,
|
|
8
|
+
p_pr_number integer,
|
|
9
|
+
p_head_sha text,
|
|
10
|
+
p_title text
|
|
11
|
+
)
|
|
12
|
+
returns table(job_id uuid, duplicate boolean)
|
|
13
|
+
language plpgsql
|
|
14
|
+
security definer
|
|
15
|
+
set search_path = public
|
|
16
|
+
as $$
|
|
17
|
+
declare
|
|
18
|
+
queued_id uuid;
|
|
19
|
+
inserted_delivery integer;
|
|
20
|
+
begin
|
|
21
|
+
if nullif(trim(p_delivery_id), '') is null
|
|
22
|
+
or nullif(trim(p_event_name), '') is null
|
|
23
|
+
or p_installation_id <= 0
|
|
24
|
+
or nullif(trim(p_repository), '') is null
|
|
25
|
+
or nullif(trim(p_owner), '') is null
|
|
26
|
+
or nullif(trim(p_repo), '') is null
|
|
27
|
+
or p_pr_number <= 0
|
|
28
|
+
or nullif(trim(p_head_sha), '') is null
|
|
29
|
+
or nullif(trim(p_title), '') is null then
|
|
30
|
+
raise exception 'invalid review webhook job';
|
|
31
|
+
end if;
|
|
32
|
+
|
|
33
|
+
insert into public.indusagi_review_deliveries(delivery_id, event_name)
|
|
34
|
+
values (p_delivery_id, p_event_name)
|
|
35
|
+
on conflict (delivery_id) do nothing;
|
|
36
|
+
get diagnostics inserted_delivery = row_count;
|
|
37
|
+
|
|
38
|
+
if inserted_delivery = 0 then
|
|
39
|
+
return query select null::uuid, true;
|
|
40
|
+
return;
|
|
41
|
+
end if;
|
|
42
|
+
|
|
43
|
+
insert into public.indusagi_review_jobs(
|
|
44
|
+
delivery_id,
|
|
45
|
+
installation_id,
|
|
46
|
+
repository,
|
|
47
|
+
owner,
|
|
48
|
+
repo,
|
|
49
|
+
pr_number,
|
|
50
|
+
head_sha,
|
|
51
|
+
title
|
|
52
|
+
) values (
|
|
53
|
+
p_delivery_id,
|
|
54
|
+
p_installation_id,
|
|
55
|
+
p_repository,
|
|
56
|
+
p_owner,
|
|
57
|
+
p_repo,
|
|
58
|
+
p_pr_number,
|
|
59
|
+
p_head_sha,
|
|
60
|
+
p_title
|
|
61
|
+
)
|
|
62
|
+
on conflict (repository, pr_number, head_sha) do update
|
|
63
|
+
set installation_id = excluded.installation_id,
|
|
64
|
+
title = excluded.title,
|
|
65
|
+
updated_at = now()
|
|
66
|
+
returning id into queued_id;
|
|
67
|
+
|
|
68
|
+
return query select queued_id, false;
|
|
69
|
+
end;
|
|
70
|
+
$$;
|
|
71
|
+
|
|
72
|
+
revoke all on function public.enqueue_indusagi_review_job(text, text, bigint, text, text, text, integer, text, text)
|
|
73
|
+
from public, anon, authenticated;
|
|
74
|
+
grant execute on function public.enqueue_indusagi_review_job(text, text, bigint, text, text, text, integer, text, text)
|
|
75
|
+
to service_role;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
alter table public.indusagi_review_jobs
|
|
2
|
+
add column if not exists check_completed_at timestamptz;
|
|
3
|
+
|
|
4
|
+
create index if not exists indusagi_review_jobs_check_reconcile_idx
|
|
5
|
+
on public.indusagi_review_jobs (completed_at)
|
|
6
|
+
where status = 'completed'
|
|
7
|
+
and check_run_id is not null
|
|
8
|
+
and check_completed_at is null;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
alter table public.indusagi_review_jobs
|
|
2
|
+
add column if not exists lease_token uuid,
|
|
3
|
+
add column if not exists lease_expires_at timestamptz;
|
|
4
|
+
|
|
5
|
+
create index if not exists indusagi_review_jobs_lease_expiry_idx
|
|
6
|
+
on public.indusagi_review_jobs (lease_expires_at)
|
|
7
|
+
where status = 'running';
|
|
8
|
+
|
|
9
|
+
create or replace function public.claim_indusagi_review_job(p_worker_id text)
|
|
10
|
+
returns setof public.indusagi_review_jobs
|
|
11
|
+
language plpgsql
|
|
12
|
+
security definer
|
|
13
|
+
set search_path = public
|
|
14
|
+
as $$
|
|
15
|
+
declare
|
|
16
|
+
claimed public.indusagi_review_jobs;
|
|
17
|
+
begin
|
|
18
|
+
if nullif(trim(p_worker_id), '') is null then
|
|
19
|
+
raise exception 'worker id is required';
|
|
20
|
+
end if;
|
|
21
|
+
|
|
22
|
+
select * into claimed
|
|
23
|
+
from public.indusagi_review_jobs
|
|
24
|
+
where status = 'pending' and available_at <= now()
|
|
25
|
+
order by created_at
|
|
26
|
+
for update skip locked
|
|
27
|
+
limit 1;
|
|
28
|
+
|
|
29
|
+
if claimed.id is null then
|
|
30
|
+
return;
|
|
31
|
+
end if;
|
|
32
|
+
|
|
33
|
+
update public.indusagi_review_jobs
|
|
34
|
+
set status = 'running',
|
|
35
|
+
attempts = attempts + 1,
|
|
36
|
+
worker_id = p_worker_id,
|
|
37
|
+
lease_token = gen_random_uuid(),
|
|
38
|
+
claimed_at = now(),
|
|
39
|
+
lease_expires_at = now() + interval '5 minutes',
|
|
40
|
+
updated_at = now()
|
|
41
|
+
where id = claimed.id
|
|
42
|
+
returning * into claimed;
|
|
43
|
+
|
|
44
|
+
return next claimed;
|
|
45
|
+
end;
|
|
46
|
+
$$;
|
|
47
|
+
|
|
48
|
+
create or replace function public.recover_stale_indusagi_review_jobs(p_stale_minutes integer default 15)
|
|
49
|
+
returns integer
|
|
50
|
+
language plpgsql
|
|
51
|
+
security definer
|
|
52
|
+
set search_path = public
|
|
53
|
+
as $$
|
|
54
|
+
declare
|
|
55
|
+
recovered integer;
|
|
56
|
+
begin
|
|
57
|
+
update public.indusagi_review_jobs
|
|
58
|
+
set status = case when attempts < max_attempts then 'pending' else 'failed' end,
|
|
59
|
+
available_at = now(),
|
|
60
|
+
worker_id = null,
|
|
61
|
+
lease_token = null,
|
|
62
|
+
claimed_at = null,
|
|
63
|
+
lease_expires_at = null,
|
|
64
|
+
error = coalesce(error, 'worker lease expired'),
|
|
65
|
+
completed_at = case when attempts >= max_attempts then now() else completed_at end,
|
|
66
|
+
updated_at = now()
|
|
67
|
+
where status = 'running'
|
|
68
|
+
and coalesce(
|
|
69
|
+
lease_expires_at,
|
|
70
|
+
claimed_at + make_interval(mins => greatest(1, p_stale_minutes))
|
|
71
|
+
) < now();
|
|
72
|
+
get diagnostics recovered = row_count;
|
|
73
|
+
return recovered;
|
|
74
|
+
end;
|
|
75
|
+
$$;
|
|
76
|
+
|
|
77
|
+
revoke all on function public.claim_indusagi_review_job(text) from public, anon, authenticated;
|
|
78
|
+
revoke all on function public.recover_stale_indusagi_review_jobs(integer) from public, anon, authenticated;
|
|
79
|
+
grant execute on function public.claim_indusagi_review_job(text) to service_role;
|
|
80
|
+
grant execute on function public.recover_stale_indusagi_review_jobs(integer) to service_role;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
create or replace function public.enqueue_indusagi_review_job(
|
|
2
|
+
p_delivery_id text,
|
|
3
|
+
p_event_name text,
|
|
4
|
+
p_installation_id bigint,
|
|
5
|
+
p_repository text,
|
|
6
|
+
p_owner text,
|
|
7
|
+
p_repo text,
|
|
8
|
+
p_pr_number integer,
|
|
9
|
+
p_head_sha text,
|
|
10
|
+
p_title text
|
|
11
|
+
)
|
|
12
|
+
returns table(job_id uuid, duplicate boolean)
|
|
13
|
+
language plpgsql
|
|
14
|
+
security definer
|
|
15
|
+
set search_path = public
|
|
16
|
+
as $$
|
|
17
|
+
declare
|
|
18
|
+
queued_id uuid;
|
|
19
|
+
inserted_delivery integer;
|
|
20
|
+
begin
|
|
21
|
+
if nullif(trim(p_delivery_id), '') is null
|
|
22
|
+
or nullif(trim(p_event_name), '') is null
|
|
23
|
+
or p_installation_id < 0
|
|
24
|
+
or nullif(trim(p_repository), '') is null
|
|
25
|
+
or nullif(trim(p_owner), '') is null
|
|
26
|
+
or nullif(trim(p_repo), '') is null
|
|
27
|
+
or p_pr_number <= 0
|
|
28
|
+
or nullif(trim(p_head_sha), '') is null
|
|
29
|
+
or nullif(trim(p_title), '') is null then
|
|
30
|
+
raise exception 'invalid review webhook job';
|
|
31
|
+
end if;
|
|
32
|
+
|
|
33
|
+
insert into public.indusagi_review_deliveries(delivery_id, event_name)
|
|
34
|
+
values (p_delivery_id, p_event_name)
|
|
35
|
+
on conflict (delivery_id) do nothing;
|
|
36
|
+
get diagnostics inserted_delivery = row_count;
|
|
37
|
+
|
|
38
|
+
if inserted_delivery = 0 then
|
|
39
|
+
return query select null::uuid, true;
|
|
40
|
+
return;
|
|
41
|
+
end if;
|
|
42
|
+
|
|
43
|
+
insert into public.indusagi_review_jobs(
|
|
44
|
+
delivery_id,
|
|
45
|
+
installation_id,
|
|
46
|
+
repository,
|
|
47
|
+
owner,
|
|
48
|
+
repo,
|
|
49
|
+
pr_number,
|
|
50
|
+
head_sha,
|
|
51
|
+
title
|
|
52
|
+
) values (
|
|
53
|
+
p_delivery_id,
|
|
54
|
+
p_installation_id,
|
|
55
|
+
p_repository,
|
|
56
|
+
p_owner,
|
|
57
|
+
p_repo,
|
|
58
|
+
p_pr_number,
|
|
59
|
+
p_head_sha,
|
|
60
|
+
p_title
|
|
61
|
+
)
|
|
62
|
+
on conflict (repository, pr_number, head_sha) do update
|
|
63
|
+
set installation_id = excluded.installation_id,
|
|
64
|
+
title = excluded.title,
|
|
65
|
+
updated_at = now()
|
|
66
|
+
returning id into queued_id;
|
|
67
|
+
|
|
68
|
+
return query select queued_id, false;
|
|
69
|
+
end;
|
|
70
|
+
$$;
|
|
71
|
+
|
|
72
|
+
revoke all on function public.enqueue_indusagi_review_job(text, text, bigint, text, text, text, integer, text, text)
|
|
73
|
+
from public, anon, authenticated;
|
|
74
|
+
grant execute on function public.enqueue_indusagi_review_job(text, text, bigint, text, text, text, integer, text, text)
|
|
75
|
+
to service_role;
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "vitest";
|
|
2
|
-
import { translateAgentEvent } from "./translate.js";
|
|
3
|
-
|
|
4
|
-
type InputEvent = Parameters<typeof translateAgentEvent>[0];
|
|
5
|
-
|
|
6
|
-
describe("translateAgentEvent tool signals", () => {
|
|
7
|
-
it("includes tool arguments in tool_start", () => {
|
|
8
|
-
const event: InputEvent = {
|
|
9
|
-
type: "tool_execution_start",
|
|
10
|
-
toolCallId: "call-1",
|
|
11
|
-
toolName: "read",
|
|
12
|
-
args: { path: "README.md" },
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
expect(translateAgentEvent(event)).toEqual([{
|
|
16
|
-
kind: "tool_start",
|
|
17
|
-
id: "call-1",
|
|
18
|
-
name: "read",
|
|
19
|
-
args: { path: "README.md" },
|
|
20
|
-
}]);
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
it("includes tool results and error status in tool_end", () => {
|
|
24
|
-
const event: InputEvent = {
|
|
25
|
-
type: "tool_execution_end",
|
|
26
|
-
toolCallId: "call-2",
|
|
27
|
-
toolName: "write",
|
|
28
|
-
result: { content: "permission denied" },
|
|
29
|
-
isError: true,
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
expect(translateAgentEvent(event)).toEqual([{
|
|
33
|
-
kind: "tool_end",
|
|
34
|
-
id: "call-2",
|
|
35
|
-
ok: false,
|
|
36
|
-
result: { content: "permission denied" },
|
|
37
|
-
isError: true,
|
|
38
|
-
}]);
|
|
39
|
-
});
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
describe("translateAgentEvent preserved signals", () => {
|
|
43
|
-
it("preserves prompt extraction", () => {
|
|
44
|
-
const event = {
|
|
45
|
-
type: "message_end",
|
|
46
|
-
message: {
|
|
47
|
-
role: "user",
|
|
48
|
-
content: [{ type: "text", text: "hello " }, { type: "text", text: "world" }],
|
|
49
|
-
timestamp: 1,
|
|
50
|
-
},
|
|
51
|
-
} as unknown as InputEvent;
|
|
52
|
-
|
|
53
|
-
expect(translateAgentEvent(event)).toEqual([{ kind: "prompt", text: "hello world" }]);
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
it("preserves text and thinking deltas", () => {
|
|
57
|
-
const message = { role: "assistant", content: [], timestamp: 1 };
|
|
58
|
-
|
|
59
|
-
expect(translateAgentEvent({
|
|
60
|
-
type: "message_update",
|
|
61
|
-
message,
|
|
62
|
-
assistantMessageEvent: { type: "text_delta", delta: "answer" },
|
|
63
|
-
} as unknown as InputEvent)).toEqual([{ kind: "text", delta: "answer" }]);
|
|
64
|
-
expect(translateAgentEvent({
|
|
65
|
-
type: "message_update",
|
|
66
|
-
message,
|
|
67
|
-
assistantMessageEvent: { type: "thinking_delta", delta: "reason" },
|
|
68
|
-
} as unknown as InputEvent)).toEqual([{ kind: "thinking", delta: "reason" }]);
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
it("preserves turn_end usage", () => {
|
|
72
|
-
const usage = { input: 1, output: 2, cacheRead: 3, cacheWrite: 4, totalTokens: 10, cost: { total: 0.5 } };
|
|
73
|
-
const event = {
|
|
74
|
-
type: "turn_end",
|
|
75
|
-
message: { role: "assistant", content: [], usage, timestamp: 1 },
|
|
76
|
-
toolResults: [],
|
|
77
|
-
} as unknown as InputEvent;
|
|
78
|
-
|
|
79
|
-
expect(translateAgentEvent(event)).toEqual([{ kind: "turn_end", usage }]);
|
|
80
|
-
});
|
|
81
|
-
});
|
package/src/entry.test.ts
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
2
|
-
import { resolveBoot } from "./entry.js";
|
|
3
|
-
|
|
4
|
-
describe("resolveBoot", () => {
|
|
5
|
-
afterEach(() => vi.restoreAllMocks());
|
|
6
|
-
|
|
7
|
-
it("prints fatal boot errors and returns a failing exit code", async () => {
|
|
8
|
-
const stderr = vi.spyOn(process.stderr, "write").mockReturnValue(true);
|
|
9
|
-
|
|
10
|
-
await expect(resolveBoot(Promise.reject(new Error("startup failed")))).resolves.toBe(1);
|
|
11
|
-
expect(stderr).toHaveBeenCalledWith("indusagi: startup failed\n");
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
it("preserves successful exit codes", async () => {
|
|
15
|
-
await expect(resolveBoot(Promise.resolve(0))).resolves.toBe(0);
|
|
16
|
-
});
|
|
17
|
-
});
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest";
|
|
2
|
-
import { resolveSarvamConfig, redactApiKey } from "./config.js";
|
|
3
|
-
|
|
4
|
-
describe("resolveSarvamConfig", () => {
|
|
5
|
-
const isolatedHome = "/tmp/indusagi-sarvam-test-home-empty";
|
|
6
|
-
|
|
7
|
-
it("returns undefined when nothing is configured", () => {
|
|
8
|
-
expect(
|
|
9
|
-
resolveSarvamConfig({ env: {}, cwd: "/tmp", home: isolatedHome, disabled: false }),
|
|
10
|
-
).toBeUndefined();
|
|
11
|
-
});
|
|
12
|
-
|
|
13
|
-
it("prefers explicit key over env", () => {
|
|
14
|
-
const cfg = resolveSarvamConfig({
|
|
15
|
-
apiKey: "key-from-arg",
|
|
16
|
-
env: { SARVAM_API_KEY: "key-from-env" },
|
|
17
|
-
cwd: "/tmp",
|
|
18
|
-
home: isolatedHome,
|
|
19
|
-
});
|
|
20
|
-
expect(cfg?.apiKey).toBe("key-from-arg");
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
it("honors SARVAM_API_KEY", () => {
|
|
24
|
-
const cfg = resolveSarvamConfig({
|
|
25
|
-
env: { SARVAM_API_KEY: "sk-test-123" },
|
|
26
|
-
cwd: "/tmp",
|
|
27
|
-
home: isolatedHome,
|
|
28
|
-
});
|
|
29
|
-
expect(cfg?.apiKey).toBe("sk-test-123");
|
|
30
|
-
expect(cfg?.id).toBe("sarvam");
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
it("disabled wins", () => {
|
|
34
|
-
const cfg = resolveSarvamConfig({
|
|
35
|
-
apiKey: "sk-test",
|
|
36
|
-
disabled: true,
|
|
37
|
-
cwd: "/tmp",
|
|
38
|
-
home: isolatedHome,
|
|
39
|
-
});
|
|
40
|
-
expect(cfg).toBeUndefined();
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
it("honors basePath from options", () => {
|
|
44
|
-
const cfg = resolveSarvamConfig({
|
|
45
|
-
apiKey: "sk-test",
|
|
46
|
-
basePath: "/tmp/sarvam-out",
|
|
47
|
-
cwd: "/tmp",
|
|
48
|
-
home: isolatedHome,
|
|
49
|
-
});
|
|
50
|
-
expect(cfg?.basePath).toBe("/tmp/sarvam-out");
|
|
51
|
-
});
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
describe("redactApiKey", () => {
|
|
55
|
-
it("masks middle", () => {
|
|
56
|
-
const r = redactApiKey("abcdefghijklmnop");
|
|
57
|
-
expect(r).toContain("…");
|
|
58
|
-
expect(r).not.toBe("abcdefghijklmnop");
|
|
59
|
-
});
|
|
60
|
-
});
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest";
|
|
2
|
-
import { resolveZohoConfig, redactUrl } from "./config.js";
|
|
3
|
-
|
|
4
|
-
describe("resolveZohoConfig", () => {
|
|
5
|
-
const isolatedHome = "/tmp/indusagi-zoho-test-home-empty";
|
|
6
|
-
|
|
7
|
-
it("returns undefined when nothing is configured", () => {
|
|
8
|
-
expect(
|
|
9
|
-
resolveZohoConfig({ env: {}, cwd: "/tmp", home: isolatedHome, disabled: false }),
|
|
10
|
-
).toBeUndefined();
|
|
11
|
-
});
|
|
12
|
-
|
|
13
|
-
it("prefers explicit url over env", () => {
|
|
14
|
-
const cfg = resolveZohoConfig({
|
|
15
|
-
url: "https://mcp.example/a",
|
|
16
|
-
env: { ZOHO_MCP_URL: "https://mcp.example/b" },
|
|
17
|
-
cwd: "/tmp",
|
|
18
|
-
home: isolatedHome,
|
|
19
|
-
});
|
|
20
|
-
expect(cfg?.url).toBe("https://mcp.example/a");
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
it("honors ZOHO_MCP_URL", () => {
|
|
24
|
-
const cfg = resolveZohoConfig({
|
|
25
|
-
env: { ZOHO_MCP_URL: "https://mcp.zoho.example/x" },
|
|
26
|
-
cwd: "/tmp",
|
|
27
|
-
home: isolatedHome,
|
|
28
|
-
});
|
|
29
|
-
expect(cfg?.url).toBe("https://mcp.zoho.example/x");
|
|
30
|
-
expect(cfg?.id).toBe("zoho");
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
it("disabled wins", () => {
|
|
34
|
-
const cfg = resolveZohoConfig({
|
|
35
|
-
url: "https://mcp.example/a",
|
|
36
|
-
disabled: true,
|
|
37
|
-
cwd: "/tmp",
|
|
38
|
-
home: isolatedHome,
|
|
39
|
-
});
|
|
40
|
-
expect(cfg).toBeUndefined();
|
|
41
|
-
});
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
describe("redactUrl", () => {
|
|
45
|
-
it("keeps host", () => {
|
|
46
|
-
const r = redactUrl("https://mcp.zoho.in/secret/path/more");
|
|
47
|
-
expect(r).toContain("mcp.zoho.in");
|
|
48
|
-
expect(r).toContain("…");
|
|
49
|
-
});
|
|
50
|
-
});
|
package/src/launch/oauth.test.ts
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
2
|
-
import { hasRegisteredOAuthClientId, launchAttempts } from "./oauth.js";
|
|
3
|
-
|
|
4
|
-
afterEach(() => vi.unstubAllEnvs());
|
|
5
|
-
|
|
6
|
-
describe("OpenAI Codex OAuth registration", () => {
|
|
7
|
-
it("uses the framework client when no environment override exists", () => {
|
|
8
|
-
vi.stubEnv("INDUSAGI_OPENAI_CODEX_CLIENT_ID", "");
|
|
9
|
-
expect(hasRegisteredOAuthClientId("openai-codex")).toBe(true);
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
it("opens ampersand-heavy OAuth URLs on Windows without cmd parsing", () => {
|
|
13
|
-
const url = "https://example.com/oauth?response_type=code&client_id=client&scope=openid";
|
|
14
|
-
expect(launchAttempts(url, "win32")).toEqual([
|
|
15
|
-
{ command: "rundll32.exe", args: ["url.dll,FileProtocolHandler", url] },
|
|
16
|
-
]);
|
|
17
|
-
});
|
|
18
|
-
});
|