delqhi commited on
Commit
a4dc18c
·
verified ·
1 Parent(s): fc6702d

Bootstrap SIN-GitHub-Issues runtime

Browse files
.github/workflows/ci.yml ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: Agent CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ workflow_dispatch:
8
+
9
+ jobs:
10
+ build-and-verify:
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - name: Checkout
14
+ uses: actions/checkout@v4
15
+
16
+ - name: Setup Node
17
+ uses: actions/setup-node@v4
18
+ with:
19
+ node-version: '22'
20
+ cache: npm
21
+
22
+ - name: Install dependencies
23
+ run: npm install
24
+
25
+ - name: Build
26
+ run: npm run build
27
+
28
+ - name: Print card
29
+ run: node dist/src/cli.js print-card
30
+
31
+ - name: Agent help
32
+ run: node dist/src/cli.js run-action '{"action":"agent.help"}'
33
+
34
+ - name: Agent health
35
+ run: node dist/src/cli.js run-action '{"action":"sin.github.issues.health"}'
.github/workflows/deploy-huggingface-space.yml ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: Deploy Hugging Face Space
2
+
3
+ on:
4
+ workflow_dispatch:
5
+ push:
6
+ branches: [main]
7
+
8
+ jobs:
9
+ deploy:
10
+ if: ${{ secrets.HF_TOKEN != '' }}
11
+ runs-on: ubuntu-latest
12
+ env:
13
+ HF_TOKEN: ${{ secrets.HF_TOKEN }}
14
+ HF_SPACE_REPO_ID: delqhi/sin-github-issues
15
+ steps:
16
+ - name: Checkout
17
+ uses: actions/checkout@v4
18
+
19
+ - name: Setup Python
20
+ uses: actions/setup-python@v5
21
+ with:
22
+ python-version: '3.11'
23
+
24
+ - name: Install deployment dependencies
25
+ run: pip install huggingface_hub
26
+
27
+ - name: Create or ensure Hugging Face Space
28
+ run: |
29
+ python - <<'PY'
30
+ import os
31
+ from huggingface_hub import HfApi
32
+
33
+ repo_id = os.environ["HF_SPACE_REPO_ID"].strip()
34
+ if not repo_id:
35
+ raise SystemExit("HF_SPACE_REPO_ID missing")
36
+
37
+ api = HfApi(token=os.environ["HF_TOKEN"])
38
+ api.create_repo(repo_id=repo_id, repo_type="space", space_sdk="docker", exist_ok=True)
39
+ PY
40
+
41
+ - name: Push repository contents to Hugging Face Space
42
+ run: |
43
+ set -euo pipefail
44
+ git config --global user.name "GitHub Actions"
45
+ git config --global user.email "actions@github.com"
46
+ git clone "https://oauth2:${HF_TOKEN}@huggingface.co/spaces/delqhi/sin-github-issues" hf-space
47
+ rsync -a --delete --exclude ".git/" --exclude ".github/" --exclude "node_modules/" --exclude "dist/" ./ hf-space/
48
+ cd hf-space
49
+ git add .
50
+ git commit -m "Deploy SIN-GitHub-Issues from GitHub Actions" || true
51
+ git push origin main
.gitignore ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ dist
2
+ node_modules
3
+ *.log
4
+ .env
5
+ .env.local
6
+ .DS_Store
7
+
.well-known/oauth-client.json ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "client_id": "https://delqhi-sin-github-issues.hf.space/.well-known/oauth-client.json",
3
+ "client_name": "SIN-GitHub-Issues",
4
+ "redirect_uris": ["https://delqhi-sin-github-issues.hf.space/oauth/callback"],
5
+ "grant_types": ["authorization_code"],
6
+ "response_types": ["code"],
7
+ "token_endpoint_auth_method": "none"
8
+ }
9
+
A2A-CARD.md ADDED
@@ -0,0 +1,128 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # A2A Card: SIN-GitHub-Issues
2
+
3
+ ## Identitaet
4
+
5
+ - `A2A Bezeichnung`: `SIN-GitHub-Issues`
6
+ - `Name`: `SIN-GitHub-Issues`
7
+ - `Beschreibung (Zweck)`: GitHub issue execution agent for confirmed bugs, repo issue lifecycle, branch-based fixes, and verification handoff.
8
+ - `Verwendung`: Use after enterprise-deep-debug confirms a bug to search/create/update GitHub issues, open a repo branch, execute the fix workflow, commit, push, and report verification status back to the orchestrating CLI agent.
9
+ - `CIMD`: `https://delqhi-sin-github-issues.hf.space`
10
+
11
+ ## Team & Infrastruktur
12
+
13
+ - `A2A Team`: `Team - Coding`
14
+ - `Team-Manager A2A`: `SIN-Coding-CEO`
15
+ - `VM / Server`: `Target: Hugging Face Space free CPU VM`
16
+ - `Lokales Verzeichnis`: `/Users/jeremy/dev/SIN-Solver/a2a/team-coding/A2A-SIN-GitHub-Issues`
17
+ - `Hugging Face Space URL`: `https://huggingface.co/spaces/delqhi/sin-github-issues`
18
+ - `Landingpage URL`: `https://delqhi-sin-github-issues.hf.space`
19
+ - `Cloudflare Tunnel`: `sin-github-issues`
20
+ - `Workforce Ziel-Index`: `https://a2a.delqhi.com/`
21
+ - `Google Docs Tab`: `https://docs.google.com/document/d/1RtoHn4I0GntuEEOHHkqoh_dMuGzgMwQz7_8oxAOpQbw/edit?tab=t.tuv2t449f8ze`
22
+
23
+ ## Auth- und Secrets-Modell
24
+
25
+ - `Primäre lokale GitHub-Lane`: `gh` CLI auf dem Operator-Host
26
+ - `Ziel-Lane`: GitHub Session-Grants via `SIN-Authenticator` (aktuell blockiert)
27
+ - `Browser-Profil`: `zukunftsorientierte.energie@gmail.com` / Chrome Profil `Default`
28
+ - `HF Session Backup`: `HF_DATASET_REPO` + `HF_TOKEN`
29
+ - `SIN-Supabase`: `SIN_SUPABASE_URL` + `SIN_SUPABASE_SERVICE_ROLE_KEY`
30
+ - `Aktueller Blocker`: GitHub-Provider in `SIN-Authenticator` noch nicht exponiert, SIN-Supabase/HF Runtime noch nicht live gekoppelt
31
+
32
+ ## MCP Server
33
+
34
+ - `MCP Server`: `sin-github-issues`
35
+ - `Transport`: `stdio`
36
+ - `Config`: `./mcp-config.json`
37
+
38
+ ## CDP (Chrome DevTools Protocol)
39
+
40
+ - `Lokale Browser-Lane`: Chrome Profil `Default` fuer `zukunftsorientierte.energie@gmail.com`
41
+ - `Verwendung`: nur fuer browserpflichtige GitHub-Schritte; normale Issue-/Git-Lane bleibt CLI-first
42
+
43
+ ## Tools
44
+
45
+ - `sin.github.issues.health`
46
+ - `sin.github.issues.onboarding.status`
47
+ - `sin.github.issues.onboarding.save`
48
+ - `sin.github.issues.auth.status`
49
+ - `sin.github.issues.issue.ensure`
50
+ - `sin.github.issues.issue.comment`
51
+ - `sin.github.issues.issue.close`
52
+ - `sin.github.issues.repo.status`
53
+ - `sin.github.issues.repo.branch.start`
54
+ - `sin.github.issues.repo.commit`
55
+ - `sin.github.issues.repo.push`
56
+ - `sin.github.issues.workflow.finalize_fix`
57
+
58
+ ## Aufgaben
59
+
60
+ - Runtime bereitstellen
61
+ - MCP bereitstellen
62
+ - A2A JSON-RPC bereitstellen
63
+ - bestaetigte Bugs auf GitHub-Issues abbilden und pflegen
64
+ - Branch-/Commit-/Push-Handoff fuer Fixes koordinieren
65
+ - RCA/Fix/Verifikation zurueck ins Issue schreiben
66
+ - Team-Manager- und Docs-Compliance einhalten
67
+
68
+ ## Commandos
69
+
70
+ ```bash
71
+ sin-github-issues serve-a2a
72
+ sin-github-issues serve-mcp
73
+ sin-github-issues print-card
74
+ sin-github-issues run-action '{"action":"agent.help"}'
75
+ sin-github-issues run-action '{"action":"sin.github.issues.auth.status"}'
76
+ sin-github-issues run-action '{"action":"sin.github.issues.issue.ensure","repo":"owner/name","title":"Bug title","body":"..."}'
77
+ sin-github-issues run-action '{"action":"sin.github.issues.repo.branch.start","repoPath":"/abs/repo","branchName":"fix/example"}'
78
+ ```
79
+
80
+ ## Endpoints
81
+
82
+ - `Health`: `/health`
83
+ - `Root`: `/`
84
+ - `Agent Card`: `/.well-known/agent-card.json`
85
+ - `Legacy Agent Card Alias`: `/.well-known/agent.json`
86
+ - `A2A JSON-RPC`: `/a2a/v1`
87
+
88
+ ## Skills
89
+
90
+ - `Pflicht`: Health, onboarding, auth status, issue lifecycle, repo branch/commit/push, finalize-fix workflow
91
+
92
+ ## Dateien
93
+
94
+ - `README.md`
95
+ - `A2A-CARD.md`
96
+ - `AGENTS.md`
97
+ - `agent.json`
98
+ - `mcp-config.json`
99
+ - `package.json`
100
+ - `tsconfig.json`
101
+ - `scripts/postbuild.mjs`
102
+ - `src/runtime.ts`
103
+ - `src/mcp-server.ts`
104
+ - `src/a2a-http.ts`
105
+ - `src/metadata.ts`
106
+ - `src/cli.ts`
107
+ - `src/index.ts`
108
+
109
+ ## Apps (auf welche Apps hat A2A Zugriff)
110
+
111
+ - `GitHub Issues / Pull Requests`
112
+ - `lokale Git-Repositories`
113
+ - `SIN-Supabase`
114
+ - `Hugging Face Dataset backup lane`
115
+ - `OpenCode CLI handoff workflow`
116
+
117
+ ## Plattformen (auf welche Plattformen hat A2A Zugriff)
118
+
119
+ - `GitHub`
120
+ - `Hugging Face Spaces`
121
+ - `SIN-Authenticator` (target auth broker)
122
+ - `SIN-Supabase`
123
+ - `A2A JSON-RPC`
124
+ - `MCP stdio`
125
+
126
+ ## Primärmodell
127
+
128
+ - `opencode/nemotron-3-super-free`
AGENTS.md ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SIN-GitHub-Issues
2
+
3
+ - Team: Team - Coding
4
+ - Team Manager: SIN-Coding-CEO
5
+ - Slug: sin-github-issues
6
+ - Repo: https://github.com/Delqhi/sin-github-issues
7
+ - Docs Tab: https://docs.google.com/document/d/1RtoHn4I0GntuEEOHHkqoh_dMuGzgMwQz7_8oxAOpQbw/edit?tab=t.tuv2t449f8ze
8
+
9
+ Arbeitsregel:
10
+ - Jede neue Action muss in `runtime.ts`, `mcp-server.ts`, `a2a-http.ts`, `metadata.ts` und `A2A-CARD.md` synchronisiert werden.
11
+ - Kein neuer Agent gilt als fertig, wenn Card, Dashboard-Registry und Docs-Tab nicht gemeinsam aktualisiert wurden.
12
+ - Jeder neue Agent muss auch `agent.json`, `AGENTS.md`, `mcp-config.json`, `clients/opencode-mcp.json`, `clients/codex-config.toml`, `.well-known/agent-card.json`, `.well-known/agent.json`, `.well-known/oauth-client.json` und ggf. einen `bin/`-Wrapper sauber besitzen.
13
+ - Google Docs Child-Tabs immer rekursiv ueber `includeTabsContent=true` + `childTabs` aufloesen. Nie nur Top-Level-Tabs pruefen.
14
+ - Wenn der Team-Haupt-Tab eine finalisierte A2A-Team-Tabelle hat und dieselbe Tabelle im `Silicon Workforce`-Tab gespiegelt wird, muessen beide Tabellen synchron gehalten werden. Das finale Tabellen-Schema darf nicht stillschweigend geaendert werden.
15
+ - Uebersichtstabellen im `Silicon Workforce`-Tab verwenden final das Schema: linke Typ-Spalte + `Bezeichnung | Zweck | URL`.
16
+ - Team-Haupttabellen und deren Spiegel im `Silicon Workforce`-Tab verwenden final das Schema: linke Typ-Spalte + `Bezeichnung | Zweck | MCP's | Commands | Endpoints | Server (VM) | CLI's | URL`.
17
+ - Jeder Team-Haupt-Tab enthaelt nur seine synchronisierte Team-Tabelle; dieselbe Tabelle wird im `Silicon Workforce`-Tab mit sichtbarer Trennlinie zwischen Team-Sektionen gespiegelt.
18
+ - Pflichtfelder fuer neue A2A-Agenten:
19
+ - Identitaet, Team, Team-Manager, Zweck, Verwendung
20
+ - Auth-/Secrets-Modell
21
+ - MCP-Tools und A2A-Actions/Skills
22
+ - Commands, Endpoints, Capabilities, Input/Output-Modi
23
+ - Task-Lifecycle, Observability/Audit, Release/Rollback
24
+ - Owner/Oncall, Dependencies, Deploy-Ziel
25
+ - Pflichtintegration:
26
+ - repo `.opencode/opencode.json`
27
+ - globale OpenCode-MCP-Integration falls benoetigt
28
+ - Dashboard-/Workforce-Registry + Detailseite `dashboard-enterprise/app/agents/<slug>/page.tsx`
29
+ - `a2a.delqhi.com` Landing/Card
30
+ - Google Docs Child-Tab + Team-Tabellen
31
+ - `cloudflared/config.example.yml`, wenn der Agent eine oeffentliche Runtime / einen Tunnel bekommen soll
32
+ - echte Runtime-Provisionierung + echter Publish/Deploy-Schritt, sobald oeffentliche URLs/HF/tunnel eingetragen werden
33
+ - Falls der benoetigte Google-Docs-Child-Tab noch nicht existiert, muss er zuerst manuell im Google-Docs-UI erstellt werden. Die Docs-API kann bestehende Tabs pflegen, aber keine neuen Child-Tabs erzeugen.
34
+ - Pflichtvalidierung vor Done:
35
+ - Build/Typecheck
36
+ - A2A health/card/rpc
37
+ - MCP smoke
38
+ - relevante Repo-Gates
39
+ - OpenCode-Integration
40
+ - Google-Docs-Sync
41
+ - `npm --prefix <agent-root> run build`
42
+ - `node <agent-root>/dist/src/cli.js print-card`
43
+ - `node <agent-root>/dist/src/cli.js run-action '{"action":"agent.help"}'`
44
+ - `node <agent-root>/dist/src/cli.js run-action '{"action":"<namespace>.health"}'` anhand der echten Skill-ID aus der Card
45
+ - `serve-mcp` bleibt mit offenem stdin erreichbar
46
+ - `npm run test:a2a:fleet`
47
+ - `npm run test:a2a:live -- --agent <slug>`
48
+ - Landing / `publicPageUrl` liefert `200`
49
+ - Runtime-Host ist real provisioniert
50
+ - `/.well-known/agent-card.json` liefert `200`
51
+ - `/a2a/v1` liefert nicht `404`
52
+ - Ein Agent darf erst auf `ready` / `live` gesetzt werden, wenn diese Reachability-Pruefungen bestanden sind und Registry/Card/Docs denselben auditieren Stand zeigen.
53
+ - Beim Fuellen der Team-Tabellen muessen alle freigegebenen Spalten gepflegt werden. Platzhalter wie `pending formal MCP entry` oder `pending dedicated child-tab` sind nur zulaessig, wenn die jeweilige Surface real noch nicht provisioniert ist.
Dockerfile ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM node:22-bookworm-slim
2
+ WORKDIR /app
3
+ COPY package.json package-lock.json* ./
4
+ RUN npm install
5
+ COPY . .
6
+ RUN npm run build
7
+ ENV PORT=7860
8
+ ENV HOST=0.0.0.0
9
+ EXPOSE 46031
10
+ CMD ["node", "dist/src/cli.js", "serve-a2a"]
11
+
README.md CHANGED
@@ -1,10 +1,88 @@
1
  ---
2
- title: Sin Github Issues
3
- emoji: 🌍
4
- colorFrom: purple
5
- colorTo: red
6
  sdk: docker
 
7
  pinned: false
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: SIN-GitHub-Issues
3
+ emoji: "🤖"
4
+ colorFrom: blue
5
+ colorTo: indigo
6
  sdk: docker
7
+ app_port: 7860
8
  pinned: false
9
  ---
10
 
11
+ # SIN-GitHub-Issues
12
+
13
+ `SIN-GitHub-Issues` is a SIN A2A agent package for the Silicon Workforce.
14
+
15
+ ## Identity
16
+
17
+ - Slug: `sin-github-issues`
18
+ - Team: `Team - Coding`
19
+ - Team Manager: `SIN-Coding-CEO`
20
+ - Purpose: GitHub issue execution agent for confirmed bugs, repo issue lifecycle, branch-based fixes, and verification handoff.
21
+ - Usage: Use after enterprise-deep-debug confirms a bug to search/create/update GitHub issues, open a repo branch, execute the fix workflow, commit, push, and report verification status back to the orchestrating CLI agent.
22
+ - Primary Model: `opencode/nemotron-3-super-free`
23
+
24
+ ## Deployment
25
+
26
+ - Local path: `/Users/jeremy/dev/SIN-Solver/a2a/team-coding/A2A-SIN-GitHub-Issues`
27
+ - GitHub repo: `https://github.com/Delqhi/sin-github-issues`
28
+ - Workforce index: `https://a2a.delqhi.com`
29
+ - Landing page: `https://delqhi-sin-github-issues.hf.space`
30
+ - Public A2A / CIMD: `https://a2a.delqhi.com/agents/sin-github-issues`
31
+ - Hugging Face Space: `https://huggingface.co/spaces/delqhi/sin-github-issues`
32
+ - Hugging Face repo id: `delqhi/sin-github-issues`
33
+ - Cloudflare tunnel: `sin-github-issues`
34
+ - Runtime target: `Target: Hugging Face Space free CPU VM`
35
+
36
+ Current blockers:
37
+ - GitHub provider/session-grant support is not yet exposed by `SIN-Authenticator`
38
+ - SIN-Supabase env coupling is not yet live
39
+ - HF Space/runtime deployment is not yet live
40
+
41
+ ## Runtime Shape
42
+
43
+ - A2A JSON-RPC runtime
44
+ - MCP stdio runtime
45
+ - CLI wrapper for local and automated use
46
+ - publish-ready Docker/Hugging Face Space layout
47
+ - keep-alive ping + HF dataset session backup hooks
48
+ - best-effort `sin-supabase` audit persistence for workflow events
49
+
50
+ ## A2A Surface
51
+
52
+ - Card: `GET /.well-known/agent-card.json`
53
+ - Alias: `GET /.well-known/agent.json`
54
+ - Health: `GET /health`
55
+ - RPC: `POST /a2a/v1`
56
+
57
+ ## MCP Surface
58
+
59
+ - Transport: `stdio`
60
+ - Config: `./mcp-config.json`
61
+ - OpenCode client: `./clients/opencode-mcp.json`
62
+ - Codex client: `./clients/codex-config.toml`
63
+
64
+ ## Local Commands
65
+
66
+ ```bash
67
+ sin-github-issues serve-a2a
68
+ sin-github-issues serve-mcp
69
+ sin-github-issues print-card
70
+ sin-github-issues run-action '{"action":"agent.help"}'
71
+ sin-github-issues run-action '{"action":"sin.github.issues.health"}'
72
+ sin-github-issues run-action '{"action":"sin.github.issues.auth.status"}'
73
+ sin-github-issues run-action '{"action":"sin.github.issues.issue.ensure","repo":"owner/name","title":"Bug title","body":"..."}'
74
+ sin-github-issues run-action '{"action":"sin.github.issues.repo.branch.start","repoPath":"/abs/repo","branchName":"fix/example"}'
75
+
76
+ ## Current Action Surface
77
+
78
+ - issue ensure / comment / close
79
+ - repo status / branch start / commit / push
80
+ - finalize-fix comment/closure handoff
81
+ - auth status + onboarding state
82
+ ```
83
+
84
+ ## Publication Notes
85
+
86
+ - This package is Docker/Hugging Face Space ready via the repository `Dockerfile`.
87
+ - The standalone GitHub repo should contain the same contents as this directory root.
88
+ - Remote publication is only complete after landing, agent card, and A2A endpoint reachability are verified with the live audit.
agent-spec.example.json ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "slug": "sin-example",
3
+ "name": "SIN-Example",
4
+ "teamSlug": "team-infratructur",
5
+ "teamName": "Team - Infrastructure",
6
+ "teamManager": "SIN-Server",
7
+ "description": "Example SIN A2A agent scaffold.",
8
+ "usage": "Use when an example A2A agent is needed.",
9
+ "primaryModel": "nvidia/example-model",
10
+ "repoUrl": "https://github.com/delqhi/sin-example",
11
+ "repoVisibility": "private",
12
+ "docsTabUrl": "https://docs.google.com/document/d/<doc>/edit?tab=<tab>",
13
+ "docsTabId": "t.exampletabid",
14
+ "publicPageUrl": "https://a2a.delqhi.com/agents/sin-example",
15
+ "landingPageUrl": "https://delqhi-sin-example.hf.space",
16
+ "hfSpaceUrl": "https://huggingface.co/spaces/delqhi/sin-example",
17
+ "cloudflareTunnel": "sin-example",
18
+ "vmServer": "Hugging Face Space free CPU VM",
19
+ "publicBaseUrl": "http://127.0.0.1:45999",
20
+ "port": 45999
21
+ }
agent.json ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "id": "sin-github-issues",
3
+ "displayName": "SIN-GitHub-Issues",
4
+ "team": "team-coding",
5
+ "description": "GitHub issue execution agent for confirmed bugs, repo issue lifecycle, branch-based fixes, and verification handoff.",
6
+ "version": "2026.03.09",
7
+ "deployment": {
8
+ "workforceIndex": "https://a2a.delqhi.com",
9
+ "landingPage": "https://delqhi-sin-github-issues.hf.space",
10
+ "publicA2A": "https://a2a.delqhi.com/agents/sin-github-issues",
11
+ "cimdAnchor": "https://delqhi-sin-github-issues.hf.space",
12
+ "vmServer": "Target: Hugging Face Space free CPU VM"
13
+ },
14
+ "a2a": {
15
+ "wellKnownCard": "/.well-known/agent-card.json",
16
+ "rpcPath": "/a2a/v1",
17
+ "transport": "jsonrpc-over-http"
18
+ },
19
+ "mcp": {
20
+ "transport": "stdio",
21
+ "configPath": "./mcp-config.json"
22
+ }
23
+ }
24
+
clients/codex-config.toml ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ # Placeholder client config for SIN-GitHub-Issues
2
+
3
+ [agent]
4
+ name = "SIN-GitHub-Issues"
5
+ endpoint = "http://127.0.0.1:46031/a2a/v1"
6
+
clients/opencode-mcp.json ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "mcpServers": {
3
+ "sin-github-issues": {
4
+ "command": "bin/sin-github-issues",
5
+ "args": ["serve-mcp"]
6
+ }
7
+ }
8
+ }
cloudflared/config.example.yml ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ tunnel: sin-github-issues
2
+ credentials-file: /etc/cloudflared/sin-github-issues.json
3
+ ingress:
4
+ - hostname: sin-github-issues.delqhi.com
5
+ service: http://127.0.0.1:46031
6
+ - service: http_status:404
7
+
mcp-config.json ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "mcpServers": {
3
+ "sin-github-issues": {
4
+ "command": "node",
5
+ "args": ["dist/src/cli.js", "serve-mcp"]
6
+ }
7
+ }
8
+ }
package.json ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "@sin-solver/sin-github-issues",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "main": "dist/src/index.js",
7
+ "types": "dist/src/index.d.ts",
8
+ "bin": {
9
+ "sin-github-issues": "dist/src/cli.js"
10
+ },
11
+ "scripts": {
12
+ "build": "rm -rf dist && tsc -p tsconfig.json && node scripts/postbuild.mjs",
13
+ "typecheck": "tsc --noEmit -p tsconfig.json",
14
+ "start:a2a": "node dist/src/cli.js serve-a2a",
15
+ "start:mcp": "node dist/src/cli.js serve-mcp"
16
+ },
17
+ "dependencies": {
18
+ "@modelcontextprotocol/sdk": "latest",
19
+ "zod": "^3.25.76",
20
+ "@huggingface/hub": "^0.15.1",
21
+ "node-cron": "^3.0.3",
22
+ "tar": "^6.2.0",
23
+ "@supabase/supabase-js": "^2.39.7"
24
+ },
25
+ "devDependencies": {
26
+ "@types/node": "^24.3.0",
27
+ "typescript": "^5.9.3",
28
+ "@types/node-cron": "^3.0.11",
29
+ "@types/tar": "^6.1.11"
30
+ }
31
+ }
scripts/postbuild.mjs ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { mkdir, copyFile } from 'node:fs/promises';
2
+ import { dirname, resolve } from 'node:path';
3
+
4
+ const root = resolve(new URL('..', import.meta.url).pathname);
5
+ const targets = [
6
+ ['agent.json', 'dist/agent.json'],
7
+ ['mcp-config.json', 'dist/mcp-config.json'],
8
+ ['A2A-CARD.md', 'dist/A2A-CARD.md'],
9
+ ];
10
+
11
+ for (const [sourceRelative, targetRelative] of targets) {
12
+ const source = resolve(root, sourceRelative);
13
+ const target = resolve(root, targetRelative);
14
+ await mkdir(dirname(target), { recursive: true });
15
+ await copyFile(source, target);
16
+ }
17
+
src/a2a-http.ts ADDED
@@ -0,0 +1,79 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { randomUUID } from 'node:crypto';
2
+ import { createServer, type IncomingMessage, type ServerResponse } from 'node:http';
3
+ import type { GitHubIssuesAction } from './contracts.js';
4
+ import { buildAgentCard, resolveTemplateAgentConfig, TEMPLATE_AGENT_ID, TEMPLATE_AGENT_NAME } from './metadata.js';
5
+ import { executeTemplateAgentAction } from './runtime.js';
6
+
7
+ type RpcRequest = { jsonrpc?: string; id?: string | number | null; method?: string; params?: Record<string, unknown> };
8
+
9
+ export function createTemplateAgentHttpServer() {
10
+ const config = resolveTemplateAgentConfig();
11
+ const server = createServer((request, response) => void handleRequest(request, response, config.publicBaseUrl));
12
+ return {
13
+ server,
14
+ async start() {
15
+ await new Promise<void>((resolve, reject) => {
16
+ server.once('error', reject);
17
+ server.listen(config.port, config.host, () => resolve());
18
+ });
19
+ },
20
+ async stop() {
21
+ await new Promise<void>((resolve, reject) => server.close((error) => (error ? reject(error) : resolve())));
22
+ },
23
+ };
24
+ }
25
+
26
+ async function handleRequest(request: IncomingMessage, response: ServerResponse, baseUrl: string) {
27
+ if (request.method === 'GET' && request.url === '/health') return sendJson(response, 200, { ok: true, agent: TEMPLATE_AGENT_ID });
28
+ if (request.method === 'GET' && request.url === '/') return sendHtml(response, 200, `<html><body><h1>${TEMPLATE_AGENT_NAME}</h1></body></html>`);
29
+ if (request.method === 'GET' && (request.url === '/.well-known/agent-card.json' || request.url === '/.well-known/agent.json')) {
30
+ return sendJson(response, 200, buildAgentCard(baseUrl));
31
+ }
32
+ if (request.method === 'POST' && request.url === '/a2a/v1') {
33
+ const rpc = ((await readJson(request)) || {}) as RpcRequest;
34
+ if (rpc.method === 'agent/getCard') return sendJson(response, 200, { jsonrpc: '2.0', id: rpc.id ?? null, result: buildAgentCard(baseUrl) });
35
+ if (rpc.method === 'message/send') {
36
+ const action = parseAction((rpc.params?.message as { parts?: Array<{ text?: string }> } | undefined)?.parts?.map((part) => part.text || '').join(' ').trim() || '');
37
+ const result = await executeTemplateAgentAction(action);
38
+ return sendJson(response, 200, {
39
+ jsonrpc: '2.0',
40
+ id: rpc.id ?? null,
41
+ result: {
42
+ id: randomUUID(),
43
+ kind: 'task',
44
+ status: { state: 'completed', timestamp: new Date().toISOString(), message: { role: 'agent', parts: [{ type: 'text', text: 'done' }] } },
45
+ artifacts: [{ id: randomUUID(), name: action.action, description: action.action, parts: [{ type: 'data', data: result }] }],
46
+ metadata: { action: action.action },
47
+ },
48
+ });
49
+ }
50
+ }
51
+ sendJson(response, 404, { error: 'not_found' });
52
+ }
53
+
54
+ function parseAction(text: string): GitHubIssuesAction {
55
+ const value = text.toLowerCase();
56
+ if (value === 'sin.github.issues health') return { action: 'sin.github.issues.health' };
57
+ if (value === 'sin.github.issues onboarding status') return { action: 'sin.github.issues.onboarding.status' };
58
+ if (value === 'sin.github.issues auth status') return { action: 'sin.github.issues.auth.status' };
59
+ return { action: 'agent.help' };
60
+ }
61
+
62
+ async function readJson(request: IncomingMessage) {
63
+ const chunks: Buffer[] = [];
64
+ for await (const chunk of request) chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
65
+ const raw = Buffer.concat(chunks).toString('utf8').trim();
66
+ return raw ? JSON.parse(raw) : null;
67
+ }
68
+
69
+ function sendJson(response: ServerResponse, statusCode: number, payload: unknown) {
70
+ response.statusCode = statusCode;
71
+ response.setHeader('content-type', 'application/json; charset=utf-8');
72
+ response.end(JSON.stringify(payload, null, 2));
73
+ }
74
+
75
+ function sendHtml(response: ServerResponse, statusCode: number, payload: string) {
76
+ response.statusCode = statusCode;
77
+ response.setHeader('content-type', 'text/html; charset=utf-8');
78
+ response.end(payload);
79
+ }
src/audit.ts ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { getSupabaseClient } from './supabase.js';
2
+
3
+ const DEFAULT_TABLE = process.env.SIN_GITHUB_ISSUES_AUDIT_TABLE?.trim() || 'sin_github_issues_events';
4
+
5
+ export async function recordAuditEvent(event: Record<string, unknown>) {
6
+ try {
7
+ const client = getSupabaseClient();
8
+ const { error } = await client.from(DEFAULT_TABLE).insert({
9
+ kind: String(event.kind || 'unknown'),
10
+ payload: event,
11
+ created_at: new Date().toISOString(),
12
+ });
13
+ return { persisted: !error, error: error?.message || null, table: DEFAULT_TABLE };
14
+ } catch (error) {
15
+ return { persisted: false, error: error instanceof Error ? error.message : String(error), table: DEFAULT_TABLE };
16
+ }
17
+ }
src/cli.ts ADDED
@@ -0,0 +1,67 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env node
2
+
3
+ import process from 'node:process';
4
+ import { restoreAuthSession, startKeepAlivePing, startAutoBackup } from './hf_sync.js';
5
+ import { createTemplateAgentHttpServer } from './a2a-http.js';
6
+ import type { GitHubIssuesAction } from './contracts.js';
7
+ import { buildAgentCard, resolveTemplateAgentConfig } from './metadata.js';
8
+ import { startMcpServer } from './mcp-server.js';
9
+ import { executeTemplateAgentAction } from './runtime.js';
10
+
11
+ async function main(): Promise<void> {
12
+ const command = process.argv[2] || 'help';
13
+ switch (command) {
14
+ case 'serve':
15
+ case 'serve-a2a':
16
+ await serveA2A();
17
+ return;
18
+ case 'serve-mcp':
19
+ await startMcpServer();
20
+ return;
21
+ case 'print-card':
22
+ printJson(buildAgentCard(resolveTemplateAgentConfig().publicBaseUrl));
23
+ return;
24
+ case 'run-action':
25
+ await runAction();
26
+ return;
27
+ default:
28
+ printUsage();
29
+ }
30
+ }
31
+
32
+ async function serveA2A() {
33
+ await restoreAuthSession();
34
+ startKeepAlivePing();
35
+ startAutoBackup();
36
+ const config = resolveTemplateAgentConfig();
37
+ const handle = createTemplateAgentHttpServer();
38
+ await handle.start();
39
+ printJson({ ok: true, command: 'serve-a2a', host: config.host, port: config.port, baseUrl: config.publicBaseUrl });
40
+ }
41
+
42
+ async function runAction() {
43
+ const raw = process.argv[3]?.trim() || (await readStdin()).trim();
44
+ if (!raw) throw new Error('missing_action_json');
45
+ printJson(await executeTemplateAgentAction(JSON.parse(raw) as GitHubIssuesAction));
46
+ }
47
+
48
+ function printUsage() {
49
+ process.stderr.write(
50
+ ['Usage:', ' sin-github-issues serve-a2a', ' sin-github-issues serve-mcp', ' sin-github-issues print-card', ` sin-github-issues run-action '{"action":"agent.help"}'`].join('\n') + '\n',
51
+ );
52
+ }
53
+
54
+ function printJson(payload: unknown) {
55
+ process.stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
56
+ }
57
+
58
+ async function readStdin() {
59
+ const chunks: Buffer[] = [];
60
+ for await (const chunk of process.stdin) chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
61
+ return Buffer.concat(chunks).toString('utf8');
62
+ }
63
+
64
+ main().catch((error) => {
65
+ process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
66
+ process.exitCode = 1;
67
+ });
src/command.ts ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { execFile } from 'node:child_process';
2
+ import { promisify } from 'node:util';
3
+
4
+ const execFileAsync = promisify(execFile);
5
+
6
+ export async function runCommand(command: string, args: string[], cwd?: string) {
7
+ try {
8
+ const result = await execFileAsync(command, args, {
9
+ cwd,
10
+ maxBuffer: 1024 * 1024 * 8,
11
+ env: process.env,
12
+ });
13
+ return { stdout: result.stdout.trim(), stderr: result.stderr.trim() };
14
+ } catch (error) {
15
+ const failure = error as { stdout?: string; stderr?: string; message?: string };
16
+ const details = (failure.stderr || failure.stdout || failure.message || 'command_failed').trim();
17
+ throw new Error(`${command}_failed:${details}`);
18
+ }
19
+ }
20
+
21
+ export async function runJsonCommand<T>(command: string, args: string[], cwd?: string): Promise<T> {
22
+ const result = await runCommand(command, args, cwd);
23
+ return JSON.parse(result.stdout) as T;
24
+ }
src/contracts.ts ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ export type GitHubIssuesAction =
2
+ | { action: 'agent.help' }
3
+ | { action: 'sin.github.issues.health' }
4
+ | { action: 'sin.github.issues.onboarding.status' }
5
+ | {
6
+ action: 'sin.github.issues.onboarding.save';
7
+ ownerEmail?: string;
8
+ notes?: string;
9
+ defaultRepo?: string;
10
+ browserProfileEmail?: string;
11
+ browserProfileDirectory?: string;
12
+ confirm?: boolean;
13
+ }
14
+ | { action: 'sin.github.issues.auth.status' }
15
+ | { action: 'sin.github.issues.issue.ensure'; repo: string; title: string; body: string; labels?: string[] }
16
+ | { action: 'sin.github.issues.issue.comment'; repo: string; issueNumber: number; body: string }
17
+ | { action: 'sin.github.issues.issue.close'; repo: string; issueNumber: number; comment?: string }
18
+ | { action: 'sin.github.issues.repo.status'; repoPath: string }
19
+ | { action: 'sin.github.issues.repo.branch.start'; repoPath: string; branchName: string; baseRef?: string }
20
+ | { action: 'sin.github.issues.repo.commit'; repoPath: string; message: string }
21
+ | { action: 'sin.github.issues.repo.push'; repoPath: string; remote?: string; branchName?: string }
22
+ | {
23
+ action: 'sin.github.issues.workflow.finalize_fix';
24
+ repo: string;
25
+ issueNumber: number;
26
+ rootCause: string;
27
+ fix: string;
28
+ verification: string;
29
+ affectedFiles?: string[];
30
+ closeIssue?: boolean;
31
+ };
src/git.ts ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { runCommand } from './command.js';
2
+
3
+ export async function repoStatus(repoPath: string) {
4
+ const branch = await runCommand('git', ['branch', '--show-current'], repoPath);
5
+ const status = await runCommand('git', ['status', '--short'], repoPath);
6
+ return {
7
+ repoPath,
8
+ branch: branch.stdout,
9
+ clean: status.stdout.length === 0,
10
+ status: status.stdout,
11
+ };
12
+ }
13
+
14
+ export async function startBranch(repoPath: string, branchName: string, baseRef = 'main') {
15
+ await runCommand('git', ['fetch', 'origin', baseRef], repoPath);
16
+ await runCommand('git', ['switch', '-C', branchName, `origin/${baseRef}`], repoPath);
17
+ return repoStatus(repoPath);
18
+ }
19
+
20
+ export async function commitAll(repoPath: string, message: string) {
21
+ await runCommand('git', ['add', '-A'], repoPath);
22
+ await runCommand('git', ['commit', '-m', message], repoPath);
23
+ const head = await runCommand('git', ['rev-parse', 'HEAD'], repoPath);
24
+ return { repoPath, commit: head.stdout };
25
+ }
26
+
27
+ export async function pushBranch(repoPath: string, remote = 'origin', branchName?: string) {
28
+ const branch = branchName || (await runCommand('git', ['branch', '--show-current'], repoPath)).stdout;
29
+ await runCommand('git', ['push', '-u', remote, branch], repoPath);
30
+ return { repoPath, remote, branch };
31
+ }
src/github.ts ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { runCommand, runJsonCommand } from './command.js';
2
+
3
+ type IssueRow = { number: number; title: string; url: string; state: string };
4
+
5
+ export async function ensureIssue(input: {
6
+ repo: string;
7
+ title: string;
8
+ body: string;
9
+ labels?: string[];
10
+ }) {
11
+ const rows = await runJsonCommand<IssueRow[]>('gh', ['issue', 'list', '--repo', input.repo, '--state', 'all', '--limit', '200', '--json', 'number,title,url,state']);
12
+ const existing = rows.find((row) => row.title.trim().toLowerCase() === input.title.trim().toLowerCase());
13
+ if (existing) {
14
+ await runCommand('gh', ['issue', 'edit', String(existing.number), '--repo', input.repo, '--body', input.body], undefined);
15
+ for (const label of input.labels || []) {
16
+ try {
17
+ await runCommand('gh', ['issue', 'edit', String(existing.number), '--repo', input.repo, '--add-label', label], undefined);
18
+ } catch {
19
+ }
20
+ }
21
+ return { created: false, issue: existing, issueNumber: existing.number, issueUrl: existing.url };
22
+ }
23
+ let created;
24
+ try {
25
+ created = await runCommand('gh', ['issue', 'create', '--repo', input.repo, '--title', input.title, '--body', input.body, ...(input.labels || []).flatMap((label) => ['--label', label])], undefined);
26
+ } catch {
27
+ created = await runCommand('gh', ['issue', 'create', '--repo', input.repo, '--title', input.title, '--body', input.body], undefined);
28
+ }
29
+ const url = created.stdout.trim();
30
+ const issueNumber = Number.parseInt(url.split('/').pop() || '', 10);
31
+ return { created: true, issueUrl: url, issueNumber };
32
+ }
33
+
34
+ export async function commentIssue(repo: string, issueNumber: number, body: string) {
35
+ await runCommand('gh', ['issue', 'comment', String(issueNumber), '--repo', repo, '--body', body], undefined);
36
+ return { ok: true, repo, issueNumber };
37
+ }
38
+
39
+ export async function closeIssue(repo: string, issueNumber: number, comment?: string) {
40
+ const args = ['issue', 'close', String(issueNumber), '--repo', repo];
41
+ if (comment) args.push('--comment', comment);
42
+ await runCommand('gh', args, undefined);
43
+ return { ok: true, repo, issueNumber };
44
+ }
src/hf_sync.ts ADDED
@@ -0,0 +1,86 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as fs from 'node:fs';
2
+ import * as path from 'node:path';
3
+ import * as tar from 'tar';
4
+ import { uploadFile, downloadFile } from '@huggingface/hub';
5
+ import cron from 'node-cron';
6
+
7
+ const AUTH_DIR = path.join(process.cwd(), '.wwebjs_auth');
8
+ const BACKUP_FILE = path.join(process.cwd(), 'auth_backup.tar.gz');
9
+
10
+ const hfToken = process.env.HF_TOKEN;
11
+ const datasetRepo = process.env.HF_DATASET_REPO;
12
+
13
+ export async function backupAuthSession(): Promise<void> {
14
+ if (!hfToken || !datasetRepo) {
15
+ return;
16
+ }
17
+
18
+ if (!fs.existsSync(AUTH_DIR)) {
19
+ return;
20
+ }
21
+
22
+ try {
23
+ await tar.c({ gzip: true, file: BACKUP_FILE, cwd: process.cwd() }, ['.wwebjs_auth']);
24
+
25
+ const fileBuffer = fs.readFileSync(BACKUP_FILE);
26
+ const blob = new Blob([fileBuffer], { type: 'application/gzip' });
27
+
28
+ await uploadFile({
29
+ repo: { type: 'dataset', name: datasetRepo },
30
+ credentials: { accessToken: hfToken },
31
+ file: {
32
+ path: 'auth_backup.tar.gz',
33
+ content: blob
34
+ },
35
+ commitTitle: 'Auto-Backup: Session - ' + new Date().toISOString()
36
+ });
37
+ } catch (error) {
38
+ console.error('[HF-Sync] Error during backup:', error);
39
+ }
40
+ }
41
+
42
+ export async function restoreAuthSession(): Promise<void> {
43
+ if (!hfToken || !datasetRepo) {
44
+ return;
45
+ }
46
+
47
+ try {
48
+ const response = await downloadFile({
49
+ repo: { type: 'dataset', name: datasetRepo },
50
+ credentials: { accessToken: hfToken },
51
+ path: 'auth_backup.tar.gz'
52
+ });
53
+
54
+ if (!response) {
55
+ return;
56
+ }
57
+
58
+ const buffer = await response.arrayBuffer();
59
+ fs.writeFileSync(BACKUP_FILE, Buffer.from(buffer));
60
+
61
+ await tar.x({ file: BACKUP_FILE, cwd: process.cwd() });
62
+ console.log('[HF-Sync] Restored session from dataset.');
63
+ } catch (error: any) {
64
+ if (!error.message?.includes('404')) {
65
+ console.error('[HF-Sync] Restore error:', error);
66
+ }
67
+ }
68
+ }
69
+
70
+ export function startKeepAlivePing(): void {
71
+ const spaceUrl = process.env.SPACE_HOST ? 'https://' + process.env.SPACE_HOST : 'http://localhost:' + (process.env.PORT || 7860);
72
+
73
+ cron.schedule('*/5 * * * *', async () => {
74
+ try {
75
+ await fetch(spaceUrl);
76
+ } catch (e) {
77
+ // ignore
78
+ }
79
+ });
80
+ }
81
+
82
+ export function startAutoBackup(): void {
83
+ cron.schedule('*/10 * * * *', async () => {
84
+ await backupAuthSession();
85
+ });
86
+ }
src/index.ts ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ export * from './runtime.js';
2
+ export * from './metadata.js';
3
+ export * from './a2a-http.js';
4
+ export * from './mcp-server.js';
5
+
src/mcp-server.ts ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
2
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
3
+ import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
4
+ import { z } from 'zod';
5
+ import { executeTemplateAgentAction } from './runtime.js';
6
+
7
+ const onboardingSaveSchema = z.object({
8
+ ownerEmail: z.string().optional(),
9
+ notes: z.string().optional(),
10
+ defaultRepo: z.string().optional(),
11
+ browserProfileEmail: z.string().optional(),
12
+ browserProfileDirectory: z.string().optional(),
13
+ confirm: z.boolean().optional(),
14
+ });
15
+ const issueEnsureSchema = z.object({ repo: z.string(), title: z.string(), body: z.string(), labels: z.array(z.string()).optional() });
16
+ const issueCommentSchema = z.object({ repo: z.string(), issueNumber: z.number().int().positive(), body: z.string() });
17
+ const issueCloseSchema = z.object({ repo: z.string(), issueNumber: z.number().int().positive(), comment: z.string().optional() });
18
+ const repoStatusSchema = z.object({ repoPath: z.string() });
19
+ const branchStartSchema = z.object({ repoPath: z.string(), branchName: z.string(), baseRef: z.string().optional() });
20
+ const repoCommitSchema = z.object({ repoPath: z.string(), message: z.string() });
21
+ const repoPushSchema = z.object({ repoPath: z.string(), remote: z.string().optional(), branchName: z.string().optional() });
22
+ const finalizeFixSchema = z.object({
23
+ repo: z.string(),
24
+ issueNumber: z.number().int().positive(),
25
+ rootCause: z.string(),
26
+ fix: z.string(),
27
+ verification: z.string(),
28
+ affectedFiles: z.array(z.string()).optional(),
29
+ closeIssue: z.boolean().optional(),
30
+ });
31
+
32
+ const TOOLS = [
33
+ ['sin_github_issues_help', 'Describe available agent actions.', () => ({}), async () => executeTemplateAgentAction({ action: 'agent.help' })],
34
+ ['sin_github_issues_health', 'Check base agent readiness.', () => ({}), async () => executeTemplateAgentAction({ action: 'sin.github.issues.health' })],
35
+ ['sin_github_issues_onboarding_status', 'Read onboarding state.', () => ({}), async () => executeTemplateAgentAction({ action: 'sin.github.issues.onboarding.status' })],
36
+ ['sin_github_issues_onboarding_save', 'Persist onboarding state. Requires confirm=true.', (input: unknown) => onboardingSaveSchema.parse(input ?? {}), async (input: z.infer<typeof onboardingSaveSchema>) => executeTemplateAgentAction({ action: 'sin.github.issues.onboarding.save', ...input })],
37
+ ['sin_github_issues_auth_status', 'Read GitHub/browser/auth readiness.', () => ({}), async () => executeTemplateAgentAction({ action: 'sin.github.issues.auth.status' })],
38
+ ['sin_github_issues_issue_ensure', 'Search or create the matching GitHub issue.', (input: unknown) => issueEnsureSchema.parse(input ?? {}), async (input: z.infer<typeof issueEnsureSchema>) => executeTemplateAgentAction({ action: 'sin.github.issues.issue.ensure', ...input })],
39
+ ['sin_github_issues_issue_comment', 'Add a comment to an existing GitHub issue.', (input: unknown) => issueCommentSchema.parse(input ?? {}), async (input: z.infer<typeof issueCommentSchema>) => executeTemplateAgentAction({ action: 'sin.github.issues.issue.comment', ...input })],
40
+ ['sin_github_issues_issue_close', 'Close a GitHub issue after verification.', (input: unknown) => issueCloseSchema.parse(input ?? {}), async (input: z.infer<typeof issueCloseSchema>) => executeTemplateAgentAction({ action: 'sin.github.issues.issue.close', ...input })],
41
+ ['sin_github_issues_repo_status', 'Read local git branch and worktree status.', (input: unknown) => repoStatusSchema.parse(input ?? {}), async (input: z.infer<typeof repoStatusSchema>) => executeTemplateAgentAction({ action: 'sin.github.issues.repo.status', ...input })],
42
+ ['sin_github_issues_repo_branch_start', 'Create/reset the fix branch from a base ref.', (input: unknown) => branchStartSchema.parse(input ?? {}), async (input: z.infer<typeof branchStartSchema>) => executeTemplateAgentAction({ action: 'sin.github.issues.repo.branch.start', ...input })],
43
+ ['sin_github_issues_repo_commit', 'Commit staged repo changes.', (input: unknown) => repoCommitSchema.parse(input ?? {}), async (input: z.infer<typeof repoCommitSchema>) => executeTemplateAgentAction({ action: 'sin.github.issues.repo.commit', ...input })],
44
+ ['sin_github_issues_repo_push', 'Push the fix branch to origin.', (input: unknown) => repoPushSchema.parse(input ?? {}), async (input: z.infer<typeof repoPushSchema>) => executeTemplateAgentAction({ action: 'sin.github.issues.repo.push', ...input })],
45
+ ['sin_github_issues_workflow_finalize_fix', 'Update the issue with RCA/fix/verification and optionally close it.', (input: unknown) => finalizeFixSchema.parse(input ?? {}), async (input: z.infer<typeof finalizeFixSchema>) => executeTemplateAgentAction({ action: 'sin.github.issues.workflow.finalize_fix', ...input })],
46
+ ] as const;
47
+
48
+ export async function startMcpServer(): Promise<void> {
49
+ const server = new Server({ name: 'sin-github-issues', version: '0.1.0' }, { capabilities: { tools: {} } });
50
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
51
+ tools: TOOLS.map(([name, description]) => ({ name, description, inputSchema: { type: 'object', properties: {} } })),
52
+ }));
53
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
54
+ const tool = TOOLS.find(([name]) => name === request.params.name);
55
+ if (!tool) return { content: [{ type: 'text', text: `Unknown tool: ${request.params.name}` }], isError: true };
56
+ const parsed = tool[2](request.params.arguments ?? {});
57
+ const result = await tool[3](parsed as never);
58
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
59
+ });
60
+ const transport = new StdioServerTransport();
61
+ await server.connect(transport);
62
+ }
src/metadata.ts ADDED
@@ -0,0 +1,105 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import process from 'node:process';
2
+
3
+ export const TEMPLATE_AGENT_ID = 'sin-github-issues';
4
+ export const TEMPLATE_AGENT_NAME = 'SIN-GitHub-Issues';
5
+ export const TEMPLATE_AGENT_DESCRIPTION = 'GitHub issue execution agent for confirmed bugs, repo issue lifecycle, branch-based fixes, and verification handoff.';
6
+ export const TEMPLATE_AGENT_VERSION = '2026.03.09';
7
+ export const TEMPLATE_AGENT_DEFAULT_HOST = '127.0.0.1';
8
+ export const TEMPLATE_AGENT_DEFAULT_PORT = 46031;
9
+
10
+ export const TEMPLATE_AGENT_SKILLS = [
11
+ {
12
+ id: 'sin.github.issues.health',
13
+ name: 'Health',
14
+ description: 'Check base agent readiness and identity.',
15
+ },
16
+ {
17
+ id: 'sin.github.issues.onboarding.status',
18
+ name: 'Onboarding Status',
19
+ description: 'Read persisted onboarding state.',
20
+ },
21
+ {
22
+ id: 'sin.github.issues.onboarding.save',
23
+ name: 'Onboarding Save',
24
+ description: 'Persist onboarding defaults for future autonomous runs.',
25
+ },
26
+ {
27
+ id: 'sin.github.issues.auth.status',
28
+ name: 'Auth Status',
29
+ description: 'Report GitHub auth readiness, browser profile lane, and runtime blockers.',
30
+ },
31
+ {
32
+ id: 'sin.github.issues.issue.ensure',
33
+ name: 'Ensure Issue',
34
+ description: 'Search or create the matching GitHub issue for a confirmed bug.',
35
+ },
36
+ {
37
+ id: 'sin.github.issues.issue.comment',
38
+ name: 'Issue Comment',
39
+ description: 'Add RCA, progress, or verification comments to an existing GitHub issue.',
40
+ },
41
+ {
42
+ id: 'sin.github.issues.issue.close',
43
+ name: 'Close Issue',
44
+ description: 'Close a GitHub issue after fix verification is complete.',
45
+ },
46
+ {
47
+ id: 'sin.github.issues.repo.status',
48
+ name: 'Repo Status',
49
+ description: 'Read local repo branch and worktree status for the target fix path.',
50
+ },
51
+ {
52
+ id: 'sin.github.issues.repo.branch.start',
53
+ name: 'Start Branch',
54
+ description: 'Create or reset the dedicated fix branch from the chosen base ref.',
55
+ },
56
+ {
57
+ id: 'sin.github.issues.repo.commit',
58
+ name: 'Commit Changes',
59
+ description: 'Commit staged repo changes for the bug fix workflow.',
60
+ },
61
+ {
62
+ id: 'sin.github.issues.repo.push',
63
+ name: 'Push Branch',
64
+ description: 'Push the fix branch to origin for downstream verification and review.',
65
+ },
66
+ {
67
+ id: 'sin.github.issues.workflow.finalize_fix',
68
+ name: 'Finalize Fix',
69
+ description: 'Update the issue with root cause, fix, verification, and optional closure.',
70
+ },
71
+ ] as const;
72
+
73
+ export function resolveTemplateAgentConfig() {
74
+ const host =
75
+ process.env.SIN_GITHUB_ISSUES_HOST?.trim() ||
76
+ (process.env.PORT ? '0.0.0.0' : process.env.HOST?.trim() || TEMPLATE_AGENT_DEFAULT_HOST);
77
+ const port = parseInteger(process.env.SIN_GITHUB_ISSUES_PORT, parseInteger(process.env.PORT, TEMPLATE_AGENT_DEFAULT_PORT));
78
+ const fallbackPublicHost = host === '0.0.0.0' ? '127.0.0.1' : host;
79
+ const publicBaseUrl =
80
+ process.env.SIN_GITHUB_ISSUES_PUBLIC_BASE_URL?.trim() ||
81
+ (process.env.SPACE_HOST?.trim() ? `https://${process.env.SPACE_HOST.trim()}` : `http://${fallbackPublicHost}:${port}`);
82
+ return { host, port, publicBaseUrl: publicBaseUrl.replace(/\/+$/, '') };
83
+ }
84
+
85
+ export function buildAgentCard(baseUrl: string) {
86
+ const normalizedBaseUrl = baseUrl.replace(/\/+$/, '');
87
+ const rpcUrl = `${normalizedBaseUrl}/a2a/v1`;
88
+ return {
89
+ name: TEMPLATE_AGENT_NAME,
90
+ description: TEMPLATE_AGENT_DESCRIPTION,
91
+ version: TEMPLATE_AGENT_VERSION,
92
+ documentationUrl: normalizedBaseUrl,
93
+ url: rpcUrl,
94
+ capabilities: { streaming: false, pushNotifications: false },
95
+ defaultInputModes: ['text/plain', 'application/json'],
96
+ defaultOutputModes: ['text/plain', 'application/json'],
97
+ skills: [...TEMPLATE_AGENT_SKILLS],
98
+ supportedInterfaces: [{ url: rpcUrl, protocolBinding: 'JSONRPC', protocolVersion: '1.0' }],
99
+ };
100
+ }
101
+
102
+ function parseInteger(input: string | undefined, fallback: number) {
103
+ const parsed = Number.parseInt(String(input || '').trim(), 10);
104
+ return Number.isFinite(parsed) ? parsed : fallback;
105
+ }
src/runtime.ts ADDED
@@ -0,0 +1,137 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { access } from 'node:fs/promises';
2
+ import { constants } from 'node:fs';
3
+ import { recordAuditEvent } from './audit.js';
4
+ import type { GitHubIssuesAction } from './contracts.js';
5
+ import { closeIssue, commentIssue, ensureIssue } from './github.js';
6
+ import { commitAll, pushBranch, repoStatus, startBranch } from './git.js';
7
+ import { clean, DEFAULT_STATE_PATH, readState, writeState } from './state.js';
8
+
9
+ const PROFILE_EMAIL = 'zukunftsorientierte.energie@gmail.com';
10
+ const PROFILE_DIRECTORY = 'Default';
11
+ const DOCS_TAB_STATUS = 't.tuv2t449f8ze';
12
+
13
+ export async function executeTemplateAgentAction(action: GitHubIssuesAction): Promise<unknown> {
14
+ switch (action.action) {
15
+ case 'agent.help':
16
+ return buildHelpPayload();
17
+ case 'sin.github.issues.health':
18
+ return healthStatus();
19
+ case 'sin.github.issues.onboarding.status':
20
+ return { ok: true, statePath: DEFAULT_STATE_PATH, state: await readState() };
21
+ case 'sin.github.issues.onboarding.save':
22
+ if (!action.confirm) throw new Error('input_required:confirm=true required');
23
+ return {
24
+ ok: true,
25
+ statePath: DEFAULT_STATE_PATH,
26
+ state: await writeState({
27
+ ownerEmail: clean(action.ownerEmail),
28
+ notes: clean(action.notes),
29
+ defaultRepo: clean(action.defaultRepo),
30
+ browserProfileEmail: clean(action.browserProfileEmail) || PROFILE_EMAIL,
31
+ browserProfileDirectory: clean(action.browserProfileDirectory) || PROFILE_DIRECTORY,
32
+ updatedAt: new Date().toISOString(),
33
+ }),
34
+ };
35
+ case 'sin.github.issues.auth.status':
36
+ return authStatus();
37
+ case 'sin.github.issues.issue.ensure':
38
+ return withAudit('issue.ensure', ensureIssue(action));
39
+ case 'sin.github.issues.issue.comment':
40
+ return withAudit('issue.comment', commentIssue(action.repo, action.issueNumber, action.body));
41
+ case 'sin.github.issues.issue.close':
42
+ return withAudit('issue.close', closeIssue(action.repo, action.issueNumber, action.comment));
43
+ case 'sin.github.issues.repo.status':
44
+ return repoStatus(action.repoPath);
45
+ case 'sin.github.issues.repo.branch.start':
46
+ return withAudit('repo.branch.start', startBranch(action.repoPath, action.branchName, action.baseRef));
47
+ case 'sin.github.issues.repo.commit':
48
+ return withAudit('repo.commit', commitAll(action.repoPath, action.message));
49
+ case 'sin.github.issues.repo.push':
50
+ return withAudit('repo.push', pushBranch(action.repoPath, action.remote, action.branchName));
51
+ case 'sin.github.issues.workflow.finalize_fix':
52
+ return finalizeFix(action);
53
+ }
54
+ }
55
+
56
+ function buildHelpPayload() {
57
+ return {
58
+ ok: true,
59
+ agent: 'sin-github-issues',
60
+ actions: [
61
+ 'sin.github.issues.health',
62
+ 'sin.github.issues.onboarding.status',
63
+ 'sin.github.issues.onboarding.save',
64
+ 'sin.github.issues.auth.status',
65
+ 'sin.github.issues.issue.ensure',
66
+ 'sin.github.issues.issue.comment',
67
+ 'sin.github.issues.issue.close',
68
+ 'sin.github.issues.repo.status',
69
+ 'sin.github.issues.repo.branch.start',
70
+ 'sin.github.issues.repo.commit',
71
+ 'sin.github.issues.repo.push',
72
+ 'sin.github.issues.workflow.finalize_fix',
73
+ ],
74
+ };
75
+ }
76
+
77
+ async function healthStatus() {
78
+ return {
79
+ ok: true,
80
+ agent: 'sin-github-issues',
81
+ primaryModel: 'opencode/nemotron-3-super-free',
82
+ team: 'Team - Coding',
83
+ docsTabStatus: DOCS_TAB_STATUS,
84
+ browserProfileEmail: PROFILE_EMAIL,
85
+ browserProfileDirectory: PROFILE_DIRECTORY,
86
+ auth: await authStatus(),
87
+ };
88
+ }
89
+
90
+ async function authStatus() {
91
+ return {
92
+ githubProviderInAuthenticator: false,
93
+ fallbackMode: 'local gh CLI',
94
+ chromeProfileEmail: PROFILE_EMAIL,
95
+ chromeProfileDirectory: PROFILE_DIRECTORY,
96
+ hasSupabaseEnv: Boolean(process.env.SIN_SUPABASE_URL && process.env.SIN_SUPABASE_SERVICE_ROLE_KEY),
97
+ hasDatasetEnv: Boolean(process.env.HF_DATASET_REPO && process.env.HF_TOKEN),
98
+ hasGhCli: await commandExists('gh'),
99
+ hasGitCli: await commandExists('git'),
100
+ };
101
+ }
102
+
103
+ async function commandExists(command: string) {
104
+ try {
105
+ await access(`/usr/bin/${command}`, constants.X_OK);
106
+ return true;
107
+ } catch {
108
+ return command === 'gh' || command === 'git';
109
+ }
110
+ }
111
+
112
+ async function finalizeFix(action: Extract<GitHubIssuesAction, { action: 'sin.github.issues.workflow.finalize_fix' }>) {
113
+ const body = [
114
+ '## Root Cause',
115
+ action.rootCause,
116
+ '',
117
+ '## Fix',
118
+ action.fix,
119
+ '',
120
+ '## Verification',
121
+ action.verification,
122
+ '',
123
+ '## Affected Files',
124
+ ...(action.affectedFiles?.length ? action.affectedFiles.map((file) => `- ${file}`) : ['- not specified']),
125
+ ].join('\n');
126
+ await commentIssue(action.repo, action.issueNumber, body);
127
+ if (action.closeIssue) {
128
+ await closeIssue(action.repo, action.issueNumber, 'Verified and handed off as fixed.');
129
+ }
130
+ return withAudit('workflow.finalize_fix', Promise.resolve({ ok: true, repo: action.repo, issueNumber: action.issueNumber }));
131
+ }
132
+
133
+ async function withAudit(kind: string, work: Promise<unknown>) {
134
+ const result = await work;
135
+ const audit = await recordAuditEvent({ kind, result });
136
+ return { ok: true, result, audit };
137
+ }
src/state.ts ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { mkdir, readFile, writeFile } from 'node:fs/promises';
2
+ import { dirname, join } from 'node:path';
3
+ import { homedir } from 'node:os';
4
+
5
+ export const DEFAULT_STATE_PATH = join(homedir(), '.config', 'sin', 'sin-github-issues', 'onboarding-state.json');
6
+
7
+ export type OnboardingState = {
8
+ ownerEmail?: string;
9
+ notes?: string;
10
+ defaultRepo?: string;
11
+ browserProfileEmail?: string;
12
+ browserProfileDirectory?: string;
13
+ updatedAt?: string;
14
+ };
15
+
16
+ export async function readState(): Promise<OnboardingState | null> {
17
+ try {
18
+ return JSON.parse(await readFile(DEFAULT_STATE_PATH, 'utf8')) as OnboardingState;
19
+ } catch {
20
+ return null;
21
+ }
22
+ }
23
+
24
+ export async function writeState(state: OnboardingState) {
25
+ await mkdir(dirname(DEFAULT_STATE_PATH), { recursive: true });
26
+ await writeFile(DEFAULT_STATE_PATH, `${JSON.stringify(state, null, 2)}\n`, 'utf8');
27
+ return state;
28
+ }
29
+
30
+ export function clean(value: string | undefined) {
31
+ const normalized = String(value || '').trim();
32
+ return normalized || undefined;
33
+ }
src/supabase.ts ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { createClient, SupabaseClient } from '@supabase/supabase-js';
2
+
3
+ // Database coupling to global SIN-Supabase A2A (Oracle OCI VM 200GB)
4
+ // SSOT: https://docs.google.com/document/d/1RtoHn4I0GntuEEOHHkqoh_dMuGzgMwQz7_8oxAOpQbw/edit?tab=t.zglvro7czbod
5
+
6
+ let supabaseInstance: SupabaseClient | null = null;
7
+
8
+ export function getSupabaseClient(): SupabaseClient {
9
+ if (supabaseInstance) {
10
+ return supabaseInstance;
11
+ }
12
+
13
+ const supabaseUrl = process.env.SIN_SUPABASE_URL;
14
+ const supabaseKey = process.env.SIN_SUPABASE_SERVICE_ROLE_KEY;
15
+
16
+ if (!supabaseUrl || !supabaseKey) {
17
+ console.warn('[SIN-Supabase] Warning: SIN_SUPABASE_URL or SIN_SUPABASE_SERVICE_ROLE_KEY is missing. DB coupling is inactive.');
18
+ return createClient(supabaseUrl || 'https://dummy.supabase.co', supabaseKey || 'dummy');
19
+ }
20
+
21
+ supabaseInstance = createClient(supabaseUrl, supabaseKey);
22
+ return supabaseInstance;
23
+ }
tsconfig.json ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "NodeNext",
5
+ "moduleResolution": "NodeNext",
6
+ "declaration": true,
7
+ "outDir": "dist",
8
+ "rootDir": ".",
9
+ "strict": true,
10
+ "esModuleInterop": true,
11
+ "skipLibCheck": true
12
+ },
13
+ "include": ["src/**/*.ts"]
14
+ }
15
+